diff options
author | James McCoy <jamessan@jamessan.com> | 2018-03-28 21:52:06 -0400 |
---|---|---|
committer | James McCoy <jamessan@jamessan.com> | 2018-03-28 21:54:39 -0400 |
commit | 79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1 (patch) | |
tree | 4e0589d75801f3ff6a9678f84f5009102766661e /test/functional/core/job_spec.lua | |
parent | 4403864da3c48412595d439f36458d1e6ccfc49f (diff) | |
parent | 3f3de9b1a95d273463a87516365510dbffcaf3d2 (diff) | |
download | rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.tar.gz rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.tar.bz2 rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.zip |
Merge branch 'master' into yagebu/option-fixes
Diffstat (limited to 'test/functional/core/job_spec.lua')
-rw-r--r-- | test/functional/core/job_spec.lua | 198 |
1 files changed, 154 insertions, 44 deletions
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index b98169b067..6d4cadbdc8 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -2,13 +2,21 @@ local helpers = require('test.functional.helpers')(after_each) local clear, eq, eval, exc_exec, feed_command, feed, insert, neq, next_msg, nvim, nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear, helpers.eq, helpers.eval, helpers.exc_exec, helpers.feed_command, helpers.feed, - helpers.insert, helpers.neq, helpers.next_message, helpers.nvim, + helpers.insert, helpers.neq, helpers.next_msg, helpers.nvim, helpers.nvim_dir, helpers.ok, helpers.source, helpers.write_file, helpers.mkdir, helpers.rmdir local command = helpers.command +local funcs = helpers.funcs +local retry = helpers.retry +local meths = helpers.meths +local NIL = helpers.NIL local wait = helpers.wait local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep +local pathroot = helpers.pathroot +local nvim_set = helpers.nvim_set +local expect_twostreams = helpers.expect_twostreams +local expect_msg_seq = helpers.expect_msg_seq local Screen = require('test.functional.ui.screen') describe('jobs', function() @@ -28,15 +36,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 \ } ]]) @@ -50,18 +57,20 @@ 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) it('changes to given / directory', function() nvim('command', "let g:job_opts.cwd = '/'") if iswin() then - nvim('command', "let j = jobstart('pwd|%{$_.Path}', g:job_opts)") + nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end eq({'notification', 'stdout', - {0, {(iswin() and [[C:\]] or '/'), ''}}}, next_msg()) + {0, {pathroot(), ''}}}, next_msg()) + eq({'notification', 'stdout', {0, {''}}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg()) end) @@ -70,12 +79,22 @@ describe('jobs', function() mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then - nvim('command', "let j = jobstart('pwd|%{$_.Path}', g:job_opts)") + nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end - eq({'notification', 'stdout', {0, {dir, ''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {0, {dir, ''} } }, + {'notification', 'stdout', {0, {''} } }, + {'notification', 'exit', {0, 0} } + }, + -- Alternative sequence: + { {'notification', 'stdout', {0, {dir} } }, + {'notification', 'stdout', {0, {'', ''} } }, + {'notification', 'stdout', {0, {''} } }, + {'notification', 'exit', {0, 0} } + } + ) rmdir(dir) end) @@ -100,13 +119,13 @@ describe('jobs', function() end) it('returns -1 when target is not executable #5465', function() - if helpers.pending_win32(pending) then return end local function new_job() return eval([[jobstart('')]]) end local executable_jobid = new_job() - local nonexecutable_jobid = eval( - "jobstart(['./test/functional/fixtures/non_executable.txt'])") + local nonexecutable_jobid = eval("jobstart(['"..(iswin() + and './test/functional/fixtures' + or './test/functional/fixtures/non_executable.txt').."'])") eq(-1, nonexecutable_jobid) -- Should _not_ throw an error. eq("", eval("v:errmsg")) @@ -117,8 +136,11 @@ 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', "call jobstart('echo', g:job_opts)") - eq({'notification', 'stdout', {0, {'', ''}}}, next_msg()) + nvim('command', "let g:job_opts.on_stderr = function('OnEvent')") + nvim('command', [[call jobstart('echo ""', g:job_opts)]]) + expect_twostreams({{'notification', 'stdout', {0, {'', ''}}}, + {'notification', 'stdout', {0, {''}}}}, + {{'notification', 'stderr', {0, {''}}}}) eq({'notification', 'exit', {0, 0}}, next_msg()) end) @@ -133,6 +155,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) @@ -144,6 +167,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) @@ -167,6 +191,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) @@ -185,6 +210,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) @@ -195,6 +221,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) @@ -202,6 +229,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) @@ -228,7 +256,6 @@ describe('jobs', function() end) it('will not leak memory if we leave a job running', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. nvim('command', "call jobstart(['cat', '-'], g:job_opts)") end) @@ -238,6 +265,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) @@ -268,21 +296,21 @@ describe('jobs', function() nvim('command', 'let g:job_opts.user = {"n": 5, "s": "str", "l": [1]}') nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) local data = {n = 5, s = 'str', l = {1}} - eq({'notification', 'stdout', {data, {'foo', ''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {data, {'foo', ''}}}, + {'notification', 'stdout', {data, {''}}}, + }, + -- Alternative sequence: + { {'notification', 'stdout', {data, {'foo'}}}, + {'notification', 'stdout', {data, {'', ''}}}, + {'notification', 'stdout', {data, {''}}}, + } + ) eq({'notification', 'exit', {data, 0}}, next_msg()) end) - it('can omit options', function() - if helpers.pending_win32(pending) then return end - neq(0, nvim('eval', 'delete(".Xtestjob")')) - nvim('command', "call jobstart(['touch', '.Xtestjob'])") - nvim('command', "sleep 100m") - eq(0, nvim('eval', 'delete(".Xtestjob")')) - end) - 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()) @@ -292,12 +320,22 @@ describe('jobs', function() nvim('command', 'unlet g:job_opts.on_exit') nvim('command', 'let g:job_opts.user = 5') nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) - eq({'notification', 'stdout', {5, {'foo', ''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {5, {'foo', ''} } }, + {'notification', 'stdout', {5, {''} } }, + }, + -- Alternative sequence: + { {'notification', 'stdout', {5, {'foo'} } }, + {'notification', 'stdout', {5, {'', ''} } }, + {'notification', 'stdout', {5, {''} } }, + } + ) 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) @@ -313,7 +351,6 @@ describe('jobs', function() end) it('can redefine callbacks being used by a job', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. local screen = Screen.new() screen:attach() screen:set_default_attr_ids({ @@ -328,7 +365,7 @@ describe('jobs', function() \ 'on_stderr': function('g:JobHandler'), \ 'on_exit': function('g:JobHandler') \ } - let job = jobstart('cat -', g:callbacks) + let job = jobstart(['cat', '-'], g:callbacks) ]]) wait() source([[ @@ -340,6 +377,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 @@ -349,14 +394,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() @@ -375,7 +416,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() @@ -389,7 +430,14 @@ describe('jobs', function() let g:job_opts = {'on_stdout': Callback} call jobstart('echo "some text"', g:job_opts) ]]) - eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + expect_msg_seq( + { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, + }, + -- Alternative sequence: + { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, + {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + } + ) end) it('jobstart() works with closures', function() @@ -402,7 +450,14 @@ describe('jobs', function() let g:job_opts = {'on_stdout': MkFun()} call jobstart('echo "some text"', g:job_opts) ]]) - eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + expect_msg_seq( + { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, + }, + -- Alternative sequence: + { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, + {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + } + ) end) it('jobstart() works when closure passed directly to `jobstart`', function() @@ -410,7 +465,14 @@ describe('jobs', function() let g:job_opts = {'on_stdout': {id, data, event -> rpcnotify(g:channel, '1', 'foo', 'bar', Normalize(data), event)}} call jobstart('echo "some text"', g:job_opts) ]]) - eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + expect_msg_seq( + { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, + }, + -- Alternative sequence: + { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, + {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + } + ) end) describe('jobwait', function() @@ -469,7 +531,7 @@ describe('jobs', function() eq({'notification', 'wait', {{-3, 5}}}, next_msg()) end) - it('will return -2 when interrupted', function() + it('will return -2 when interrupted without timeout', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. 'jobwait([jobstart("sleep 10; exit 55")]))') @@ -478,6 +540,15 @@ describe('jobs', function() eq({'notification', 'wait', {{-2}}}, next_msg()) end) + it('will return -2 when interrupted with timeout', function() + feed_command('call rpcnotify(g:channel, "ready") | '.. + 'call rpcnotify(g:channel, "wait", '.. + 'jobwait([jobstart("sleep 10; exit 55")], 10000))') + eq({'notification', 'ready', {}}, next_msg()) + feed('<c-c>') + eq({'notification', 'wait', {{-2}}}, next_msg()) + end) + it('can be called recursively', function() if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. source([[ @@ -496,7 +567,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 @@ -551,6 +623,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")') @@ -567,6 +640,42 @@ describe('jobs', function() ok(string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") ~= nil) end) + it('jobstop() kills entire process tree #6530', function() + command('set shell& shellcmdflag& shellquote& shellpipe& shellredir& shellxquote&') + + -- XXX: Using `nvim` isn't a good test, it reaps its children on exit. + -- local c = 'call jobstart([v:progpath, "-u", "NONE", "-i", "NONE", "--headless"])' + -- local j = eval("jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '--headless', '-c', '" + -- ..c.."', '-c', '"..c.."'])") + + -- Create child with several descendants. + local j = (iswin() + and eval([=[jobstart('start /b cmd /c "ping 127.0.0.1 -n 1 -w 30000 > NUL"]=] + ..[=[ & start /b cmd /c "ping 127.0.0.1 -n 1 -w 40000 > NUL"]=] + ..[=[ & start /b cmd /c "ping 127.0.0.1 -n 1 -w 50000 > NUL"')]=]) + or eval("jobstart('sleep 30 | sleep 30 | sleep 30')")) + local ppid = funcs.jobpid(j) + local children + retry(nil, nil, function() + children = meths.get_proc_children(ppid) + eq(3, #children) + end) + -- Assert that nvim_get_proc() sees the children. + for _, child_pid in ipairs(children) do + local info = meths.get_proc(child_pid) + -- eq((iswin() and 'nvim.exe' or 'nvim'), info.name) + eq(ppid, info.ppid) + end + -- Kill the root of the tree. + funcs.jobstop(j) + -- Assert that the children were killed. + retry(nil, nil, function() + for _, child_pid in ipairs(children) do + eq(NIL, meths.get_proc(child_pid)) + end + end) + end) + describe('running tty-test program', function() if helpers.pending_win32(pending) then return end local function next_chunk() @@ -637,7 +746,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 .. ')')) @@ -669,18 +778,19 @@ describe("pty process teardown", function() if helpers.pending_win32(pending) then return end -- Use a nested nvim (in :term) to test without --headless. feed_command(":terminal '"..helpers.nvim_prog + .."' -u NONE -i NONE --cmd '"..nvim_set.."' " -- Use :term again in the _nested_ nvim to get a PTY process. -- Use `sleep` to simulate a long-running child of the PTY. - .."' +terminal +'!(sleep 300 &)' +qa") + .."+terminal +'!(sleep 300 &)' +qa") -- Exiting should terminate all descendants (PTY, its children, ...). screen:expect([[ - | + ^ | [Process exited 0] | | | | - -- TERMINAL -- | + | ]]) end) end) |