# openresty 中的代码覆率解决方案

1. 可以外在的记录每一行的代码
2. 在记录的同时，可以知道这一行的代码上下文是什么
3. 更重要的是，我们需要尽可能的不动现有业务代码

Sets the given function as a hook. The string mask and the number count describe when the hook will be called. The string mask may have any combination of the following characters, with the given meaning:

• 'c': the hook is called every time Lua calls a function;
• 'r': the hook is called every time Lua returns from a function;
• 'l': the hook is called every time Lua enters a new line of code.Moreover, with a count different from zero, the hook is called also after every count instructions.

When called without arguments, debug.sethook turns off the hook.
When the hook is called, its first argument is a string describing the event that has triggered its call: "call" (or "tail call"), "return", "line", and "count". For line events, the hook also gets the new line number as its second parameter. Inside a hook, you can call getinfo with level 2 to get more information about the running function (level 0 is the getinfo function, and level 1 is the hook function).

• 'c' 当lua开始执行一个方法时调用;
• 'r' 当lua执行一个方法在返回时调用;
• 'l' 当lua每执行到一行代码时调用.即lua从0开始执行一个方法的每一行时，这个钩子都会被调用.

Returns a table with information about a function. You can give the function directly or you can give a number as the value of f, which means the function running at level f of the call stack of the given thread: level 0 is the current function (getinfo itself); level 1 is the function that called getinfo (except for tail calls, which do not count on the stack); and so on. If f is a number larger than the number of active functions, then getinfo returns nil.

The returned table can contain all the fields returned by lua_getinfo, with the string what describing which fields to fill in. The default for what is to get all information available, except the table of valid lines. If present, the option 'f' adds a field named func with the function itself. If present, the option 'L' adds a field named activelines with the table of valid lines.

For instance, the expression debug.getinfo(1,"n").name returns a name for the current function, if a reasonable name can be found, and the expression debug.getinfo(print) returns a table with all available information about the print function.

OK,有了这两个方法,我们的思路就变得很清析了：

1. 在生命周期开始时注册钩子函数.
2. 将每一次调用情况记录汇总.

local debug = load "debug"
local M = {}
local mt = { __index = M }
local sharekey = 'test_hook'
local cachekey = 'test_hook'
function M:new()
local ins = {}
local share = ngx.shared[sharekey]
local info ,ret = share:get(cachekey)
if info then
info = cjson.decode(info)
else
info = {}
end
ins.info = info
setmetatable(ins,mt)
return ins
end
function M:sethook ()
debug.sethook(function(event,line)
local info = debug.getinfo(2)
local s = info.short_src
local f = info.name
local startline = info.linedefined
local endline = info.lastlinedefined
if  string.find(s,"lualib") ~= nil then
return
end
if self.info[s] == nil then
self.info[s]={}
end
if f == nil then
return
end
if self.info[s][f] ==nil then
self.info[s][f]={
start = startline,
endline=endline,
exec = {},
activelines = debug.getinfo(2,'L').activelines
}
end
self.info[s][f].exec[tostring(line)]=true

end,'l')
end
function M:save()
local share = ngx.shared[sharekey]
local ret = share:set(cachekey,cjson.encode(self.info),120000)
end
function M:delete()
local share = ngx.shared[sharekey]
local ret = share:delete(cachekey)
self.info = {}
end
function M:get_report()
local res = {}
for f,v in pairs(self.info) do
item = {
file=f,
funcs={}
}
for m ,i in pairs(v) do
local cover = 0
local index = 0
for c,code in pairs(i.activelines) do
if i.activelines[c] then
index = index + 1
end
if i.exec[tostring(c)] or i.exec[c] then
cover = cover +1
end
end
item.funcs[#item.funcs+1] = { name = m ,coverage=   string.format("%.2f",cover / index*100 ) .."%"}
end
res[#res+1]=item
end
return res
end
return M


local hook = load "libs.test.hook"
local test = hook:new()
test:sethook()
--other code ..


test:save()


local hook = require 'libs.test.hook'
local router =  lor:Router ()
local M = {}
router:get('/test/coverage/json-report',
function(req,res,next)
local t = hook:new()
res:json(t:get_report())
end)
router:get('/test/coverage/txt-report',
function(req,res,next)
local t = hook:new()
local msg = "Report"
local rpt = t:get_report()
for i ,v in pairs(rpt) do
msg =msg.."\r\n"..v.file
for j,f in pairs(v.funcs) do
msg = msg .."\r\n\t function name:" .. f.name .."\tcoverage:"..f.coverage
end
end
msg =msg .."\r\nEnd"
res:send(msg)
end)
return router


posted @ 2018-12-20 08:49  葛云飞  阅读(738)  评论(0编辑  收藏  举报