[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Getting coverage
- From: peterhickman <peterhi@...>
- Date: Sat, 5 Jan 2008 16:47:36 +0000
As I'm hacking away at my Lua xml parser (plxml.lua mentioned a
couple of weeks ago) I wanted to check that my tests were giving me
the coverage that I wanted. Not finding anything I hacked this up:
-- coverage.lua
-- The name of the source file to match
local match = nil
-- A table to hold the lines being encountered
local lines = {}
-- The highest line number encountered
local max = 0
local function hook()
local info = debug.getinfo(2,"Sl")
local source = info.source
if(string.match(source,match)) then
local line = info.currentline
if(lines[line] == nil) then
lines[line] = 1
if(line > max) then
max = line
end
else
lines[line] = lines[line] + 1
end
end
end
match = arg[1]
for p = 2,#arg do
local filename = arg[p]
local f = assert(loadfile(filename))
debug.sethook(hook, "l")
f()
debug.sethook()
end
local out = io.open('coverage.txt','w')
for p = 1,max do
if(lines[p] ~= nil) then
out:write(p .. ' ' .. lines[p] .. "\n")
end
end
out:close()
Which is used thus:
$ lua coverage.lua plxml.lua tests/*
The second argument 'plxml.lua' is the source I want to match and the
rest of the arguments are the tests I run to make sure that my code
is still working. This all results in a file called 'coverage.txt'
that contains two numbers per line. The first in the line number and
the second the number of times that the line was encountered.
-- coverage.txt
15 1
16 1
17 1
18 1
19 1
21 1
27 54
28 54
29 54
30 54
...
This file, coverage.txt, and the source we were checking (plxml.lua)
are then run through another program to create a report.
$ lua mc.lua plxml.lua coverage.txt
Which gives this report to standard out:
...
102 true local text = data:sub(start+1,-3)
103 true return newpi( name, text )
104 true end
105 true
106 true local function makedoctype( data )
107 true -- The data is between the '<!' and the final
'>'
108 false local text = data:sub(3,-2)
109 false return newdoctype( text )
110 true end
111 true
112 true local function makeelement( data )
113 true local name = ''
114 true local attributes = {}
115 true
116 true local pos = data:find(' ')
117 true if(pos) then
...
439 true end
440 true
441 true return text
442 true end
443 true
444 true
------------------------------------------------------------------------
--------
Total lines in file ..: 444
Total lines of code ..: 319
Code covered .........: 309 (96.87%)
Code missed ..........: 10
The first number is the line number, the second is true when the line
is either non code (blank or comment) or has been reported by the
coverage tool. Or it says false if it is code and has not been
reported in the coverage file. Then you have the source line.
Presently cannot handle multiline strings or comments (but then these
have yet to appear in my code) and coverage.lua has had to hack a few
lines that the debug hook does not seem to catch.
I've found it useful, it's pointed out a completely useless function
that I had in the source but never called and showed that there were
some cases that the tests were not covering so I will have to beef up
the tests.
Have I just reinvented the wheel or would someone find this useful? I
feel a LuaForge project coming on (other than plxml.lua).
Oh here's the mc.lua file:
-- mc.lua
-- Is this line blank or is it just a comment
local function blank( data )
local text = data
-- Remove the comments
local pos = text:find('--', 1, true)
if(pos) then
if(pos == 1) then
text = ''
else
text = text:sub(1,pos-1)
end
end
-- Remove whitespace
text = text:gsub('%s','')
return text == ''
end
-- Read the source in
local function readsource( filename )
local inp = io.open(filename,'r')
local lines = {}
while true do
local line = inp:read('*line')
if not line then break end
local data = {}
data.source = line
data.ignore = blank(line)
data.count = 0
lines[#lines+1] = data
end
inp:close()
return lines
end
-- Add the coverage information
local function readcoverage( filename, lines )
local inp = io.open(filename,'r')
while true do
local line, count = inp:read('*number', '*number')
if not line then break end
lines[line].count = count
end
inp:close()
return lines
end
-- Some lines don't seem to be counted
--
-- 1) end
-- 2) else
-- 3) local function
-- 4) return function
local function patchup( lines )
for k,v in ipairs(lines) do
if(v.ignore == false) then
if(v.count == 0) then
-- print(v.source)
if(string.match(v.source, "^%s+end%s*$") ~= nil) then
v.count = -1
elseif(string.match(v.source, "^%s+else%s*$") ~= nil) then
v.count = -1
elseif(string.match(v.source, "%s*local%s+function%s+") ~= nil) then
v.count = -1
elseif(string.match(v.source, "%s*return%s+function%s*%(") ~=
nil) then
v.count = -1
end
end
end
end
return lines
end
local sourcefilename = arg[1]
local coverfilename = arg[2]
local lines = readsource(sourcefilename)
lines = readcoverage(coverfilename, lines)
lines = patchup(lines)
local total_source_lines = #lines
local total_code_lines = 0
local total_code_covered = 0
for k,v in ipairs(lines) do
local ok = true
if(v.ignore == false) then
total_code_lines = total_code_lines + 1
if(v.count == 0) then
ok = false
else
total_code_covered = total_code_covered + 1
end
end
print(k,ok,v.source)
end
print()
print("Total lines in file ..: " .. total_source_lines)
print("Total lines of code ..: " .. total_code_lines)
print("Code covered .........: " .. total_code_covered .. " (" ..
string.format("%.2f",(total_code_covered / total_code_lines *
100)) .. "%)")
print("Code missed ..........: " .. (total_code_lines -
total_code_covered))
--
Genius has its limitations. Stupidity is not thus handicapped.