Merge 0.7->0.8
[prosody.git] / tests / test.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9
10
11 function run_all_tests()
12         dotest "util.jid"
13         dotest "util.multitable"
14         dotest "core.modulemanager"
15         dotest "core.stanza_router"
16         dotest "core.s2smanager"
17         dotest "core.configmanager"
18         dotest "util.stanza"
19         dotest "util.sasl.scram"
20         
21         dosingletest("test_sasl.lua", "latin1toutf8");
22 end
23
24 local verbosity = tonumber(arg[1]) or 2;
25
26 if os.getenv("WINDIR") then
27         package.path = package.path..";..\\?.lua";
28         package.cpath = package.cpath..";..\\?.dll";
29 else
30         package.path = package.path..";../?.lua";
31         package.cpath = package.cpath..";../?.so";
32 end
33
34 local _realG = _G;
35
36 require "util.import"
37
38 local env_mt = { __index = function (t,k) return rawget(_realG, k) or print("WARNING: Attempt to access nil global '"..tostring(k).."'"); end };
39 function testlib_new_env(t)
40         return setmetatable(t or {}, env_mt);
41 end
42
43 function assert_equal(a, b, message, level)
44         if not (a == b) then
45                 error("\n   assert_equal failed: "..tostring(a).." ~= "..tostring(b)..(message and ("\n   Message: "..message) or ""), (level or 1) + 1);
46         elseif verbosity >= 4 then
47                 print("assert_equal succeeded: "..tostring(a).." == "..tostring(b));
48         end
49 end
50
51 function assert_table(a, message, level)
52         assert_equal(type(a), "table", message, (level or 1) + 1);
53 end
54 function assert_function(a, message, level)
55         assert_equal(type(a), "function", message, (level or 1) + 1);
56 end
57 function assert_string(a, message, level)
58         assert_equal(type(a), "string", message, (level or 1) + 1);
59 end
60 function assert_boolean(a, message)
61         assert_equal(type(a), "boolean", message);
62 end
63 function assert_is(a, message)
64         assert_equal(not not a, true, message);
65 end
66 function assert_is_not(a, message)
67         assert_equal(not not a, false, message);
68 end
69
70
71 function dosingletest(testname, fname)
72         local tests = setmetatable({}, { __index = _realG });
73         tests.__unit = testname;
74         tests.__test = fname;
75         local chunk, err = loadfile(testname);
76         if not chunk then
77                 print("WARNING: ", "Failed to load tests for "..testname, err);
78                 return;
79         end
80
81         setfenv(chunk, tests);
82         local success, err = pcall(chunk);
83         if not success then
84                 print("WARNING: ", "Failed to initialise tests for "..testname, err);
85                 return;
86         end
87         
88         if type(tests[fname]) ~= "function" then
89                 error(testname.." has no test '"..fname.."'", 0);
90         end
91         
92         
93         local line_hook, line_info = new_line_coverage_monitor(testname);
94         debug.sethook(line_hook, "l")
95         local success, ret = pcall(tests[fname]);
96         debug.sethook();
97         if not success then
98                 print("TEST FAILED! Unit: ["..testname.."] Function: ["..fname.."]");
99                 print("   Location: "..ret:gsub(":%s*\n", "\n"));
100                 line_info(fname, false, report_file);
101         elseif verbosity >= 2 then
102                 print("TEST SUCCEEDED: ", testname, fname);
103                 print(string.format("TEST COVERED %d/%d lines", line_info(fname, true, report_file)));
104         else
105                 line_info(name, success, report_file);
106         end
107 end
108
109 function dotest(unitname)
110         local _fakeG = setmetatable({}, {__index = _realG});
111         _fakeG._G = _fakeG;
112         local tests = setmetatable({}, { __index = _fakeG });
113         tests.__unit = unitname;
114         local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua");
115         if not chunk then
116                 print("WARNING: ", "Failed to load tests for "..unitname, err);
117                 return;
118         end
119
120         setfenv(chunk, tests);
121         local success, err = pcall(chunk);
122         if not success then
123                 print("WARNING: ", "Failed to initialise tests for "..unitname, err);
124                 return;
125         end
126         if tests.env then setmetatable(tests.env, { __index = _realG }); end
127         local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _fakeG }, { __index = tests.env or _fakeG }) });
128         local fn = "../"..unitname:gsub("%.", "/")..".lua";
129         local chunk, err = loadfile(fn);
130         if not chunk then
131                 print("WARNING: ", "Failed to load module: "..unitname, err);
132                 return;
133         end
134         
135         local oldmodule, old_M = _fakeG.module, _fakeG._M;
136         _fakeG.module = function () _M = _G end
137         setfenv(chunk, unit);
138         local success, err = pcall(chunk);
139         _fakeG.module, _fakeG._M = oldmodule, old_M;
140         if not success then
141                 print("WARNING: ", "Failed to initialise module: "..unitname, err);
142                 return;
143         end
144         
145         for name, f in pairs(unit) do
146                 local test = rawget(tests, name);
147                 if type(f) ~= "function" then
148                         if verbosity >= 3 then
149                                 print("INFO: ", "Skipping "..unitname.."."..name.." because it is not a function");
150                         end
151                 elseif type(test) ~= "function" then
152                         if verbosity >= 1 then
153                                 print("WARNING: ", unitname.."."..name.." has no test!");
154                         end
155                 else
156                         if verbosity >= 4 then
157                                 print("INFO: ", "Testing "..unitname.."."..name);
158                         end
159                         local line_hook, line_info = new_line_coverage_monitor(fn);
160                         debug.sethook(line_hook, "l")
161                         local success, ret = pcall(test, f, unit);
162                         debug.sethook();
163                         if not success then
164                                 print("TEST FAILED! Unit: ["..unitname.."] Function: ["..name.."]");
165                                 print("   Location: "..ret:gsub(":%s*\n", "\n"));
166                                 line_info(name, false, report_file);
167                         elseif verbosity >= 2 then
168                                 print("TEST SUCCEEDED: ", unitname, name);
169                                 print(string.format("TEST COVERED %d/%d lines", line_info(name, true, report_file)));
170                         else
171                                 line_info(name, success, report_file);
172                         end
173                 end
174         end
175 end
176
177 function runtest(f, msg)
178         if not f then print("SUBTEST NOT FOUND: "..(msg or "(no description)")); return; end
179         local success, ret = pcall(f);
180         if success and verbosity >= 2 then
181                 print("SUBTEST PASSED: "..(msg or "(no description)"));
182         elseif (not success) and verbosity >= 0 then
183                 print("SUBTEST FAILED: "..(msg or "(no description)"));
184                 error(ret, 0);
185         end
186 end
187
188 function new_line_coverage_monitor(file)
189         local lines_hit, funcs_hit = {}, {};
190         local total_lines, covered_lines = 0, 0;
191         
192         for line in io.lines(file) do
193                 total_lines = total_lines + 1;
194         end
195         
196         return function (event, line) -- Line hook
197                         if not lines_hit[line] then
198                                 local info = debug.getinfo(2, "fSL")
199                                 if not info.source:find(file) then return; end
200                                 if not funcs_hit[info.func] and info.activelines then
201                                         funcs_hit[info.func] = true;
202                                         for line in pairs(info.activelines) do
203                                                 lines_hit[line] = false; -- Marks it as hittable, but not hit yet
204                                         end
205                                 end
206                                 if lines_hit[line] == false then
207                                         --print("New line hit: "..line.." in "..debug.getinfo(2, "S").source);
208                                         lines_hit[line] = true;
209                                         covered_lines = covered_lines + 1;
210                                 end
211                         end
212                 end,
213                 function (test_name, success) -- Get info
214                         local fn = file:gsub("^%W*", "");
215                         local total_active_lines = 0;
216                         local coverage_file = io.open("reports/coverage_"..fn:gsub("%W+", "_")..".report", "a+");
217                         for line, active in pairs(lines_hit) do
218                                 if active ~= nil then total_active_lines = total_active_lines + 1; end
219                                 if coverage_file then
220                                         if active == false then coverage_file:write(fn, "|", line, "|", name or "", "|miss\n");
221                                         else coverage_file:write(fn, "|", line, "|", name or "", "|", tostring(success), "\n"); end
222                                 end
223                         end
224                         if coverage_file then coverage_file:close(); end
225                         return covered_lines, total_active_lines, lines_hit;
226                 end
227 end
228
229 run_all_tests()