| 1 | -- Minimal harness for running verse against a live XMPP server. |
| 2 | -- Each test is a function(t) that calls t.done() on success; errors and |
| 3 | -- timeouts fail the test. Clients created through t.client() are closed |
| 4 | -- after the test finishes. |
| 5 | |
| 6 | local verse = require "verse".init("client"); |
| 7 | local timer = require "prosody.util.timer"; |
| 8 | |
| 9 | local harness = {}; |
| 10 | |
| 11 | harness.host = os.getenv("VERSE_TEST_HOST") or "localhost"; |
| 12 | harness.port = tonumber(os.getenv("VERSE_TEST_PORT")) or 5222; |
| 13 | harness.domain = os.getenv("VERSE_TEST_DOMAIN") or "localhost"; |
| 14 | harness.password = "secret"; |
| 15 | harness.timeout = tonumber(os.getenv("VERSE_TEST_TIMEOUT")) or 10; |
| 16 | harness.verse = verse; |
| 17 | |
| 18 | if (os.getenv("VERSE_TEST_DEBUG") or "") ~= "" then |
| 19 | verse.set_log_handler(verse._default_log_handler); |
| 20 | end |
| 21 | |
| 22 | local function run_loop_for(seconds) |
| 23 | timer.add_task(seconds, function () verse.quit(); end); |
| 24 | verse.loop(); |
| 25 | end |
| 26 | |
| 27 | -- Runs one test, returning true on success or false plus an error message. |
| 28 | function harness.run(test) |
| 29 | local clients = {}; |
| 30 | local finished, failure; |
| 31 | local t = {}; |
| 32 | |
| 33 | function t.done() |
| 34 | if finished then return; end |
| 35 | finished = true; |
| 36 | verse.quit(); |
| 37 | end |
| 38 | |
| 39 | function t.fail(err) |
| 40 | if finished then return; end |
| 41 | finished, failure = true, tostring(err); |
| 42 | verse.quit(); |
| 43 | end |
| 44 | |
| 45 | -- Wrap a callback so an assertion failure inside it fails the test |
| 46 | -- instead of being swallowed by verse's error handler. |
| 47 | function t.cb(f) |
| 48 | return function (...) |
| 49 | local ok, err = pcall(f, ...); |
| 50 | if not ok then t.fail(err); end |
| 51 | end |
| 52 | end |
| 53 | |
| 54 | -- Returns a new stream that connects and authenticates as `user`; |
| 55 | -- `ready` is called once the resource is bound. |
| 56 | function t.client(user, ready, opts) |
| 57 | opts = opts or {}; |
| 58 | local c = verse.new(); |
| 59 | clients[#clients + 1] = c; |
| 60 | c.connect_host, c.connect_port = harness.host, harness.port; |
| 61 | for _, plugin in ipairs(opts.plugins or {}) do |
| 62 | c:add_plugin(plugin); |
| 63 | end |
| 64 | c:hook("authentication-failure", function (err) |
| 65 | if not opts.expect_failure then |
| 66 | t.fail("authentication failed for "..user..": "..tostring(err.condition)); |
| 67 | end |
| 68 | end); |
| 69 | c:hook("disconnected", function (err) |
| 70 | if not finished and not opts.expect_disconnect then |
| 71 | t.fail("client "..user.." disconnected: "..tostring(err and err.reason)); |
| 72 | end |
| 73 | end); |
| 74 | if ready then |
| 75 | c:hook("ready", t.cb(function () ready(c); end)); |
| 76 | end |
| 77 | c:connect_client(user.."@"..harness.domain.."/"..(opts.resource or "verse-test"), opts.password or harness.password); |
| 78 | return c; |
| 79 | end |
| 80 | |
| 81 | local deadline = timer.add_task(harness.timeout, function () |
| 82 | t.fail("timed out after "..harness.timeout.."s"); |
| 83 | end); |
| 84 | |
| 85 | local ok, err = pcall(test, t); |
| 86 | if not ok then |
| 87 | t.fail(err); |
| 88 | end |
| 89 | if not finished then |
| 90 | verse.loop(); |
| 91 | end |
| 92 | timer.stop(deadline); |
| 93 | |
| 94 | -- Tear down: close any open streams and let the loop flush them |
| 95 | finished = true; |
| 96 | for _, c in ipairs(clients) do |
| 97 | if c.conn then c:close(); end |
| 98 | end |
| 99 | run_loop_for(0.2); |
| 100 | |
| 101 | return failure == nil, failure; |
| 102 | end |
| 103 | |
| 104 | return harness; |