Update unit testing to output coverage reports
authorMatthew Wild <mwild1@gmail.com>
Thu, 20 Nov 2008 21:02:49 +0000
changeset 361 a2d83b04d769
parent 360 e918c979ad1a
child 362 af21058d565e
Update unit testing to output coverage reports
tests/reports/empty
tests/test.lua
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/reports/empty	Thu Nov 20 21:02:49 2008 +0000
@@ -0,0 +1,1 @@
+This file was intentionally left blank.
--- a/tests/test.lua	Thu Nov 20 03:00:43 2008 +0000
+++ b/tests/test.lua	Thu Nov 20 21:02:49 2008 +0000
@@ -1,3 +1,10 @@
+
+function run_all_tests()
+	dotest "util.jid"
+	dotest "core.stanza_router"
+	dotest "core.s2smanager"
+	dotest "core.configmanager"
+end
 
 local verbosity = tonumber(arg[1]) or 2;
 
@@ -36,7 +43,8 @@
 	
 	local unit = setmetatable({}, { __index = setmetatable({ module = function () end }, { __index = _G }) });
 
-	local chunk, err = loadfile("../"..unitname:gsub("%.", "/")..".lua");
+	local fn = "../"..unitname:gsub("%.", "/")..".lua";
+	local chunk, err = loadfile(fn);
 	if not chunk then
 		print("WARNING: ", "Failed to load module: "..unitname, err);
 		return;
@@ -50,21 +58,29 @@
 	end
 	
 	for name, f in pairs(unit) do
+		local test = rawget(tests, name);
 		if type(f) ~= "function" then
 			if verbosity >= 3 then
 				print("INFO: ", "Skipping "..unitname.."."..name.." because it is not a function");
 			end
-		elseif type(tests[name]) ~= "function" then
+		elseif type(test) ~= "function" then
 			if verbosity >= 1 then
 				print("WARNING: ", unitname.."."..name.." has no test!");
 			end
 		else
-			local success, ret = pcall(tests[name], f, unit);
+			local line_hook, line_info = new_line_coverage_monitor(fn);
+			debug.sethook(line_hook, "l")
+			local success, ret = pcall(test, f, unit);
+			debug.sethook();
 			if not success then
 				print("TEST FAILED! Unit: ["..unitname.."] Function: ["..name.."]");
 				print("   Location: "..ret:gsub(":%s*\n", "\n"));
+				line_info(name, false);
 			elseif verbosity >= 2 then
 				print("TEST SUCCEEDED: ", unitname, name);
+				print(string.format("TEST COVERED %d/%d lines", line_info(name, true)));
+			else
+				line_info(name, success);
 			end
 		end
 	end
@@ -81,6 +97,45 @@
 	end
 end
 
-dotest "util.jid"
-dotest "core.stanza_router"
-dotest "core.s2smanager"
+function new_line_coverage_monitor(file)
+	local lines_hit, funcs_hit = {}, {};
+	local total_lines, covered_lines = 0, 0;
+	
+	for line in io.lines(file) do
+		total_lines = total_lines + 1;
+	end
+	
+	return function (event, line) -- Line hook
+			if not lines_hit[line] then
+				local info = debug.getinfo(2, "fSL")
+				if not info.source:find(file) then return; end
+				if not funcs_hit[info.func] and info.activelines then
+					funcs_hit[info.func] = true;
+					for line in pairs(info.activelines) do
+						lines_hit[line] = false; -- Marks it as hittable, but not hit yet
+					end
+				end
+				if lines_hit[line] == false then
+					--print("New line hit: "..line.." in "..debug.getinfo(2, "S").source);
+					lines_hit[line] = true;
+					covered_lines = covered_lines + 1;
+				end
+			end
+		end,
+		function (test_name, success) -- Get info
+			local fn = file:gsub("^%W*", "");
+			local total_active_lines = 0;
+			local coverage_file = io.open("reports/coverage_"..fn:gsub("%W+", "_")..".report", "a+");
+			for line, active in pairs(lines_hit) do
+				if active ~= nil then total_active_lines = total_active_lines + 1; end
+				if coverage_file then
+					if active == false then coverage_file:write(fn, "|", line, "|", name or "", "|miss\n"); 
+					else coverage_file:write(fn, "|", line, "|", name or "", "|", tostring(success), "\n"); end
+				end
+			end
+			if coverage_file then coverage_file:close(); end
+			return covered_lines, total_active_lines, lines_hit;
+		end
+end
+
+run_all_tests()