-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The original `justrun` module (path: tarantool/test/justrun.lua) has been moved to the current project with minor changes and will be available as follows: local t = require('luatest') t.justrun.tarantool(...) This module works with the `popen` module which requires Tarantool 2.4.1 and newer. Otherwise `justrun.tarantool(dir, env, args[, opts])` will cause an error. Closes #365
- Loading branch information
Showing
5 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
--- Simple Tarantool runner and output catcher. | ||
-- | ||
-- Sometimes it is necessary to run tarantool with particular arguments and | ||
-- verify its output. `luatest.server` provides a supervisor like | ||
-- interface: an instance is started, calls box.cfg() and we can | ||
-- communicate with it using net.box. Another helper in tarantool/tarantool, | ||
-- `test.interactive_tarantool`, aims to solve all the problems around | ||
-- readline console and also provides ability to communicate with the | ||
-- instance interactively. | ||
-- | ||
-- However, there is nothing like 'just run tarantool with given args and | ||
-- give me its output'. | ||
-- | ||
-- @module luatest.justrun | ||
|
||
local checks = require('checks') | ||
local fun = require('fun') | ||
local json = require('json') | ||
local fiber = require('fiber') | ||
|
||
local log = require('luatest.log') | ||
|
||
local justrun = {} | ||
|
||
local function collect_stderr(ph) | ||
local f = fiber.new(function() | ||
local fiber_name = "child's stderr collector" | ||
fiber.name(fiber_name, {truncate = true}) | ||
|
||
local chunks = {} | ||
|
||
while true do | ||
local chunk, err = ph:read({stderr = true}) | ||
if chunk == nil then | ||
log.warn('%s: got error, exiting: %s', fiber_name, err) | ||
break | ||
end | ||
if chunk == '' then | ||
log.info('%s: got EOF, exiting', fiber_name) | ||
break | ||
end | ||
table.insert(chunks, chunk) | ||
end | ||
|
||
-- Glue all chunks, strip trailing newline. | ||
return table.concat(chunks):rstrip() | ||
end) | ||
f:set_joinable(true) | ||
return f | ||
end | ||
|
||
local function cancel_stderr_fiber(stderr_fiber) | ||
if stderr_fiber == nil then | ||
return | ||
end | ||
stderr_fiber:cancel() | ||
end | ||
|
||
local function join_stderr_fiber(stderr_fiber) | ||
if stderr_fiber == nil then | ||
return | ||
end | ||
return select(2, assert(stderr_fiber:join())) | ||
end | ||
|
||
--- Run tarantool in given directory with given environment and | ||
-- command line arguments and catch its output. | ||
-- | ||
-- Expects JSON lines as the output and parses it into an array | ||
-- (it can be disabled using `nojson` option). | ||
-- | ||
-- Options: | ||
-- | ||
-- - nojson (boolean, default: false) | ||
-- | ||
-- Don't attempt to decode stdout as a stream of JSON lines, | ||
-- return as is. | ||
-- | ||
-- - stderr (boolean, default: false) | ||
-- | ||
-- Collect stderr and place it into the `stderr` field of the | ||
-- return value | ||
-- | ||
-- - quote_args (boolean, default: false) | ||
-- | ||
-- Quote CLI arguments before concatenating them into a shell | ||
-- command. | ||
-- | ||
-- @string dir Directory where the process will run. | ||
-- @tparam table env Environment variables for the process. | ||
-- @tparam table args Options that will be passed when the process starts. | ||
-- @tparam[opt] table opts Custom options: nojson, stderr and quote_args. | ||
-- @treturn table | ||
function justrun.tarantool(dir, env, args, opts) | ||
checks('string', 'table', 'table', '?table') | ||
opts = opts or {} | ||
|
||
local popen = require('popen') | ||
|
||
-- Prevent system/user inputrc configuration file from | ||
-- influencing testing code. | ||
env['INPUTRC'] = '/dev/null' | ||
|
||
local tarantool_exe = arg[-1] | ||
-- Use popen.shell() instead of popen.new() due to lack of | ||
-- cwd option in popen (gh-5633). | ||
local env_str = table.concat(fun.iter(env):map(function(k, v) | ||
return ('%s=%q'):format(k, v) | ||
end):totable(), ' ') | ||
local args_str = table.concat(fun.iter(args):map(function(v) | ||
return opts.quote_args and ('%q'):format(v) or v | ||
end):totable(), ' ') | ||
local command = ('cd %s && %s %s %s'):format(dir, env_str, tarantool_exe, | ||
args_str) | ||
log.info('Running a command: %s', command) | ||
local mode = opts.stderr and 'rR' or 'r' | ||
local ph = popen.shell(command, mode) | ||
|
||
local stderr_fiber | ||
if opts.stderr then | ||
stderr_fiber = collect_stderr(ph) | ||
end | ||
|
||
-- Read everything until EOF. | ||
local chunks = {} | ||
while true do | ||
local chunk, err = ph:read() | ||
if chunk == nil then | ||
cancel_stderr_fiber(stderr_fiber) | ||
ph:close() | ||
error(err) | ||
end | ||
if chunk == '' then -- EOF | ||
break | ||
end | ||
table.insert(chunks, chunk) | ||
end | ||
|
||
local exit_code = ph:wait().exit_code | ||
local stderr = join_stderr_fiber(stderr_fiber) | ||
ph:close() | ||
|
||
-- If an error occurs, discard the output and return only the | ||
-- exit code. However, return stderr. | ||
if exit_code ~= 0 then | ||
return { | ||
exit_code = exit_code, | ||
stderr = stderr, | ||
} | ||
end | ||
|
||
-- Glue all chunks, strip trailing newline. | ||
local res = table.concat(chunks):rstrip() | ||
log.info('Command output:\n%s', res) | ||
|
||
-- Decode JSON object per line into array of tables (if | ||
-- `nojson` option is not passed). | ||
local decoded | ||
if opts.nojson then | ||
decoded = res | ||
else | ||
decoded = fun.iter(res:split('\n')):map(json.decode):totable() | ||
end | ||
|
||
return { | ||
exit_code = exit_code, | ||
stdout = decoded, | ||
stderr = stderr, | ||
} | ||
end | ||
|
||
return justrun |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
local t = require('luatest') | ||
local fio = require('fio') | ||
|
||
local justrun = require('luatest.justrun') | ||
local utils = require('luatest.utils') | ||
|
||
local g = t.group() | ||
|
||
g.before_each(function() | ||
g.tempdir = fio.tempdir() | ||
g.tempfile = fio.pathjoin(g.tempdir, 'main.lua') | ||
|
||
local default_flags = {'O_CREAT', 'O_WRONLY', 'O_TRUNC'} | ||
local default_mode = tonumber('644', 8) | ||
|
||
g.tempfile_fh = fio.open(g.tempfile, default_flags, default_mode) | ||
end) | ||
|
||
g.after_each(function() | ||
fio.rmdir(g.tempdir) | ||
end) | ||
|
||
g.before_test('test_stdout_stderr_output', function() | ||
g.tempfile_fh:write([[ | ||
local log = require('log') | ||
print('hello stdout!') | ||
log.info('hello stderr!') | ||
]]) | ||
end) | ||
|
||
g.test_stdout_stderr_output = function() | ||
t.skip_if(not utils.version_current_ge_than(2, 4, 1), | ||
"popen module is available since Tarantool 2.4.1.") | ||
local res = justrun.tarantool(g.tempdir, {}, {g.tempfile}, {nojson = true, stderr = true}) | ||
|
||
t.assert_equals(res.exit_code, 0) | ||
t.assert_str_contains(res.stdout, 'hello stdout!') | ||
t.assert_str_contains(res.stderr, 'hello stderr!') | ||
end | ||
|
||
g.before_test('test_decode_stdout_as_json', function() | ||
g.tempfile_fh:write([[ | ||
print('{"a": 1, "b": 2}') | ||
]]) | ||
end) | ||
|
||
g.test_decode_stdout_as_json = function() | ||
t.skip_if(not utils.version_current_ge_than(2, 4, 1), | ||
"popen module is available since Tarantool 2.4.1.") | ||
local res = justrun.tarantool(g.tempdir, {}, {g.tempfile}, {nojson = false, stdout = true}) | ||
|
||
t.assert_equals(res.exit_code, 0) | ||
t.assert_equals(res.stdout, {{ a = 1, b = 2}}) | ||
end | ||
|
||
g.before_test('test_bad_exit_code', function() | ||
g.tempfile_fh:write([[ | ||
local magic = require('magic_lib') | ||
]]) | ||
end) | ||
|
||
g.test_bad_exit_code = function() | ||
t.skip_if(not utils.version_current_ge_than(2, 4, 1), | ||
"popen module is available since Tarantool 2.4.1.") | ||
local res = justrun.tarantool(g.tempdir, {}, {g.tempfile}, {nojson = true, stderr = true}) | ||
|
||
t.assert_equals(res.exit_code, 1) | ||
|
||
t.assert_str_contains(res.stderr, "module 'magic_lib' not found") | ||
t.assert_equals(res.stdout, nil) | ||
end | ||
|
||
g.test_error_when_popen_is_not_available = function() | ||
-- Substitute `require` function to test the behavior of `justrun.tarantool` | ||
-- if the `popen` module is not available (on versions below 2.4.1). | ||
|
||
-- luacheck: push ignore 121 | ||
local old = require | ||
require = function(name) -- ignore: | ||
if name == 'popen' then | ||
return error("module " .. name .. " not found:") | ||
else | ||
return old(name) | ||
end | ||
end | ||
|
||
local _, err = pcall(justrun.tarantool, g.tempdir, {}, {g.tempfile}, {nojson = true}) | ||
|
||
t.assert_str_contains(err, 'module popen not found:') | ||
|
||
require = old | ||
-- luacheck: pop | ||
end |