tests/harness.lua

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;