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