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