diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/functional/api/server_requests_spec.lua | 1 | ||||
| -rw-r--r-- | test/functional/core/channels_spec.lua | 266 | ||||
| -rw-r--r-- | test/functional/core/job_spec.lua | 50 | ||||
| -rw-r--r-- | test/functional/helpers.lua | 45 | ||||
| -rw-r--r-- | test/helpers.lua | 17 | 
5 files changed, 350 insertions, 29 deletions
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index a2a198ca83..37ac532d18 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -262,6 +262,7 @@ describe('server -> client', function()        eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n"))        eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_message())        funcs.rpcrequest(jobid, "exit") +      eq({'notification', 'stderr', {0, {''}}}, next_message())        eq({'notification', 'exit', {0, 0}}, next_message())      end)    end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua new file mode 100644 index 0000000000..7a2f157df3 --- /dev/null +++ b/test/functional/core/channels_spec.lua @@ -0,0 +1,266 @@ + +local helpers = require('test.functional.helpers')(after_each) +local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq, +   helpers.eval, helpers.next_message, helpers.ok, helpers.source +local command, funcs, meths = helpers.command, helpers.funcs, helpers.meths +local sleep = helpers.sleep +local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv +local set_session = helpers.set_session +local nvim_prog = helpers.nvim_prog +local retry = helpers.retry +local expect_twostreams = helpers.expect_twostreams + +describe('channels', function() +  local init = [[ +    function! Normalize(data) abort +      " Windows: remove ^M +      return type([]) == type(a:data) +        \ ? map(a:data, 'substitute(v:val, "\r", "", "g")') +        \ : a:data +    endfunction +    function! OnEvent(id, data, event) dict +      call rpcnotify(1, a:event, a:id, a:data) +    endfunction +  ]] +  before_each(function() +    clear() +    source(init) +  end) + +  it('can connect to socket', function() +    local server = spawn(nvim_argv) +    set_session(server) +    local address = funcs.serverlist()[1] +    local client = spawn(nvim_argv) +    set_session(client, true) +    source(init) + +    meths.set_var('address', address) +    command("let g:id = sockconnect('pipe', address, {'on_data':'OnEvent'})") +    local id = eval("g:id") +    ok(id > 0) + +    command("call chansend(g:id, msgpackdump([[2,'nvim_set_var',['code',23]]]))") +    set_session(server, true) +    retry(nil, 1000, function() +      eq(23, meths.get_var('code')) +    end) +    set_session(client, true) + +    command("call chansend(g:id, msgpackdump([[0,0,'nvim_eval',['2+3']]]))") + + +    local res = eval("msgpackdump([[1,0,v:null,5]])") +    eq({"\148\001\n\192\005"}, res) +    eq({'notification', 'data', {id, res}}, next_msg()) +    command("call chansend(g:id, msgpackdump([[2,'nvim_command',['quit']]]))") +    eq({'notification', 'data', {id, {''}}}, next_msg()) +  end) + +  it('can use stdio channel', function() +    source([[ +      let g:job_opts = { +      \ 'on_stdout': function('OnEvent'), +      \ 'on_stderr': function('OnEvent'), +      \ 'on_exit': function('OnEvent'), +      \ } +    ]]) +    meths.set_var("nvim_prog", nvim_prog) +    meths.set_var("code", [[ +      function! OnEvent(id, data, event) dict +        let text = string([a:id, a:data, a:event]) +        call chansend(g:x, text) + +        if a:data == [''] +          call chansend(v:stderr, "*dies*") +          quit +        endif +      endfunction +      let g:x = stdioopen({'on_stdin':'OnEvent'}) +      call chansend(x, "hello") +    ]]) +    command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)") +    local id = eval("g:id") +    ok(id > 0) + +    eq({ "notification", "stdout", {id, { "hello" } } }, next_msg()) + +    command("call chansend(id, 'howdy')") +    eq({"notification", "stdout", {id, {"[1, ['howdy'], 'stdin']"}}}, next_msg()) + +    command("call chanclose(id, 'stdin')") +    expect_twostreams({{"notification", "stdout", {id, {"[1, [''], 'stdin']"}}}, +                       {'notification', 'stdout', {id, {''}}}}, +                      {{"notification", "stderr", {id, {"*dies*"}}}, +                       {'notification', 'stderr', {id, {''}}}}) +    eq({"notification", "exit", {3,0}}, next_msg()) +  end) + +  local function expect_twoline(id, stream, line1, line2, nobr) +    local msg = next_msg() +    local joined = nobr and {line1..line2} or {line1, line2} +    if not pcall(eq, {"notification", stream, {id, joined}}, msg) then +      local sep = (not nobr) and "" or nil +      eq({"notification", stream, {id, {line1, sep}}}, msg) +      eq({"notification", stream, {id, {line2}}}, next_msg()) +    end +  end + +  it('can use stdio channel with pty', function() +    if helpers.pending_win32(pending) then return end +    source([[ +      let g:job_opts = { +      \ 'on_stdout': function('OnEvent'), +      \ 'on_exit': function('OnEvent'), +      \ 'pty': v:true, +      \ } +    ]]) +    meths.set_var("nvim_prog", nvim_prog) +    meths.set_var("code", [[ +      function! OnEvent(id, data, event) dict +        let text = string([a:id, a:data, a:event]) +        call chansend(g:x, text) +      endfunction +      let g:x = stdioopen({'on_stdin':'OnEvent'}) +    ]]) +    command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)") +    local id = eval("g:id") +    ok(id > 0) + +    command("call chansend(id, 'TEXT\n')") +    expect_twoline(id, "stdout", "TEXT\r", "[1, ['TEXT', ''], 'stdin']") + + +    command("call chansend(id, 'neovan')") +    eq({"notification", "stdout", {id, {"neovan"}}}, next_msg()) +    command("call chansend(id, '\127\127im\n')") +    expect_twoline(id, "stdout", "\b \b\b \bim\r", "[1, ['neovim', ''], 'stdin']") + +    command("call chansend(id, 'incomplet\004')") + +    local is_freebsd = eval("system('uname') =~? 'FreeBSD'") == 1 +    local bsdlike = is_freebsd or (helpers.os_name() == "osx") +    print("bsdlike:", bsdlike) +    local extra = bsdlike and "^D\008\008" or "" +    expect_twoline(id, "stdout", +                   "incomplet"..extra, "[1, ['incomplet'], 'stdin']", true) + + +    command("call chansend(id, '\004')") +    if bsdlike then +      expect_twoline(id, "stdout", extra, "[1, [''], 'stdin']", true) +    else +      eq({"notification", "stdout", {id, {"[1, [''], 'stdin']"}}}, next_msg()) +    end + +    -- channel is still open +    command("call chansend(id, 'hi again!\n')") +    eq({"notification", "stdout", {id, {"hi again!\r", ""}}}, next_msg()) +  end) + + +  it('stdio channel can use rpc and stderr simultaneously', function() +    if helpers.pending_win32(pending) then return end +    source([[ +      let g:job_opts = { +      \ 'on_stderr': function('OnEvent'), +      \ 'on_exit': function('OnEvent'), +      \ 'rpc': v:true, +      \ } +    ]]) +    meths.set_var("nvim_prog", nvim_prog) +    meths.set_var("code", [[ +      let id = stdioopen({'rpc':v:true}) +      call rpcnotify(id,"nvim_call_function", "rpcnotify", [1, "message", "hi there!", id]) +      call chansend(v:stderr, "trouble!") +    ]]) +    command("let id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)") +    eq({"notification", "message", {"hi there!", 1}}, next_msg()) +    eq({"notification", "stderr", {3, {"trouble!"}}}, next_msg()) + +    eq(30, eval("rpcrequest(id, 'nvim_eval', '[chansend(v:stderr, \"math??\"), 5*6][1]')")) +    eq({"notification", "stderr", {3, {"math??"}}}, next_msg()) + +    local _, err = pcall(command,"call rpcrequest(id, 'nvim_command', 'call chanclose(v:stderr, \"stdin\")')") +    ok(string.find(err,"E906: invalid stream for channel") ~= nil) + +    eq(1, eval("rpcrequest(id, 'nvim_eval', 'chanclose(v:stderr, \"stderr\")')")) +    eq({"notification", "stderr", {3, {""}}}, next_msg()) + +    command("call rpcnotify(id, 'nvim_command', 'quit')") +    eq({"notification", "exit", {3, 0}}, next_msg()) +  end) + +  it('can use buffered output mode', function() +    if helpers.pending_win32(pending) then return end +    source([[ +      let g:job_opts = { +      \ 'on_stdout': function('OnEvent'), +      \ 'on_exit': function('OnEvent'), +      \ 'stdout_buffered': v:true, +      \ } +    ]]) +    command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)") +    local id = eval("g:id") + +    command([[call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")]]) +    sleep(10) +    command([[call chansend(id, "xx\n20 GOTO 10\nzz\n")]]) +    command("call chanclose(id, 'stdin')") + +    eq({"notification", "stdout", {id, {'10 PRINT "NVIM"', +                                       '20 GOTO 10', ''}}}, next_msg()) +    eq({"notification", "exit", {id, 0}}, next_msg()) + +    command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)") +    id = eval("g:id") + +    command([[call chansend(id, "is no number\nnot at all")]]) +    command("call chanclose(id, 'stdin')") + +    -- works correctly with no output +    eq({"notification", "stdout", {id, {''}}}, next_msg()) +    eq({"notification", "exit", {id, 1}}, next_msg()) + +  end) + +  it('can use buffered output mode with no stream callback', function() +    if helpers.pending_win32(pending) then return end +    source([[ +      function! OnEvent(id, data, event) dict +        call rpcnotify(1, a:event, a:id, a:data, self.stdout) +      endfunction +      let g:job_opts = { +      \ 'on_exit': function('OnEvent'), +      \ 'stdout_buffered': v:true, +      \ } +    ]]) +    command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)") +    local id = eval("g:id") + +    command([[call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")]]) +    sleep(10) +    command([[call chansend(id, "xx\n20 GOTO 10\nzz\n")]]) +    command("call chanclose(id, 'stdin')") + +    eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"', +                                         '20 GOTO 10', ''}}}, next_msg()) + +    -- reset dictionary +    source([[ +      let g:job_opts = { +      \ 'on_exit': function('OnEvent'), +      \ 'stdout_buffered': v:true, +      \ } +    ]]) +    command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)") +    id = eval("g:id") + +    command([[call chansend(id, "is no number\nnot at all")]]) +    command("call chanclose(id, 'stdin')") + +    -- works correctly with no output +    eq({"notification", "exit", {id, 1, {''}}}, next_msg()) + +  end) +end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 1b8a5b1b95..e957650c88 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -10,6 +10,7 @@ local wait = helpers.wait  local iswin = helpers.iswin  local get_pathsep = helpers.get_pathsep  local nvim_set = helpers.nvim_set +local expect_twostreams = helpers.expect_twostreams  local Screen = require('test.functional.ui.screen')  describe('jobs', function() @@ -29,15 +30,14 @@ describe('jobs', function()          \ ? map(a:data, 'substitute(v:val, "\r", "", "g")')          \ : a:data      endfunction -    function! s:OnEvent(id, data, event) dict +    function! OnEvent(id, data, event) dict        let userdata = get(self, 'user')        let data     = Normalize(a:data)        call rpcnotify(g:channel, a:event, userdata, data)      endfunction      let g:job_opts = { -    \ 'on_stdout': function('s:OnEvent'), -    \ 'on_stderr': function('s:OnEvent'), -    \ 'on_exit': function('s:OnEvent'), +    \ 'on_stdout': function('OnEvent'), +    \ 'on_exit': function('OnEvent'),      \ 'user': 0      \ }      ]]) @@ -51,6 +51,7 @@ describe('jobs', function()        nvim('command', "let j = jobstart('echo $VAR', g:job_opts)")      end      eq({'notification', 'stdout', {0, {'abc', ''}}}, next_msg()) +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -63,6 +64,7 @@ describe('jobs', function()      end      eq({'notification', 'stdout',        {0, {(iswin() and [[C:\]] or '/'), ''}}}, next_msg()) +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -76,6 +78,7 @@ describe('jobs', function()        nvim('command', "let j = jobstart('pwd', g:job_opts)")      end      eq({'notification', 'stdout', {0, {dir, ''}}}, next_msg()) +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())      rmdir(dir)    end) @@ -118,8 +121,12 @@ describe('jobs', function()    it('invokes callbacks when the job writes and exits', function()      -- TODO: hangs on Windows      if helpers.pending_win32(pending) then return end +    nvim('command', "let g:job_opts.on_stderr  = function('OnEvent')")      nvim('command', "call jobstart('echo', g:job_opts)") -    eq({'notification', 'stdout', {0, {'', ''}}}, next_msg()) +    expect_twostreams({{'notification', 'stdout', {0, {'', ''}}}, +                       {'notification', 'stdout', {0, {''}}}}, +                      {{'notification', 'stderr', {0, {''}}}}) +      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -134,6 +141,7 @@ describe('jobs', function()      nvim('command', 'call jobsend(j, [123, "xyz", ""])')      eq({'notification', 'stdout', {0, {'123', 'xyz', ''}}}, next_msg())      nvim('command', "call jobstop(j)") +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -145,6 +153,7 @@ describe('jobs', function()      nvim('command', "let j = jobstart(['cat', '"..filename.."'], g:job_opts)")      eq({'notification', 'stdout', {0, {'abc\ndef', ''}}}, next_msg()) +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())      os.remove(filename) @@ -168,6 +177,7 @@ describe('jobs', function()      nvim('command', 'call jobsend(j, "abc\\nxyz")')      eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg())      nvim('command', "call jobstop(j)") +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -186,6 +196,7 @@ describe('jobs', function()      eq({'notification', 'stdout', {0, {'\n123\n', 'abc\nxyz\n', ''}}},        next_msg())      nvim('command', "call jobstop(j)") +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -196,6 +207,7 @@ describe('jobs', function()      eq({'notification', 'stdout', {0, {'some data', 'without\nfinal nl'}}},        next_msg())      nvim('command', "call jobstop(j)") +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -203,6 +215,7 @@ describe('jobs', function()      if helpers.pending_win32(pending) then return end  -- TODO: Need `cat`.      nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")      nvim('command', 'call jobclose(j, "stdin")') +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())    end) @@ -239,6 +252,7 @@ describe('jobs', function()      local pid = eval('jobpid(j)')      eq(0,os.execute('ps -p '..pid..' > /dev/null'))      nvim('command', 'call jobstop(j)') +    eq({'notification', 'stdout', {0, {''}}}, next_msg())      eq({'notification', 'exit', {0, 0}}, next_msg())      neq(0,os.execute('ps -p '..pid..' > /dev/null'))    end) @@ -270,6 +284,7 @@ describe('jobs', function()      nvim('command', [[call jobstart('echo "foo"', g:job_opts)]])      local data = {n = 5, s = 'str', l = {1}}      eq({'notification', 'stdout', {data, {'foo', ''}}}, next_msg()) +    eq({'notification', 'stdout', {data, {''}}}, next_msg())      eq({'notification', 'exit', {data, 0}}, next_msg())    end) @@ -283,7 +298,6 @@ describe('jobs', function()    it('can omit data callbacks', function()      nvim('command', 'unlet g:job_opts.on_stdout') -    nvim('command', 'unlet g:job_opts.on_stderr')      nvim('command', 'let g:job_opts.user = 5')      nvim('command', [[call jobstart('echo "foo"', g:job_opts)]])      eq({'notification', 'exit', {5, 0}}, next_msg()) @@ -294,11 +308,13 @@ describe('jobs', function()      nvim('command', 'let g:job_opts.user = 5')      nvim('command', [[call jobstart('echo "foo"', g:job_opts)]])      eq({'notification', 'stdout', {5, {'foo', ''}}}, next_msg()) +    eq({'notification', 'stdout', {5, {''}}}, next_msg())    end)    it('will pass return code with the exit event', function()      nvim('command', 'let g:job_opts.user = 5')      nvim('command', "call jobstart('exit 55', g:job_opts)") +    eq({'notification', 'stdout', {5, {''}}}, next_msg())      eq({'notification', 'exit', {5, 55}}, next_msg())    end) @@ -341,6 +357,14 @@ describe('jobs', function()    end)    it('requires funcrefs for script-local (s:) functions', function() +    local screen = Screen.new(60, 5) +    screen:attach() +    screen:set_default_attr_ids({ +      [1] = {bold = true, foreground = Screen.colors.Blue1}, +      [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, +      [3] = {bold = true, foreground = Screen.colors.SeaGreen4} +    }) +      -- Pass job callback names _without_ `function(...)`.      source([[        function! s:OnEvent(id, data, event) dict @@ -350,14 +374,10 @@ describe('jobs', function()          \ 'on_stdout': 's:OnEvent',          \ 'on_stderr': 's:OnEvent',          \ 'on_exit':   's:OnEvent', -        \ 'user': 2349          \ })      ]]) -    -- The behavior is asynchronous, retry until a time limit. -    helpers.retry(nil, 10000, function() -      eq("E120:", string.match(eval("v:errmsg"), "E%d*:")) -    end) +    screen:expect("{2:E120: Using <SID> not in a script context: s:OnEvent}",nil,nil,nil,true)    end)    it('does not repeat output with slow output handlers', function() @@ -376,7 +396,7 @@ describe('jobs', function()        call jobwait([jobstart(cmd, d)])        call rpcnotify(g:channel, 'data', d.data)      ]]) -    eq({'notification', 'data', {{{'1', ''}, {'2', ''}, {'3', ''}, {'4', ''}, {'5', ''}}}}, next_msg()) +    eq({'notification', 'data', {{{'1', ''}, {'2', ''}, {'3', ''}, {'4', ''}, {'5', ''}, {''}}}}, next_msg())    end)    it('jobstart() works with partial functions', function() @@ -497,7 +517,8 @@ describe('jobs', function()          elseif self.state == 2            let self.state = 3            call jobsend(a:id, "line3\n") -        else +        elseif self.state == 3 +          let self.state = 4            call rpcnotify(g:channel, 'w', printf('job %d closed', self.counter))            call jobclose(a:id, 'stdin')          endif @@ -552,6 +573,7 @@ describe('jobs', function()    -- FIXME need to wait until jobsend succeeds before calling jobstop    pending('will only emit the "exit" event after "stdout" and "stderr"', function() +    nvim('command', "let g:job_opts.on_stderr  = function('s:OnEvent')")      nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")      local jobid = nvim('eval', 'j')      nvim('eval', 'jobsend(j, "abcdef")') @@ -638,7 +660,7 @@ describe('jobs', function()        -- there won't be any more messages, and the test would hang.        helpers.sleep(100)        local err = exc_exec('call jobpid(j)') -      eq('Vim(call):E900: Invalid job id', err) +      eq('Vim(call):E900: Invalid channel id', err)        -- cleanup        eq(other_pid, eval('jobpid(' .. other_jobid .. ')')) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 848f1ef477..272d2045c9 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -100,6 +100,22 @@ local function next_message()    return session:next_message()  end +local function expect_twostreams(msgs1, msgs2) +  local pos1, pos2 = 1, 1 +  while pos1 <= #msgs1 or pos2 <= #msgs2 do +    local msg = next_message() +    if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then +      pos1 = pos1 + 1 +    elseif pos2 <= #msgs2 then +      eq(msgs2[pos2], msg) +      pos2 = pos2 + 1 +    else +      -- already failed, but show the right error message +      eq(msgs1[pos1], msg) +    end +  end +end +  local function call_and_stop_on_error(...)    local status, result = copcall(...)  -- luacheck: ignore    if not status then @@ -618,6 +634,33 @@ local function alter_slashes(obj)    end  end +local function hexdump(str) +  local len = string.len( str ) +  local dump = "" +  local hex = "" +  local asc = "" + +  for i = 1, len do +    if 1 == i % 8 then +      dump = dump .. hex .. asc .. "\n" +      hex = string.format( "%04x: ", i - 1 ) +      asc = "" +    end + +    local ord = string.byte( str, i ) +    hex = hex .. string.format( "%02x ", ord ) +    if ord >= 32 and ord <= 126 then +      asc = asc .. string.char( ord ) +    else +      asc = asc .. "." +    end +  end + +  return dump .. hex +  .. string.rep( "   ", 8 - len % 8 ) .. asc + +end +  local module = {    prepend_argv = prepend_argv,    clear = clear, @@ -636,6 +679,7 @@ local module = {    command = nvim_command,    request = request,    next_message = next_message, +  expect_twostreams = expect_twostreams,    run = run,    stop = stop,    eq = eq, @@ -687,6 +731,7 @@ local module = {    get_pathsep = get_pathsep,    missing_provider = missing_provider,    alter_slashes = alter_slashes, +  hexdump = hexdump,  }  return function(after_each) diff --git a/test/helpers.lua b/test/helpers.lua index 260f10002e..123dd111ee 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -225,21 +225,8 @@ local function check_cores(app, force)      local esigns = ('='):rep(len / 2)      out:write(('\n%s Core file %s %s\n'):format(esigns, core, esigns))      out:flush() -    local pipe = io.popen( -        db_cmd:gsub('%$_NVIM_TEST_APP', app):gsub('%$_NVIM_TEST_CORE', core) -        .. ' 2>&1', 'r') -    if pipe then -      local bt = pipe:read('*a') -      if bt then -        out:write(bt) -        out:write('\n') -      else -        out:write('Failed to read from the pipe\n') -      end -    else -      out:write('Failed to create pipe\n') -    end -    out:flush() +    os.execute(db_cmd:gsub('%$_NVIM_TEST_APP', app):gsub('%$_NVIM_TEST_CORE', core) .. ' 2>&1') +    out:write('\n')      found_cores = found_cores + 1      os.remove(core)    end  | 
