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