|
| 1 | +--- Simple Tarantool runner and output catcher. |
| 2 | +-- |
| 3 | +-- Sometimes it is necessary to run tarantool with particular arguments and |
| 4 | +-- verify its output. `luatest.server` provides a supervisor like |
| 5 | +-- interface: an instance is started, calls box.cfg() and we can |
| 6 | +-- communicate with it using net.box. Another helper in tarantool/tarantool, |
| 7 | +-- `test.interactive_tarantool`, aims to solve all the problems around |
| 8 | +-- readline console and also provides ability to communicate with the |
| 9 | +-- instance interactively. |
| 10 | +-- |
| 11 | +-- However, there is nothing like 'just run tarantool with given args and |
| 12 | +-- give me its output'. |
| 13 | +-- |
| 14 | +-- @module luatest.justrun |
| 15 | + |
| 16 | +local checks = require('checks') |
| 17 | +local fun = require('fun') |
| 18 | +local json = require('json') |
| 19 | +local fiber = require('fiber') |
| 20 | + |
| 21 | +local log = require('luatest.log') |
| 22 | + |
| 23 | +local justrun = {} |
| 24 | + |
| 25 | +local function collect_stderr(ph) |
| 26 | + local f = fiber.new(function() |
| 27 | + local fiber_name = "child's stderr collector" |
| 28 | + fiber.name(fiber_name, {truncate = true}) |
| 29 | + |
| 30 | + local chunks = {} |
| 31 | + |
| 32 | + while true do |
| 33 | + local chunk, err = ph:read({stderr = true}) |
| 34 | + if chunk == nil then |
| 35 | + log.warn('%s: got error, exiting: %s', fiber_name, err) |
| 36 | + break |
| 37 | + end |
| 38 | + if chunk == '' then |
| 39 | + log.info('%s: got EOF, exiting', fiber_name) |
| 40 | + break |
| 41 | + end |
| 42 | + table.insert(chunks, chunk) |
| 43 | + end |
| 44 | + |
| 45 | + -- Glue all chunks, strip trailing newline. |
| 46 | + return table.concat(chunks):rstrip() |
| 47 | + end) |
| 48 | + f:set_joinable(true) |
| 49 | + return f |
| 50 | +end |
| 51 | + |
| 52 | +local function cancel_stderr_fiber(stderr_fiber) |
| 53 | + if stderr_fiber == nil then |
| 54 | + return |
| 55 | + end |
| 56 | + stderr_fiber:cancel() |
| 57 | +end |
| 58 | + |
| 59 | +local function join_stderr_fiber(stderr_fiber) |
| 60 | + if stderr_fiber == nil then |
| 61 | + return |
| 62 | + end |
| 63 | + return select(2, assert(stderr_fiber:join())) |
| 64 | +end |
| 65 | + |
| 66 | +--- Run tarantool in given directory with given environment and |
| 67 | +-- command line arguments and catch its output. |
| 68 | +-- |
| 69 | +-- Expects JSON lines as the output and parses it into an array |
| 70 | +-- (it can be disabled using `nojson` option). |
| 71 | +-- |
| 72 | +-- Options: |
| 73 | +-- |
| 74 | +-- - nojson (boolean, default: false) |
| 75 | +-- |
| 76 | +-- Don't attempt to decode stdout as a stream of JSON lines, |
| 77 | +-- return as is. |
| 78 | +-- |
| 79 | +-- - stderr (boolean, default: false) |
| 80 | +-- |
| 81 | +-- Collect stderr and place it into the `stderr` field of the |
| 82 | +-- return value |
| 83 | +-- |
| 84 | +-- - quote_args (boolean, default: false) |
| 85 | +-- |
| 86 | +-- Quote CLI arguments before concatenating them into a shell |
| 87 | +-- command. |
| 88 | +-- |
| 89 | +-- @string dir Directory where the process will run. |
| 90 | +-- @tparam table env Environment variables for the process. |
| 91 | +-- @tparam table args Options that will be passed when the process starts. |
| 92 | +-- @tparam[opt] table opts Custom options: nojson, stderr and quote_args. |
| 93 | +-- @treturn table |
| 94 | +function justrun.tarantool(dir, env, args, opts) |
| 95 | + checks('string', 'table', 'table', '?table') |
| 96 | + opts = opts or {} |
| 97 | + |
| 98 | + local popen = require('popen') |
| 99 | + |
| 100 | + -- Prevent system/user inputrc configuration file from |
| 101 | + -- influencing testing code. |
| 102 | + env['INPUTRC'] = '/dev/null' |
| 103 | + |
| 104 | + local tarantool_exe = arg[-1] |
| 105 | + -- Use popen.shell() instead of popen.new() due to lack of |
| 106 | + -- cwd option in popen (gh-5633). |
| 107 | + local env_str = table.concat(fun.iter(env):map(function(k, v) |
| 108 | + return ('%s=%q'):format(k, v) |
| 109 | + end):totable(), ' ') |
| 110 | + local args_str = table.concat(fun.iter(args):map(function(v) |
| 111 | + return opts.quote_args and ('%q'):format(v) or v |
| 112 | + end):totable(), ' ') |
| 113 | + local command = ('cd %s && %s %s %s'):format(dir, env_str, tarantool_exe, |
| 114 | + args_str) |
| 115 | + log.info('Running a command: %s', command) |
| 116 | + local mode = opts.stderr and 'rR' or 'r' |
| 117 | + local ph = popen.shell(command, mode) |
| 118 | + |
| 119 | + local stderr_fiber |
| 120 | + if opts.stderr then |
| 121 | + stderr_fiber = collect_stderr(ph) |
| 122 | + end |
| 123 | + |
| 124 | + -- Read everything until EOF. |
| 125 | + local chunks = {} |
| 126 | + while true do |
| 127 | + local chunk, err = ph:read() |
| 128 | + if chunk == nil then |
| 129 | + cancel_stderr_fiber(stderr_fiber) |
| 130 | + ph:close() |
| 131 | + error(err) |
| 132 | + end |
| 133 | + if chunk == '' then -- EOF |
| 134 | + break |
| 135 | + end |
| 136 | + table.insert(chunks, chunk) |
| 137 | + end |
| 138 | + |
| 139 | + local exit_code = ph:wait().exit_code |
| 140 | + local stderr = join_stderr_fiber(stderr_fiber) |
| 141 | + ph:close() |
| 142 | + |
| 143 | + -- If an error occurs, discard the output and return only the |
| 144 | + -- exit code. However, return stderr. |
| 145 | + if exit_code ~= 0 then |
| 146 | + return { |
| 147 | + exit_code = exit_code, |
| 148 | + stderr = stderr, |
| 149 | + } |
| 150 | + end |
| 151 | + |
| 152 | + -- Glue all chunks, strip trailing newline. |
| 153 | + local res = table.concat(chunks):rstrip() |
| 154 | + log.info('Command output:\n%s', res) |
| 155 | + |
| 156 | + -- Decode JSON object per line into array of tables (if |
| 157 | + -- `nojson` option is not passed). |
| 158 | + local decoded |
| 159 | + if opts.nojson then |
| 160 | + decoded = res |
| 161 | + else |
| 162 | + decoded = fun.iter(res:split('\n')):map(json.decode):totable() |
| 163 | + end |
| 164 | + |
| 165 | + return { |
| 166 | + exit_code = exit_code, |
| 167 | + stdout = decoded, |
| 168 | + stderr = stderr, |
| 169 | + } |
| 170 | +end |
| 171 | + |
| 172 | +return justrun |
0 commit comments