-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcoverage.lua
More file actions
152 lines (142 loc) · 3.83 KB
/
coverage.lua
File metadata and controls
152 lines (142 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
--[[
each mod that requires `coverage.lua` will have a remote "__coverage_modname"
with start(testname), stop(), and dump()
and will check coverage.isrunning on startup for an ongoing test
data is saved in a table by testname, short_src and line number:
coveragedata = {
levelpath = { modname = "", basepath = "" }
tests = {
[testname] = {
[short_src] = {
lines = {
[linenumber] = count
},
funcs = {
[linedefined] = {
names = {name=true,...},
linedefined = linedefined,
count = count,
},
},
}
}
}
}
--]]
local levelpath
local _Coverage = {
Start = function(testname)
remote.call("coverage","start",testname)
end,
Stop = function()
remote.call("coverage","stop")
end,
Report = function()
remote.call("coverage","report")
end,
LevelPath = function(modname,basepath)
assert(script.mod_name == "level")
levelpath = {
modname = modname,
basepath = basepath,
}
end,
}
local tests = {}
local function start(testname)
if not testname then testname = "" end
local test = tests[testname]
if not test then
test = {}
tests[testname] = test
end
local getinfo = debug.getinfo
local sub = string.sub
debug.sethook(function(event,line)
if event == "line" then
local s = getinfo(2,"S").source
-- startup logging gets all the serpent loads of `global`
-- serpent itself will also always show up as one of these
if sub(s,1,1) ~= "@" then
return
else
s = sub(s,2)
end
local fileinfo = test[s]
if not fileinfo then
fileinfo = {}
test[s] = fileinfo
end
local lines = fileinfo.lines
if not lines then
lines = {}
fileinfo.lines = lines
end
lines[line] = (lines[line] or 0) + 1
elseif event == "call" or event == "tail call" then
local info = getinfo(2,"nS")
local s = info.source
-- startup logging gets all the serpent loads of `global`
-- serpent itself will also always show up as one of these
if sub(s,1,1) ~= "@" then
return
else
s = sub(s,2)
end
local fileinfo = test[s]
if not fileinfo then
fileinfo = {}
test[s] = fileinfo
end
local funcs = fileinfo.funcs
if not funcs then
funcs = {}
fileinfo.funcs = funcs
end
local func = funcs[info.linedefined]
if not func then
func = {
linedefined = info.linedefined,
names = {},
count = 1, -- we got here by calling it, so start at one hit...
}
funcs[info.linedefined] = func
-- it's a new function, so add all the lines with zero hitcount
for line,_ in pairs(getinfo(2,"L").activelines) do
local lines = fileinfo.lines
if not lines then
lines = {}
fileinfo.lines = lines
end
lines[line] = (lines[line] or 0)
end
else
func.count = func.count + 1
end
local name = info.name
if name and name ~= "?" then
func.names[name] = (func.names[name] or 0) + 1
end
end
end,"cl")
end
log("coverage registered for " .. script.mod_name)
remote.add_interface("__coverage_" .. script.mod_name ,{
start = start,
stop = function()
debug.sethook()
end,
dump = function()
local dump = {tests = tests}
if script.mod_name == "level" then
dump.levelpath = levelpath
end
tests = {}
return dump
end
})
if settings.global["coverage-startup"].value then
log("startup coverage for " .. script.mod_name)
start("startup")
end
return _Coverage