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..d28782c6 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..41244d06 100644
--- a/luatest/runner.lua
+++ b/luatest/runner.lua
@@ -413,10 +413,10 @@ 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
self:end_group(last_group)
@@ -424,6 +424,7 @@ function Runner.mt:run_tests(tests_list)
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 +445,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 a08faa7b..ada8988a 100644
--- a/luatest/server.lua
+++ b/luatest/server.lua
@@ -100,6 +100,30 @@ 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 == nil then
+ return v(...)
+ elseif t.value == nil then
+ return v(...)
+ end
+ t = t.value
+ if object.tests[t.name] == nil then
+ object.tests[t.name] = t
+ t.servers[object.id] = object
+ end
+ return v(...)
+ end
+ end
+ end
return object
end
@@ -186,14 +210,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 self.tests == nil 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.
@@ -328,12 +350,22 @@ 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 server (%s) artifacts: %s'):format(self.alias, 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
@@ -341,6 +373,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
@@ -386,13 +419,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_test.lua b/test/artifacts_test.lua
new file mode 100644
index 00000000..b8ccded3
--- /dev/null
+++ b/test/artifacts_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 = 'all'})
+ 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)