diff --git a/luatest/output/junit.lua b/luatest/output/junit.lua
index a643b7ce..90e26ba5 100644
--- a/luatest/output/junit.lua
+++ b/luatest/output/junit.lua
@@ -1,3 +1,4 @@
+local utils = require('luatest.utils')
local ROCK_VERSION = require('luatest.VERSION')
-- See directory junitxml for more information about the junit format
@@ -20,17 +21,23 @@ function Output.xml_c_data_escape(str)
end
function Output.node_status_xml(node)
+ local artifacts = ''
+ if utils.table_len(node.servers) > 0 then
+ for _, server in pairs(node.servers) do
+ artifacts = ('%s%s -> %s'):format(artifacts, server.alias, server.artifacts)
+ end
+ end
if node:is('error') then
return table.concat(
{' \n',
' \n',
- ' ', Output.xml_escape(node.artifacts or ''), '\n',
+ ' ', Output.xml_escape(artifacts), '\n',
' \n'})
elseif node:is('fail') then
return table.concat(
{' \n',
' \n',
- ' ', Output.xml_escape(node.artifacts or ''), '\n',
+ ' ', Output.xml_escape(artifacts), '\n',
' \n'})
elseif node:is('skip') then
return table.concat({' ', Output.xml_escape(node.message or ''),'\n'})
diff --git a/luatest/output/tap.lua b/luatest/output/tap.lua
index 1292f650..6f064f93 100644
--- a/luatest/output/tap.lua
+++ b/luatest/output/tap.lua
@@ -1,3 +1,4 @@
+local utils = require('luatest.utils')
-- For a good reference for TAP format, check: http://testanything.org/tap-specification.html
local Output = require('luatest.output.generic'):new_class()
@@ -28,8 +29,11 @@ function Output.mt:update_status(node)
end
if (node:is('fail') or node:is('error')) and self.verbosity >= self.class.VERBOSITY.VERBOSE then
print(prefix .. node.trace:gsub('\n', '\n' .. prefix))
- if node.artifacts then
- print(prefix .. node.artifacts:gsub('\n', '\n' .. prefix))
+ if utils.table_len(node.servers) > 0 then
+ print(prefix .. 'artifacts:')
+ for _, server in pairs(node.servers) do
+ print(prefix .. server.alias .. ' -> ' .. server.artifacts)
+ end
end
end
end
diff --git a/luatest/output/text.lua b/luatest/output/text.lua
index bf405969..2783de20 100644
--- a/luatest/output/text.lua
+++ b/luatest/output/text.lua
@@ -1,3 +1,4 @@
+local utils = require('luatest.utils')
local Output = require('luatest.output.generic'):new_class()
Output.BOLD_CODE = '\x1B[1m'
@@ -60,10 +61,12 @@ function Output.mt:display_one_failed_test(index, fail) -- luacheck: no unused
print(index..") " .. fail.name .. self.class.ERROR_COLOR_CODE)
print(fail.message .. self.class.RESET_TERM)
print(fail.trace)
- if fail.artifacts then
- print(fail.artifacts)
+ if utils.table_len(fail.servers) > 0 then
+ print('artifacts:')
+ for _, server in pairs(fail.servers) do
+ print(('\t%s -> %s'):format(server.alias, server.artifacts))
+ end
end
- print()
end
function Output.mt:display_errored_tests()
diff --git a/luatest/runner.lua b/luatest/runner.lua
index e141869d..17eba907 100644
--- a/luatest/runner.lua
+++ b/luatest/runner.lua
@@ -413,17 +413,19 @@ end
function Runner.mt:run_tests(tests_list)
-- Make seed for ordering not affect other random numbers.
math.randomseed(os.time())
+ rawset(_G, 'current_test', {value = nil})
for _ = 1, self.exe_repeat_group or 1 do
local last_group
for _, test in ipairs(tests_list) do
- rawset(_G, 'current_test', test)
if last_group ~= test.group then
if last_group then
+ rawget(_G, 'current_test').value = nil
self:end_group(last_group)
end
self:start_group(test.group)
last_group = test.group
end
+ rawget(_G, 'current_test').value = test
self:run_test(test)
if self.result.aborted then
break
@@ -444,6 +446,13 @@ end
function Runner.mt:invoke_test_function(test)
local err = self:protected_call(test.group, test.method, test.name)
self:update_status(test, err)
+ if not test:is('success') then
+ if utils.table_len(test.servers) > 0 then
+ for _, server in pairs(test.servers) do
+ server:save_artifacts()
+ end
+ end
+ end
end
function Runner.mt:find_test(groups, name)
diff --git a/luatest/server.lua b/luatest/server.lua
index cc0d2c9c..b655fb38 100644
--- a/luatest/server.lua
+++ b/luatest/server.lua
@@ -101,6 +101,27 @@ function Server:new(object, extra)
object = utils.merge(object, extra)
self:inherit(object)
object:initialize()
+
+ -- Each method of the server instance will be overridden by a new function
+ -- in which the association of the current test and server is performed first
+ -- and then the method itself.
+ -- It solves the problem when the server is not used in the test (should not
+ -- save artifacts) and when used.
+ for k, v in pairs(self) do
+ if type(v) == 'function' then
+ object[k] = function(...)
+ local t = rawget(_G, 'current_test')
+ if t and t.value then
+ t = t.value
+ if not object.tests[t.name] then
+ object.tests[t.name] = t
+ t.servers[object.id] = object
+ end
+ end
+ return v(...)
+ end
+ end
+ end
return object
end
@@ -187,14 +208,12 @@ function Server:initialize()
self.env.LUATEST_LUACOV_ROOT = os.getenv('LUATEST_LUACOV_ROOT')
end
- if self.current_test == nil then
- self.current_test = rawget(_G, 'current_test')
- if self.current_test then
- local prefix = fio.pathjoin(Server.vardir, 'artifacts', self.rs_id or '')
- self.artifacts = fio.pathjoin(prefix, self.id)
- self.current_test:add_server_artifacts(self.alias, self.artifacts)
- end
+ if not self.tests then
+ self.tests = {}
end
+
+ local prefix = fio.pathjoin(Server.vardir, 'artifacts', self.rs_id or '')
+ self.artifacts = fio.pathjoin(prefix, self.id)
end
-- Create a table with env variables based on the constructor params.
@@ -329,12 +348,23 @@ function Server:restart(params, opts)
log.debug('Restarted server PID: ' .. self.process.pid)
end
+-- Save server artifacts by copying the working directory.
+-- Throws an error when the copying is not successful.
+function Server:save_artifacts()
+ local ok, err = fio.copytree(self.workdir, self.artifacts)
+ if not ok then
+ error(('Failed to copy artifacts for server (alias: %s, workdir: %s, pid: %d): %s')
+ :format(self.alias, fio.basename(self.workdir), self.process.pid, err))
+ end
+end
+
-- Wait until the given condition is `true` (anything except `false` and `nil`).
-- Throws an error when the server process is terminated or timeout exceeds.
local function wait_for_condition(cond_desc, server, func, ...)
local deadline = clock.time() + WAIT_TIMEOUT
while true do
if not server.process:is_alive() then
+ server:save_artifacts()
error(('Process is terminated when waiting for "%s" condition for server (alias: %s, workdir: %s, pid: %d)')
:format(cond_desc, server.alias, fio.basename(server.workdir), server.process.pid))
end
@@ -342,6 +372,7 @@ local function wait_for_condition(cond_desc, server, func, ...)
return
end
if clock.time() > deadline then
+ server:save_artifacts()
error(('Timed out to wait for "%s" condition for server (alias: %s, workdir: %s, pid: %d) within %ds')
:format(cond_desc, server.alias, fio.basename(server.workdir), server.process.pid, WAIT_TIMEOUT))
end
@@ -387,13 +418,7 @@ end
--- Stop the server and clean its working directory.
function Server:drop()
self:stop()
-
- if self.current_test and not self.current_test:is('success') then
- local ok, err = fio.copytree(self.workdir, self.artifacts)
- if not ok then
- error(('Failed to copy server artifacts: %s'):format(err))
- end
- end
+ self:save_artifacts()
fio.rmtree(self.workdir)
diff --git a/luatest/test_instance.lua b/luatest/test_instance.lua
index 549a1026..a4db250e 100644
--- a/luatest/test_instance.lua
+++ b/luatest/test_instance.lua
@@ -16,7 +16,7 @@ end
-- default constructor, test are PASS by default
function TestInstance.mt:initialize()
self.status = 'success'
- self.artifacts = nil
+ self.servers = {}
end
function TestInstance.mt:update_status(status, message, trace)
@@ -25,14 +25,6 @@ function TestInstance.mt:update_status(status, message, trace)
self.trace = trace
end
-function TestInstance.mt:add_server_artifacts(alias, workdir)
- local server_workdir = string.format('\n\t%s -> %s', alias, workdir)
- if not self.artifacts then
- self.artifacts = 'artifacts:'
- end
- self.artifacts = self.artifacts .. server_workdir
-end
-
function TestInstance.mt:is(status)
return self.status == status
end
diff --git a/test/artifacts/common_test.lua b/test/artifacts/common_test.lua
new file mode 100644
index 00000000..182c6ba9
--- /dev/null
+++ b/test/artifacts/common_test.lua
@@ -0,0 +1,63 @@
+local t = require('luatest')
+local utils = require('luatest.utils')
+
+local g = t.group()
+local Server = t.Server
+
+g.public = Server:new({ alias = 'public'})
+g.public:start()
+
+g.test_servers_not_added_if_they_are_not_used = function()
+end
+
+g.after_test('test_servers_not_added_if_they_are_not_used', function()
+ t.fail_if(
+ utils.table_len(rawget(_G, 'current_test').value.servers) ~= 0,
+ 'Test instance should not contain a servers')
+end)
+
+g.test_only_public_server_has_been_added = function()
+ g.public:get_vclock()
+end
+
+g.after_test('test_only_public_server_has_been_added', function()
+ t.fail_if(
+ rawget(_G, 'current_test').value.servers[g.public.id] == nil,
+ 'Test should contain only public server')
+end)
+
+
+g.test_only_private_server_has_been_added = function()
+ g.private = Server:new({alias = 'private'})
+ g.private:start()
+end
+
+g.after_test('test_only_private_server_has_been_added', function()
+ t.fail_if(
+ rawget(_G, 'current_test').value.servers[g.private.id] == nil,
+ 'Test should contain only private server')
+
+end)
+
+g.before_test('test_add_server_from_test_hooks', function()
+ g.before = Server:new({ alias = 'before' })
+ g.before:start()
+end)
+
+g.test_add_server_from_test_hooks = function()
+end
+
+g.after_test('test_add_server_from_test_hooks', function()
+ g.after = Server:new({ alias = 'after' })
+ g.after:start()
+
+ local test_servers = rawget(_G, 'current_test').value.servers
+
+ t.fail_if(
+ utils.table_len(test_servers) ~= 2,
+ 'Test should contain two servers (from before/after hooks)')
+ t.fail_if(
+ test_servers[g.before.id] == nil or
+ test_servers[g.after.id] == nil,
+ 'Test should contain only `before` and `after` servers')
+end)
diff --git a/test/artifacts/end_group_test.lua b/test/artifacts/end_group_test.lua
new file mode 100644
index 00000000..ac08a74b
--- /dev/null
+++ b/test/artifacts/end_group_test.lua
@@ -0,0 +1,27 @@
+local t = require('luatest')
+local utils = require('luatest.utils')
+
+local Server = t.Server
+
+local g = t.group()
+
+g.test_foo = function()
+ g.foo_test = rawget(_G, 'current_test').value
+end
+
+g.test_bar = function()
+ g.bar_test = rawget(_G, 'current_test').value
+end
+
+g.after_all(function()
+ g.s = Server:new()
+ g.s:start()
+
+ t.fail_if(
+ utils.table_len(g.foo_test.servers) ~= 0,
+ 'Test instance `foo` should not contain a servers')
+
+ t.fail_if(
+ utils.table_len(g.bar_test.servers) ~= 0,
+ 'Test instance `bar` should not contain a servers')
+end)
diff --git a/test/artifacts/hooks_test.lua b/test/artifacts/hooks_test.lua
new file mode 100644
index 00000000..2bce0b96
--- /dev/null
+++ b/test/artifacts/hooks_test.lua
@@ -0,0 +1,71 @@
+local t = require('luatest')
+local utils = require('luatest.utils')
+local fio = require('fio')
+
+local g = t.group()
+local Server = t.Server
+
+local function is_server_in_test(server, test)
+ for _, s in pairs(test.servers) do
+ if server.id == s.id then
+ return true
+ end
+ end
+ return false
+end
+
+g.public = Server:new({alias = 'public'})
+g.public:start()
+
+g.before_all(function()
+ g.all = Server:new({alias = 'all9'})
+ g.all:start()
+end)
+
+g.before_each(function()
+ g.each = Server:new({alias = 'each'})
+ g.each:start()
+end)
+
+g.before_test('test_association_between_test_and_servers', function()
+ g.test = Server:new({alias = 'test'})
+ g.test:start()
+end)
+
+
+g.test_association_between_test_and_servers = function()
+ g.internal = Server:new({alias = 'internal'})
+ g.internal:start()
+
+ local test = rawget(_G, 'current_test').value
+
+ -- test static association
+ t.assert(is_server_in_test(g.internal, test))
+ t.assert(is_server_in_test(g.each, test))
+ t.assert(is_server_in_test(g.test, test))
+ t.assert_not(is_server_in_test(g.public, test))
+
+ g.public:exec(function() return 1 + 1 end)
+ g.all:exec(function() return 1 + 1 end)
+
+ -- test dynamic association
+ t.assert(is_server_in_test(g.public, test))
+ t.assert(is_server_in_test(g.all, test))
+
+ t.assert(utils.table_len(test.servers) == 5)
+end
+
+g.after_test('test_association_between_test_and_servers', function()
+ g.test:drop()
+ t.assert(fio.path.exists(g.test.artifacts))
+end)
+
+g.after_each(function()
+ g.each:drop()
+ t.assert(fio.path.exists(g.each.artifacts))
+end)
+
+g.after_all(function()
+ g.all:drop()
+ t.assert(fio.path.exists(g.all.artifacts))
+end)
diff --git a/test/artifacts/replica_set_test.lua b/test/artifacts/replica_set_test.lua
new file mode 100644
index 00000000..508cbad7
--- /dev/null
+++ b/test/artifacts/replica_set_test.lua
@@ -0,0 +1,39 @@
+local t = require('luatest')
+local utils = require('luatest.utils')
+local ReplicaSet = require('luatest.replica_set')
+
+local g = t.group()
+
+g.box_cfg = {
+ replication_timeout = 0.1,
+ replication_connect_timeout = 3,
+ replication_sync_lag = 0.01,
+ replication_connect_quorum = 3
+}
+
+g.rs = ReplicaSet:new()
+g.rs:build_and_add_server({alias = 'replica1', box_cfg = g.box_cfg})
+g.rs:build_and_add_server({alias = 'replica2', box_cfg = g.box_cfg})
+
+g.test_foo = function()
+ g.rs:start()
+ g.foo_test = rawget(_G, 'current_test').value
+end
+
+g.test_bar = function()
+ g.bar_test = rawget(_G, 'current_test').value
+end
+
+g.after_test('test_foo', function()
+ t.fail_if(
+ utils.table_len(g.foo_test.servers) ~= 2,
+ 'Test instance should contain all servers from replica set'
+ )
+end)
+
+g.after_test('test_bar', function()
+ t.fail_if(
+ utils.table_len(g.bar_test.servers) ~= 0,
+ 'Test instance should not contain any servers'
+ )
+end)
diff --git a/test/artifacts/sequence_test.lua b/test/artifacts/sequence_test.lua
new file mode 100644
index 00000000..6dadc486
--- /dev/null
+++ b/test/artifacts/sequence_test.lua
@@ -0,0 +1,42 @@
+local t = require('luatest')
+local utils = require('luatest.utils')
+
+local g = t.group()
+local Server = t.Server
+
+g.s = Server:new()
+g.s:start()
+
+g.before_each(function()
+ g.each = Server:new()
+ g.each:start()
+end)
+
+g.test_foo = function()
+ g.foo_test = rawget(_G, 'current_test').value
+ g.foo_test_server = g.each.id
+end
+
+g.test_bar = function()
+ g.bar_test = rawget(_G, 'current_test').value
+ g.bar_test_server = g.each.id
+end
+
+g.after_test('test_foo', function()
+ t.fail_if(
+ utils.table_len(g.foo_test.servers) ~= 1,
+ 'Test instance should contain server')
+end)
+
+g.after_test('test_bar', function()
+ t.fail_if(
+ utils.table_len(g.bar_test.servers) ~= 1,
+ 'Test instance should contain server')
+end)
+
+g.after_all(function()
+ t.fail_if(
+ g.foo_test_server == g.bar_test_server,
+ 'Servers must be unique within the group'
+ )
+end)
diff --git a/test/artifacts/start_group_test.lua b/test/artifacts/start_group_test.lua
new file mode 100644
index 00000000..46821341
--- /dev/null
+++ b/test/artifacts/start_group_test.lua
@@ -0,0 +1,31 @@
+local t = require('luatest')
+local utils = require('luatest.utils')
+
+local Server = t.Server
+
+local g = t.group()
+
+g.before_all(function()
+ g.s = Server:new()
+ g.s:start()
+end)
+
+g.test_foo = function()
+ g.foo_test = rawget(_G, 'current_test').value
+end
+
+g.after_test('test_foo', function()
+ t.fail_if(
+ utils.table_len(g.foo_test.servers) ~= 0,
+ 'Test instance should not contain a servers')
+end)
+
+g.test_bar = function()
+ g.bar_test = rawget(_G, 'current_test').value
+end
+
+g.after_test('test_bar', function()
+ t.fail_if(
+ utils.table_len(g.bar_test.servers) ~= 0,
+ 'Test instance should not contain a servers')
+end)