diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/modulemanager_option_conversion.lua | 55 | ||||
-rw-r--r-- | tests/readme | 1 | ||||
-rw-r--r-- | tests/reports/empty | 1 | ||||
-rw-r--r-- | tests/run_tests.bat | 10 | ||||
-rwxr-xr-x | tests/run_tests.sh | 3 | ||||
-rw-r--r-- | tests/test.lua | 192 | ||||
-rw-r--r-- | tests/test_core_configmanager.lua | 37 | ||||
-rw-r--r-- | tests/test_core_modulemanager.lua | 48 | ||||
-rw-r--r-- | tests/test_core_s2smanager.lua | 47 | ||||
-rw-r--r-- | tests/test_core_stanza_router.lua | 232 | ||||
-rw-r--r-- | tests/test_net_http.lua | 37 | ||||
-rw-r--r-- | tests/test_sasl.lua | 38 | ||||
-rw-r--r-- | tests/test_util_jid.lua | 76 | ||||
-rw-r--r-- | tests/test_util_multitable.lua | 62 | ||||
-rw-r--r-- | tests/test_util_rfc3484.lua | 51 | ||||
-rw-r--r-- | tests/test_util_sasl_scram.lua | 23 | ||||
-rw-r--r-- | tests/test_util_stanza.lua | 27 | ||||
-rw-r--r-- | tests/util/logger.lua | 44 |
18 files changed, 963 insertions, 21 deletions
diff --git a/tests/modulemanager_option_conversion.lua b/tests/modulemanager_option_conversion.lua new file mode 100644 index 00000000..7dceeaed --- /dev/null +++ b/tests/modulemanager_option_conversion.lua @@ -0,0 +1,55 @@ +package.path = "../?.lua;"..package.path; + +local api = require "core.modulemanager".api; + +local module = setmetatable({}, {__index = api}); +local opt = nil; +function module:log() end +function module:get_option(name) + if name == "opt" then + return opt; + else + return nil; + end +end + +function test_value(value, returns) + opt = value; + assert(module:get_option_number("opt") == returns.number, "number doesn't match"); + assert(module:get_option_string("opt") == returns.string, "string doesn't match"); + assert(module:get_option_boolean("opt") == returns.boolean, "boolean doesn't match"); + + if type(returns.array) == "table" then + local target_array, returned_array = returns.array, module:get_option_array("opt"); + assert(#target_array == #returned_array, "array length doesn't match"); + for i=1,#target_array do + assert(target_array[i] == returned_array[i], "array item doesn't match"); + end + else + assert(module:get_option_array("opt") == returns.array, "array is returned (not nil)"); + end + + if type(returns.set) == "table" then + local target_items, returned_items = set.new(returns.set), module:get_option_set("opt"); + assert(target_items == returned_items, "set doesn't match"); + else + assert(module:get_option_set("opt") == returns.set, "set is returned (not nil)"); + end +end + +test_value(nil, {}); + +test_value(true, { boolean = true, string = "true", array = {true}, set = {true} }); +test_value(false, { boolean = false, string = "false", array = {false}, set = {false} }); +test_value("true", { boolean = true, string = "true", array = {"true"}, set = {"true"} }); +test_value("false", { boolean = false, string = "false", array = {"false"}, set = {"false"} }); +test_value(1, { boolean = true, string = "1", array = {1}, set = {1}, number = 1 }); +test_value(0, { boolean = false, string = "0", array = {0}, set = {0}, number = 0 }); + +test_value("hello world", { string = "hello world", array = {"hello world"}, set = {"hello world"} }); +test_value(1234, { string = "1234", number = 1234, array = {1234}, set = {1234} }); + +test_value({1, 2, 3}, { boolean = true, string = "1", number = 1, array = {1, 2, 3}, set = {1, 2, 3} }); +test_value({1, 2, 3, 3, 4}, {boolean = true, string = "1", number = 1, array = {1, 2, 3, 3, 4}, set = {1, 2, 3, 4} }); +test_value({0, 1, 2, 3}, { boolean = false, string = "0", number = 0, array = {0, 1, 2, 3}, set = {0, 1, 2, 3} }); + diff --git a/tests/readme b/tests/readme deleted file mode 100644 index d3b44845..00000000 --- a/tests/readme +++ /dev/null @@ -1 +0,0 @@ -This folder contains some test scripts. Or it will do. One day. diff --git a/tests/reports/empty b/tests/reports/empty new file mode 100644 index 00000000..0e3c9a08 --- /dev/null +++ b/tests/reports/empty @@ -0,0 +1 @@ +This file was intentionally left blank. diff --git a/tests/run_tests.bat b/tests/run_tests.bat new file mode 100644 index 00000000..648081f5 --- /dev/null +++ b/tests/run_tests.bat @@ -0,0 +1,10 @@ +@echo off
+
+set oldpath=%path%
+set path=%path%;..;..\lualibs
+
+del reports\*.report
+lua test.lua %*
+
+set path=%oldpath%
+set oldpath=
\ No newline at end of file diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 00000000..d93cd39b --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rm reports/*.report +lua test.lua $* diff --git a/tests/test.lua b/tests/test.lua index 108dd9a4..db727ce1 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,16 +1,118 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + + + +function run_all_tests() + package.loaded["net.connlisteners"] = { get = function () return {} end }; + dotest "util.jid" + dotest "util.multitable" + dotest "util.rfc3484" + dotest "net.http" + dotest "core.modulemanager" + dotest "core.stanza_router" + dotest "core.s2smanager" + dotest "core.configmanager" + dotest "util.stanza" + dotest "util.sasl.scram" + + dosingletest("test_sasl.lua", "latin1toutf8"); +end local verbosity = tonumber(arg[1]) or 2; -function assert_equal(a, b) +if os.getenv("WINDIR") then + package.path = package.path..";..\\?.lua"; + package.cpath = package.cpath..";..\\?.dll"; +else + package.path = package.path..";../?.lua"; + package.cpath = package.cpath..";../?.so"; +end + +local _realG = _G; + +require "util.import" + +local env_mt = { __index = function (t,k) return rawget(_realG, k) or print("WARNING: Attempt to access nil global '"..tostring(k).."'"); end }; +function testlib_new_env(t) + return setmetatable(t or {}, env_mt); +end + +function assert_equal(a, b, message, level) if not (a == b) then - error(getfenv(2).__unit.."assert_equal failed: "..tostring(a).." ~= "..tostring(b), 2); + error("\n assert_equal failed: "..tostring(a).." ~= "..tostring(b)..(message and ("\n Message: "..message) or ""), (level or 1) + 1); elseif verbosity >= 4 then print("assert_equal succeeded: "..tostring(a).." == "..tostring(b)); end end +function assert_table(a, message, level) + assert_equal(type(a), "table", message, (level or 1) + 1); +end +function assert_function(a, message, level) + assert_equal(type(a), "function", message, (level or 1) + 1); +end +function assert_string(a, message, level) + assert_equal(type(a), "string", message, (level or 1) + 1); +end +function assert_boolean(a, message) + assert_equal(type(a), "boolean", message); +end +function assert_is(a, message) + assert_equal(not not a, true, message); +end +function assert_is_not(a, message) + assert_equal(not not a, false, message); +end + + +function dosingletest(testname, fname) + local tests = setmetatable({}, { __index = _realG }); + tests.__unit = testname; + tests.__test = fname; + local chunk, err = loadfile(testname); + if not chunk then + print("WARNING: ", "Failed to load tests for "..testname, err); + return; + end + + setfenv(chunk, tests); + local success, err = pcall(chunk); + if not success then + print("WARNING: ", "Failed to initialise tests for "..testname, err); + return; + end + + if type(tests[fname]) ~= "function" then + error(testname.." has no test '"..fname.."'", 0); + end + + + local line_hook, line_info = new_line_coverage_monitor(testname); + debug.sethook(line_hook, "l") + local success, ret = pcall(tests[fname]); + debug.sethook(); + if not success then + print("TEST FAILED! Unit: ["..testname.."] Function: ["..fname.."]"); + print(" Location: "..ret:gsub(":%s*\n", "\n")); + line_info(fname, false, report_file); + elseif verbosity >= 2 then + print("TEST SUCCEEDED: ", testname, fname); + print(string.format("TEST COVERED %d/%d lines", line_info(fname, true, report_file))); + else + line_info(name, success, report_file); + end +end + function dotest(unitname) - local tests = setmetatable({}, { __index = _G }); + local _fakeG = setmetatable({}, {__index = _realG}); + _fakeG._G = _fakeG; + local tests = setmetatable({}, { __index = _fakeG }); tests.__unit = unitname; local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua"); if not chunk then @@ -24,41 +126,107 @@ function dotest(unitname) print("WARNING: ", "Failed to initialise tests for "..unitname, err); return; end - - local unit = setmetatable({}, { __index = setmetatable({ module = function () end }, { __index = _G }) }); - - local chunk, err = loadfile("../"..unitname:gsub("%.", "/")..".lua"); + if tests.env then setmetatable(tests.env, { __index = _realG }); end + local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _fakeG }, { __index = tests.env or _fakeG }) }); + local fn = "../"..unitname:gsub("%.", "/")..".lua"; + local chunk, err = loadfile(fn); if not chunk then print("WARNING: ", "Failed to load module: "..unitname, err); return; end - + + local oldmodule, old_M = _fakeG.module, _fakeG._M; + _fakeG.module = function () _M = _G end setfenv(chunk, unit); local success, err = pcall(chunk); + _fakeG.module, _fakeG._M = oldmodule, old_M; if not success then print("WARNING: ", "Failed to initialise module: "..unitname, err); return; 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); + if verbosity >= 4 then + print("INFO: ", "Testing "..unitname.."."..name); + end + 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: ", unitname, name, ret); + print("TEST FAILED! Unit: ["..unitname.."] Function: ["..name.."]"); + print(" Location: "..ret:gsub(":%s*\n", "\n")); + line_info(name, false, report_file); elseif verbosity >= 2 then print("TEST SUCCEEDED: ", unitname, name); + print(string.format("TEST COVERED %d/%d lines", line_info(name, true, report_file))); + else + line_info(name, success, report_file); end end end end -dotest "util.jid" +function runtest(f, msg) + if not f then print("SUBTEST NOT FOUND: "..(msg or "(no description)")); return; end + local success, ret = pcall(f); + if success and verbosity >= 2 then + print("SUBTEST PASSED: "..(msg or "(no description)")); + elseif (not success) and verbosity >= 0 then + print("SUBTEST FAILED: "..(msg or "(no description)")); + error(ret, 0); + end +end + +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() diff --git a/tests/test_core_configmanager.lua b/tests/test_core_configmanager.lua new file mode 100644 index 00000000..132dfc74 --- /dev/null +++ b/tests/test_core_configmanager.lua @@ -0,0 +1,37 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + + + +function get(get, config) + config.set("example.com", "test", "testkey", 123); + assert_equal(get("example.com", "test", "testkey"), 123, "Retrieving a set key"); + + config.set("*", "test", "testkey1", 321); + assert_equal(get("*", "test", "testkey1"), 321, "Retrieving a set global key"); + assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key of undefined host, of which only a globally set one exists"); + + config.set("example.com", "test", ""); -- Creates example.com host in config + assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key, of which only a globally set one exists"); + + assert_equal(get(), nil, "No parameters to get()"); + assert_equal(get("undefined host"), nil, "Getting for undefined host"); + assert_equal(get("undefined host", "undefined section"), nil, "Getting for undefined host & section"); + assert_equal(get("undefined host", "undefined section", "undefined key"), nil, "Getting for undefined host & section & key"); + + assert_equal(get("example.com", "undefined section", "testkey"), nil, "Defined host, undefined section"); +end + +function set(set, u) + assert_equal(set("*"), false, "Set with no section/key"); + assert_equal(set("*", "set_test"), false, "Set with no key"); + + assert_equal(set("*", "set_test", "testkey"), true, "Setting a nil global value"); + assert_equal(set("*", "set_test", "testkey", 123), true, "Setting a global value"); +end + diff --git a/tests/test_core_modulemanager.lua b/tests/test_core_modulemanager.lua new file mode 100644 index 00000000..9498875a --- /dev/null +++ b/tests/test_core_modulemanager.lua @@ -0,0 +1,48 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local config = require "core.configmanager"; +local helpers = require "util.helpers"; +local set = require "util.set"; + +function load_modules_for_host(load_modules_for_host, mm) + local test_num = 0; + local function test_load(global_modules_enabled, global_modules_disabled, host_modules_enabled, host_modules_disabled, expected_modules) + test_num = test_num + 1; + -- Prepare + hosts = { ["example.com"] = {} }; + config.set("*", "core", "modules_enabled", global_modules_enabled); + config.set("*", "core", "modules_disabled", global_modules_disabled); + config.set("example.com", "core", "modules_enabled", host_modules_enabled); + config.set("example.com", "core", "modules_disabled", host_modules_disabled); + + expected_modules = set.new(expected_modules); + expected_modules:add_list(helpers.get_upvalue(load_modules_for_host, "autoload_modules")); + + local loaded_modules = set.new(); + function mm.load(host, module) + assert_equal(host, "example.com", test_num..": Host isn't example.com but "..tostring(host)); + assert_equal(expected_modules:contains(module), true, test_num..": Loading unexpected module '"..tostring(module).."'"); + loaded_modules:add(module); + end + load_modules_for_host("example.com"); + assert_equal((expected_modules - loaded_modules):empty(), true, test_num..": Not all modules loaded: "..tostring(expected_modules - loaded_modules)); + end + + test_load({ "one", "two", "three" }, nil, nil, nil, { "one", "two", "three" }); + test_load({ "one", "two", "three" }, {}, nil, nil, { "one", "two", "three" }); + test_load({ "one", "two", "three" }, { "two" }, nil, nil, { "one", "three" }); + test_load({ "one", "two", "three" }, { "three" }, nil, nil, { "one", "two" }); + test_load({ "one", "two", "three" }, nil, nil, { "three" }, { "one", "two" }); + test_load({ "one", "two", "three" }, nil, { "three" }, { "three" }, { "one", "two", "three" }); + + test_load({ "one", "two" }, nil, { "three" }, nil, { "one", "two", "three" }); + test_load({ "one", "two", "three" }, nil, { "three" }, nil, { "one", "two", "three" }); + test_load({ "one", "two", "three" }, { "three" }, { "three" }, nil, { "one", "two", "three" }); + test_load({ "one", "two" }, { "three" }, { "three" }, nil, { "one", "two", "three" }); +end diff --git a/tests/test_core_s2smanager.lua b/tests/test_core_s2smanager.lua new file mode 100644 index 00000000..b49c7da6 --- /dev/null +++ b/tests/test_core_s2smanager.lua @@ -0,0 +1,47 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + + +function compare_srv_priorities(csp) + local r1 = { priority = 10, weight = 0 } + local r2 = { priority = 100, weight = 0 } + local r3 = { priority = 1000, weight = 2 } + local r4 = { priority = 1000, weight = 2 } + local r5 = { priority = 1000, weight = 5 } + + assert_equal(csp(r1, r1), false); + assert_equal(csp(r1, r2), true); + assert_equal(csp(r1, r3), true); + assert_equal(csp(r1, r4), true); + assert_equal(csp(r1, r5), true); + + assert_equal(csp(r2, r1), false); + assert_equal(csp(r2, r2), false); + assert_equal(csp(r2, r3), true); + assert_equal(csp(r2, r4), true); + assert_equal(csp(r2, r5), true); + + assert_equal(csp(r3, r1), false); + assert_equal(csp(r3, r2), false); + assert_equal(csp(r3, r3), false); + assert_equal(csp(r3, r4), false); + assert_equal(csp(r3, r5), false); + + assert_equal(csp(r4, r1), false); + assert_equal(csp(r4, r2), false); + assert_equal(csp(r4, r3), false); + assert_equal(csp(r4, r4), false); + assert_equal(csp(r4, r5), false); + + assert_equal(csp(r5, r1), false); + assert_equal(csp(r5, r2), false); + assert_equal(csp(r5, r3), true); + assert_equal(csp(r5, r4), true); + assert_equal(csp(r5, r5), false); + +end diff --git a/tests/test_core_stanza_router.lua b/tests/test_core_stanza_router.lua new file mode 100644 index 00000000..0a93694f --- /dev/null +++ b/tests/test_core_stanza_router.lua @@ -0,0 +1,232 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +_G.prosody = { full_sessions = {}; bare_sessions = {}; hosts = {}; }; + +function core_process_stanza(core_process_stanza, u) + local stanza = require "util.stanza"; + local s2sout_session = { to_host = "remotehost", from_host = "localhost", type = "s2sout" } + local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } } + local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session } } + local local_user_session = { username = "user", host = "localhost", resource = "resource", full_jid = "user@localhost/resource", type = "c2s" } + + _G.prosody.hosts["localhost"] = local_host_session; + _G.prosody.full_sessions["user@localhost/resource"] = local_user_session; + _G.prosody.bare_sessions["user@localhost"] = { sessions = { resource = local_user_session } }; + + -- Test message routing + local function test_message_full_jid() + local env = testlib_new_env(); + local msg = stanza.stanza("message", { to = "user@localhost/resource", type = "chat" }):tag("body"):text("Hello world"); + + local target_routed; + + function env.core_post_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct"); + assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print()); + target_routed = true; + end + + env.hosts = hosts; + env.prosody = { hosts = hosts }; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_routed, true, "stanza was not routed successfully"); + end + + local function test_message_bare_jid() + local env = testlib_new_env(); + local msg = stanza.stanza("message", { to = "user@localhost", type = "chat" }):tag("body"):text("Hello world"); + + local target_routed; + + function env.core_post_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct"); + assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print()); + target_routed = true; + end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_routed, true, "stanza was not routed successfully"); + end + + local function test_message_no_to() + local env = testlib_new_env(); + local msg = stanza.stanza("message", { type = "chat" }):tag("body"):text("Hello world"); + + local target_handled; + + function env.core_post_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); + assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); + target_handled = true; + end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_handled, true, "stanza was not handled successfully"); + end + + local function test_message_to_remote_bare() + local env = testlib_new_env(); + local msg = stanza.stanza("message", { to = "user@remotehost", type = "chat" }):tag("body"):text("Hello world"); + + local target_routed; + + function env.core_route_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); + assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); + target_routed = true; + end + + function env.core_post_stanza(...) env.core_route_stanza(...); end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_routed, true, "stanza was not routed successfully"); + end + + local function test_message_to_remote_server() + local env = testlib_new_env(); + local msg = stanza.stanza("message", { to = "remotehost", type = "chat" }):tag("body"):text("Hello world"); + + local target_routed; + + function env.core_route_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); + assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); + target_routed = true; + end + + function env.core_post_stanza(...) + env.core_route_stanza(...); + end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_routed, true, "stanza was not routed successfully"); + end + + --IQ tests + + + local function test_iq_to_remote_server() + local env = testlib_new_env(); + local msg = stanza.stanza("iq", { to = "remotehost", type = "get", id = "id" }):tag("body"):text("Hello world"); + + local target_routed; + + function env.core_route_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); + assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); + target_routed = true; + end + + function env.core_post_stanza(...) + env.core_route_stanza(...); + end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_routed, true, "stanza was not routed successfully"); + end + + local function test_iq_error_to_local_user() + local env = testlib_new_env(); + local msg = stanza.stanza("iq", { to = "user@localhost/resource", from = "user@remotehost", type = "error", id = "id" }):tag("error", { type = 'cancel' }):tag("item-not-found", { xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' }); + + local target_routed; + + function env.core_route_stanza(p_origin, p_stanza) + assert_equal(p_origin, s2sin_session, "origin of handled stanza is not correct"); + assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); + target_routed = true; + end + + function env.core_post_stanza(...) + env.core_route_stanza(...); + end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(s2sin_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_routed, true, "stanza was not routed successfully"); + end + + local function test_iq_to_local_bare() + local env = testlib_new_env(); + local msg = stanza.stanza("iq", { to = "user@localhost", from = "user@localhost", type = "get", id = "id" }):tag("ping", { xmlns = "urn:xmpp:ping:0" }); + + local target_handled; + + function env.core_post_stanza(p_origin, p_stanza) + assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); + assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); + target_handled = true; + end + + env.hosts = hosts; + setfenv(core_process_stanza, env); + assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); + assert_equal(target_handled, true, "stanza was not handled successfully"); + end + + runtest(test_message_full_jid, "Messages with full JID destinations get routed"); + runtest(test_message_bare_jid, "Messages with bare JID destinations get routed"); + runtest(test_message_no_to, "Messages with no destination are handled by the server"); + runtest(test_message_to_remote_bare, "Messages to a remote user are routed by the server"); + runtest(test_message_to_remote_server, "Messages to a remote server's JID are routed"); + + runtest(test_iq_to_remote_server, "iq to a remote server's JID are routed"); + runtest(test_iq_to_local_bare, "iq from a local user to a local user's bare JID are handled"); + runtest(test_iq_error_to_local_user, "iq type=error to a local user's JID are routed"); +end + +function core_route_stanza(core_route_stanza) + local stanza = require "util.stanza"; + local s2sout_session = { to_host = "remotehost", from_host = "localhost", type = "s2sout" } + local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } } + local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session }, sessions = {} } + local local_user_session = { username = "user", host = "localhost", resource = "resource", full_jid = "user@localhost/resource", type = "c2s" } + local hosts = { + ["localhost"] = local_host_session; + } + + local function test_iq_result_to_offline_user() + local env = testlib_new_env(); + local msg = stanza.stanza("iq", { to = "user@localhost/foo", from = "user@localhost", type = "result" }):tag("ping", { xmlns = "urn:xmpp:ping:0" }); + local msg2 = stanza.stanza("iq", { to = "user@localhost/foo", from = "user@localhost", type = "error" }):tag("ping", { xmlns = "urn:xmpp:ping:0" }); + --package.loaded["core.usermanager"] = { user_exists = function (user, host) print("RAR!") return true or user == "user" and host == "localhost" and true; end }; + local target_handled, target_replied; + + function env.core_post_stanza(p_origin, p_stanza) + target_handled = true; + end + + function local_user_session.send(data) + --print("Replying with: ", tostring(data)); + --print(debug.traceback()) + target_replied = true; + end + + env.hosts = hosts; + setfenv(core_route_stanza, env); + assert_equal(core_route_stanza(local_user_session, msg), nil, "core_route_stanza returned incorrect value"); + assert_equal(target_handled, nil, "stanza was handled and not dropped"); + assert_equal(target_replied, nil, "stanza was replied to and not dropped"); + package.loaded["core.usermanager"] = nil; + end + + --runtest(test_iq_result_to_offline_user, "iq type=result|error to an offline user are not replied to"); +end diff --git a/tests/test_net_http.lua b/tests/test_net_http.lua new file mode 100644 index 00000000..e68f96e9 --- /dev/null +++ b/tests/test_net_http.lua @@ -0,0 +1,37 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +function urlencode(urlencode) + assert_equal(urlencode("helloworld123"), "helloworld123", "Normal characters not escaped"); + assert_equal(urlencode("hello world"), "hello%20world", "Spaces escaped"); + assert_equal(urlencode("This & that = something"), "This%20%26%20that%20%3d%20something", "Important URL chars escaped"); +end + +function urldecode(urldecode) + assert_equal("helloworld123", urldecode("helloworld123"), "Normal characters not escaped"); + assert_equal("hello world", urldecode("hello%20world"), "Spaces escaped"); + assert_equal("This & that = something", urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped"); + assert_equal("This & that = something", urldecode("This%20%26%20that%20%3D%20something"), "Important URL chars escaped"); +end + +function formencode(formencode) + assert_equal(formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded"); + assert_equal(formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded"); +end + +function formdecode(formdecode) + local t = formdecode("one=1&two=2"); + assert_table(t[1]); + assert_equal(t[1].name, "one"); assert_equal(t[1].value, "1"); + assert_table(t[2]); + assert_equal(t[2].name, "two"); assert_equal(t[2].value, "2"); + + local t = formdecode("one+two=1&two+one%26=2"); + assert_equal(t[1].name, "one two"); assert_equal(t[1].value, "1"); + assert_equal(t[2].name, "two one&"); assert_equal(t[2].value, "2"); +end diff --git a/tests/test_sasl.lua b/tests/test_sasl.lua new file mode 100644 index 00000000..271fa69a --- /dev/null +++ b/tests/test_sasl.lua @@ -0,0 +1,38 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local gmatch = string.gmatch; +local t_concat, t_insert = table.concat, table.insert; +local to_byte, to_char = string.byte, string.char; + +local function _latin1toutf8(str) + if not str then return str; end + local p = {}; + for ch in gmatch(str, ".") do + ch = to_byte(ch); + if (ch < 0x80) then + t_insert(p, to_char(ch)); + elseif (ch < 0xC0) then + t_insert(p, to_char(0xC2, ch)); + else + t_insert(p, to_char(0xC3, ch - 64)); + end + end + return t_concat(p); +end + +function latin1toutf8() + local function assert_utf8(latin, utf8) + assert_equal(_latin1toutf8(latin), utf8, "Incorrect UTF8 from Latin1: "..tostring(latin)); + end + + assert_utf8("", "") + assert_utf8("test", "test") + assert_utf8(nil, nil) + assert_utf8("foobar.r\229kat.se", "foobar.r\195\165kat.se") +end diff --git a/tests/test_util_jid.lua b/tests/test_util_jid.lua index 3be0bfa1..a817e644 100644 --- a/tests/test_util_jid.lua +++ b/tests/test_util_jid.lua @@ -1,13 +1,73 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +function join(join) + assert_equal(join("a", "b", "c"), "a@b/c", "builds full JID"); + assert_equal(join("a", "b", nil), "a@b", "builds bare JID"); + assert_equal(join(nil, "b", "c"), "b/c", "builds full host JID"); + assert_equal(join(nil, "b", nil), "b", "builds bare host JID"); + assert_equal(join(nil, nil, nil), nil, "invalid JID is nil"); + assert_equal(join("a", nil, nil), nil, "invalid JID is nil"); + assert_equal(join(nil, nil, "c"), nil, "invalid JID is nil"); + assert_equal(join("a", nil, "c"), nil, "invalid JID is nil"); +end + function split(split) - function test(jid, node, server, resource) - local rnode, rserver, rresource = split(jid); - assert_equal(node, rnode, "split("..jid..") failed"); - assert_equal(server, rserver, "split("..jid..") failed"); - assert_equal(resource, rresource, "split("..jid..") failed"); + function test(input_jid, expected_node, expected_server, expected_resource) + local rnode, rserver, rresource = split(input_jid); + assert_equal(expected_node, rnode, "split("..tostring(input_jid)..") failed"); + assert_equal(expected_server, rserver, "split("..tostring(input_jid)..") failed"); + assert_equal(expected_resource, rresource, "split("..tostring(input_jid)..") failed"); end + + -- Valid JIDs test("node@server", "node", "server", nil ); - test("node@server/resource", "node", "server", "resource" ); - test("server", nil, "server", nil ); - test("server/resource", nil, "server", "resource" ); + test("node@server/resource", "node", "server", "resource" ); + test("server", nil, "server", nil ); + test("server/resource", nil, "server", "resource" ); + test("server/resource@foo", nil, "server", "resource@foo" ); + test("server/resource@foo/bar", nil, "server", "resource@foo/bar"); + + -- Always invalid JIDs + test(nil, nil, nil, nil); + test("node@/server", nil, nil, nil); + test("@server", nil, nil, nil); + test("@server/resource", nil, nil, nil); + test("@/resource", nil, nil, nil); +end + +function bare(bare) + assert_equal(bare("user@host"), "user@host", "bare JID remains bare"); + assert_equal(bare("host"), "host", "Host JID remains host"); + assert_equal(bare("host/resource"), "host", "Host JID with resource becomes host"); + assert_equal(bare("user@host/resource"), "user@host", "user@host JID with resource becomes user@host"); + assert_equal(bare("user@/resource"), nil, "invalid JID is nil"); + assert_equal(bare("@/resource"), nil, "invalid JID is nil"); + assert_equal(bare("@/"), nil, "invalid JID is nil"); + assert_equal(bare("/"), nil, "invalid JID is nil"); + assert_equal(bare(""), nil, "invalid JID is nil"); + assert_equal(bare("@"), nil, "invalid JID is nil"); + assert_equal(bare("user@"), nil, "invalid JID is nil"); + assert_equal(bare("user@@"), nil, "invalid JID is nil"); + assert_equal(bare("user@@host"), nil, "invalid JID is nil"); + assert_equal(bare("user@@host/resource"), nil, "invalid JID is nil"); + assert_equal(bare("user@host/"), nil, "invalid JID is nil"); +end + +function compare(compare) + assert_equal(compare("host", "host"), true, "host should match"); + assert_equal(compare("host", "other-host"), false, "host should not match"); + assert_equal(compare("other-user@host/resource", "host"), true, "host should match"); + assert_equal(compare("other-user@host", "user@host"), false, "user should not match"); + assert_equal(compare("user@host", "host"), true, "host should match"); + assert_equal(compare("user@host/resource", "host"), true, "host should match"); + assert_equal(compare("user@host/resource", "user@host"), true, "user and host should match"); + assert_equal(compare("user@other-host", "host"), false, "host should not match"); + assert_equal(compare("user@other-host", "user@host"), false, "host should not match"); end diff --git a/tests/test_util_multitable.lua b/tests/test_util_multitable.lua new file mode 100644 index 00000000..ed10b128 --- /dev/null +++ b/tests/test_util_multitable.lua @@ -0,0 +1,62 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + + +function new(new, multitable) + mt = new(); + assert_table(mt, "Multitable is a table"); + assert_function(mt.add, "Multitable has method add"); + assert_function(mt.get, "Multitable has method get"); + assert_function(mt.remove, "Multitable has method remove"); + + get(mt.get, multitable); +end + +function get(get, multitable) + local function has_items(list, ...) + local should_have = {}; + if select('#', ...) > 0 then + assert_table(list, "has_items: list is table", 3); + else + assert_is_not(list and #list > 0, "No items, and no list"); + return true, "has-all"; + end + for n=1,select('#', ...) do should_have[select(n, ...)] = true; end + for n, item in ipairs(list) do + if not should_have[item] then return false, "too-many"; end + should_have[item] = nil; + end + if next(should_have) then + return false, "not-enough"; + end + return true, "has-all"; + end + local function assert_has_all(message, list, ...) + return assert_equal(select(2, has_items(list, ...)), "has-all", message or "List has all expected items, and no more", 2); + end + + mt = multitable.new(); + + local trigger1, trigger2, trigger3 = {}, {}, {}; + local item1, item2, item3 = {}, {}, {}; + + assert_has_all("Has no items with trigger1", mt:get(trigger1)); + + + mt:add(1, 2, 3, item1); + + assert_has_all("Has item1 for 1, 2, 3", mt:get(1, 2, 3), item1); + +-- Doesn't support nil +--[[ mt:add(nil, item1); + mt:add(nil, item2); + mt:add(nil, item3); + + assert_has_all("Has all items with (nil)", mt:get(nil), item1, item2, item3); +]] +end diff --git a/tests/test_util_rfc3484.lua b/tests/test_util_rfc3484.lua new file mode 100644 index 00000000..18ae310e --- /dev/null +++ b/tests/test_util_rfc3484.lua @@ -0,0 +1,51 @@ +-- Prosody IM +-- Copyright (C) 2011 Florian Zeitz +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +function source(source) + local new_ip = require"util.ip".new_ip; + assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope"); + assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope"); + assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope"); + assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope"); + assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address"); + assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope"); + assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix"); + assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label"); +end + +function destination(dest) + local order; + local new_ip = require"util.ip".new_ip; + order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")}) + assert_equal(order[1].addr, "2001::1", "prefer matching scope"); + assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope") + + order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")}) + assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope") + assert_equal(order[2].addr, "2001::1", "prefer matching scope") + + order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")}) + assert_equal(order[1].addr, "2001::1", "prefer higher precedence"); + assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence"); + + order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")}) + assert_equal(order[1].addr, "fe80::1", "prefer smaller scope"); + assert_equal(order[2].addr, "fec0::1", "prefer smaller scope"); + assert_equal(order[3].addr, "2001::1", "prefer smaller scope"); + + order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")}) + assert_equal(order[1].addr, "2001::1", "longest matching prefix"); + assert_equal(order[2].addr, "3ffe::1", "longest matching prefix"); + + order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")}) + assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label"); + assert_equal(order[2].addr, "2001::1", "prefer matching label"); + + order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")}) + assert_equal(order[1].addr, "2001::1", "prefer higher precedence"); + assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence"); +end diff --git a/tests/test_util_sasl_scram.lua b/tests/test_util_sasl_scram.lua new file mode 100644 index 00000000..bc89829f --- /dev/null +++ b/tests/test_util_sasl_scram.lua @@ -0,0 +1,23 @@ + + +local hmac_sha1 = require "util.hashes".hmac_sha1; +local function toHex(s) + return s and (s:gsub(".", function (c) return ("%02x"):format(c:byte()); end)); +end + +function Hi(Hi) + assert( toHex(Hi(hmac_sha1, "password", "salt", 1)) == "0c60c80f961f0e71f3a9b524af6012062fe037a6", + [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 1)) == "0c60c80f961f0e71f3a9b524af6012062fe037a6"]]) + assert( toHex(Hi(hmac_sha1, "password", "salt", 2)) == "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", + [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 2)) == "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"]]) + assert( toHex(Hi(hmac_sha1, "password", "salt", 64)) == "a7bc9b6efea2cbd717da72d83bfcc4e17d0b6280", + [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 64)) == "a7bc9b6efea2cbd717da72d83bfcc4e17d0b6280"]]) + assert( toHex(Hi(hmac_sha1, "password", "salt", 4096)) == "4b007901b765489abead49d926f721d065a429c1", + [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 4096)) == "4b007901b765489abead49d926f721d065a429c1"]]) + -- assert( toHex(Hi(hmac_sha1, "password", "salt", 16777216)) == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984", + -- [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 16777216)) == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"]]) +end + +function init(init) + -- no tests +end diff --git a/tests/test_util_stanza.lua b/tests/test_util_stanza.lua new file mode 100644 index 00000000..fce26f3a --- /dev/null +++ b/tests/test_util_stanza.lua @@ -0,0 +1,27 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + + +function preserialize(preserialize, st) + local stanza = st.stanza("message", { a = "a" }); + local stanza2 = preserialize(stanza); + assert_is(stanza2 and stanza.name, "preserialize returns a stanza"); + assert_is_not(stanza2.tags, "Preserialized stanza has no tag list"); + assert_is_not(stanza2.last_add, "Preserialized stanza has no last_add marker"); + assert_is_not(getmetatable(stanza2), "Preserialized stanza has no metatable"); +end + +function deserialize(deserialize, st) + local stanza = st.stanza("message", { a = "a" }); + + local stanza2 = deserialize(st.preserialize(stanza)); + assert_is(stanza2 and stanza.name, "deserialize returns a stanza"); + assert_table(stanza2.attr, "Deserialized stanza has attributes"); + assert_equal(stanza2.attr.a, "a", "Deserialized stanza retains attributes"); + assert_table(getmetatable(stanza2), "Deserialized stanza has metatable"); +end diff --git a/tests/util/logger.lua b/tests/util/logger.lua new file mode 100644 index 00000000..35facd4e --- /dev/null +++ b/tests/util/logger.lua @@ -0,0 +1,44 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local format = string.format; +local print = print; +local debug = debug; +local tostring = tostring; + +local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring; +local do_pretty_printing = not os.getenv("WINDIR"); + +module "logger" + +local logstyles = {}; + +--TODO: This should be done in config, but we don't have proper config yet +if do_pretty_printing then + logstyles["info"] = getstyle("bold"); + logstyles["warn"] = getstyle("bold", "yellow"); + logstyles["error"] = getstyle("bold", "red"); +end + +function init(name) + --name = nil; -- While this line is not commented, will automatically fill in file/line number info + return function (level, message, ...) + if level == "debug" or level == "info" then return; end + if not name then + local inf = debug.getinfo(3, 'Snl'); + level = level .. ","..tostring(inf.short_src):match("[^/]*$")..":"..inf.currentline; + end + if ... then + print(name, getstring(logstyles[level], level), format(message, ...)); + else + print(name, getstring(logstyles[level], level), message); + end + end +end + +return _M; |