diff options
Diffstat (limited to 'test/functional/core/job_spec.lua')
-rw-r--r-- | test/functional/core/job_spec.lua | 778 |
1 files changed, 431 insertions, 347 deletions
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 038368c387..13f5f9a5e1 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -1,17 +1,28 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, eq, eval, exc_exec, feed_command, feed, insert, neq, next_msg, nvim, - testprg, 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_msg, helpers.nvim, - helpers.testprg, helpers.ok, helpers.source, - helpers.write_file, helpers.mkdir, helpers.rmdir +local thelpers = require('test.functional.terminal.helpers') + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local exc_exec = helpers.exc_exec +local feed_command = helpers.feed_command +local feed = helpers.feed +local insert = helpers.insert +local neq = helpers.neq +local next_msg = helpers.next_msg +local testprg = helpers.testprg +local ok = helpers.ok +local source = helpers.source +local write_file = helpers.write_file +local mkdir = helpers.mkdir +local rmdir = helpers.rmdir local assert_alive = helpers.assert_alive local command = helpers.command -local funcs = helpers.funcs +local fn = helpers.fn local os_kill = helpers.os_kill local retry = helpers.retry -local meths = helpers.meths -local NIL = helpers.NIL +local api = helpers.api +local NIL = vim.NIL local poke_eventloop = helpers.poke_eventloop local get_pathsep = helpers.get_pathsep local pathroot = helpers.pathroot @@ -31,8 +42,8 @@ describe('jobs', function() before_each(function() clear() - channel = nvim('get_api_info')[1] - nvim('set_var', 'channel', channel) + channel = api.nvim_get_chan_info(0).id + api.nvim_set_var('channel', channel) source([[ function! Normalize(data) abort " Windows: remove ^M and term escape sequences @@ -54,66 +65,60 @@ describe('jobs', function() end) it('must specify env option as a dict', function() - command("let g:job_opts.env = v:true") + command('let g:job_opts.env = v:true') local _, err = pcall(function() if is_os('win') then - nvim('command', "let j = jobstart('set', g:job_opts)") + command("let j = jobstart('set', g:job_opts)") else - nvim('command', "let j = jobstart('env', g:job_opts)") + command("let j = jobstart('env', g:job_opts)") end end) - ok(string.find(err, "E475: Invalid argument: env") ~= nil) + ok(string.find(err, 'E475: Invalid argument: env') ~= nil) end) it('append environment #env', function() - nvim('command', "let $VAR = 'abc'") - nvim('command', "let $TOTO = 'goodbye world'") - nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") + command("let $VAR = 'abc'") + command("let $TOTO = 'goodbye world'") + command("let g:job_opts.env = {'TOTO': 'hello world'}") if is_os('win') then - nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) + command([[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) else - nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) + command([[call jobstart('echo $TOTO $VAR', g:job_opts)]]) end - expect_msg_seq( - { - {'notification', 'stdout', {0, {'hello world abc'}}}, - {'notification', 'stdout', {0, {'', ''}}}, - }, - { - {'notification', 'stdout', {0, {'hello world abc', ''}}}, - {'notification', 'stdout', {0, {''}}} - } - ) + expect_msg_seq({ + { 'notification', 'stdout', { 0, { 'hello world abc' } } }, + { 'notification', 'stdout', { 0, { '', '' } } }, + }, { + { 'notification', 'stdout', { 0, { 'hello world abc', '' } } }, + { 'notification', 'stdout', { 0, { '' } } }, + }) end) it('append environment with pty #env', function() - nvim('command', "let $VAR = 'abc'") - nvim('command', "let $TOTO = 'goodbye world'") - nvim('command', "let g:job_opts.pty = v:true") - nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") + command("let $VAR = 'abc'") + command("let $TOTO = 'goodbye world'") + command('let g:job_opts.pty = v:true') + command("let g:job_opts.env = {'TOTO': 'hello world'}") if is_os('win') then - nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) + command([[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) else - nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) + command([[call jobstart('echo $TOTO $VAR', g:job_opts)]]) end - expect_msg_seq( - { - {'notification', 'stdout', {0, {'hello world abc'}}}, - {'notification', 'stdout', {0, {'', ''}}}, - }, - { - {'notification', 'stdout', {0, {'hello world abc', ''}}}, - {'notification', 'stdout', {0, {''}}} - } - ) + expect_msg_seq({ + { 'notification', 'stdout', { 0, { 'hello world abc' } } }, + { 'notification', 'stdout', { 0, { '', '' } } }, + }, { + { 'notification', 'stdout', { 0, { 'hello world abc', '' } } }, + { 'notification', 'stdout', { 0, { '' } } }, + }) end) it('replace environment #env', function() - nvim('command', "let $VAR = 'abc'") - nvim('command', "let $TOTO = 'goodbye world'") - nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") - nvim('command', "let g:job_opts.clear_env = 1") + command("let $VAR = 'abc'") + command("let $TOTO = 'goodbye world'") + command("let g:job_opts.env = {'TOTO': 'hello world'}") + command('let g:job_opts.clear_env = 1') -- libuv ensures that certain "required" environment variables are -- preserved if the user doesn't provide them in a custom environment @@ -123,39 +128,39 @@ describe('jobs', function() -- Rather than expecting a completely empty environment, ensure that $VAR -- is *not* in the environment but $TOTO is. if is_os('win') then - nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) + command([[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) expect_msg_seq({ - {'notification', 'stdout', {0, {'hello world %VAR%', ''}}} + { 'notification', 'stdout', { 0, { 'hello world %VAR%', '' } } }, }) else - nvim('command', "set shell=/bin/sh") - nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) + command('set shell=/bin/sh') + command([[call jobstart('echo $TOTO $VAR', g:job_opts)]]) expect_msg_seq({ - {'notification', 'stdout', {0, {'hello world', ''}}} + { 'notification', 'stdout', { 0, { 'hello world', '' } } }, }) end end) it('handles case-insensitively matching #env vars', function() - nvim('command', "let $TOTO = 'abc'") + command("let $TOTO = 'abc'") -- Since $Toto is being set in the job, it should take precedence over the -- global $TOTO on Windows - nvim('command', "let g:job_opts = {'env': {'Toto': 'def'}, 'stdout_buffered': v:true}") + command("let g:job_opts = {'env': {'Toto': 'def'}, 'stdout_buffered': v:true}") if is_os('win') then - nvim('command', [[let j = jobstart('set | find /I "toto="', g:job_opts)]]) + command([[let j = jobstart('set | find /I "toto="', g:job_opts)]]) else - nvim('command', [[let j = jobstart('env | grep -i toto=', g:job_opts)]]) + command([[let j = jobstart('env | grep -i toto=', g:job_opts)]]) end - nvim('command', "call jobwait([j])") - nvim('command', "let g:output = Normalize(g:job_opts.stdout)") + command('call jobwait([j])') + command('let g:output = Normalize(g:job_opts.stdout)') local actual = eval('g:output') local expected if is_os('win') then -- Toto is normalized to TOTO so we can detect duplicates, and because -- Windows doesn't care about case - expected = {'TOTO=def', ''} + expected = { 'TOTO=def', '' } else - expected = {'TOTO=abc', 'Toto=def', ''} + expected = { 'TOTO=abc', 'Toto=def', '' } end table.sort(actual) table.sort(expected) @@ -163,49 +168,50 @@ describe('jobs', function() end) it('uses &shell and &shellcmdflag if passed a string', function() - nvim('command', "let $VAR = 'abc'") + command("let $VAR = 'abc'") if is_os('win') then - nvim('command', "let j = jobstart('echo %VAR%', g:job_opts)") + command("let j = jobstart('echo %VAR%', g:job_opts)") else - nvim('command', "let j = jobstart('echo $VAR', g:job_opts)") + 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()) + 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 = '/'") + command("let g:job_opts.cwd = '/'") if is_os('win') then - nvim('command', "let j = jobstart('cd', g:job_opts)") + command("let j = jobstart('cd', g:job_opts)") else - nvim('command', "let j = jobstart('pwd', g:job_opts)") + command("let j = jobstart('pwd', g:job_opts)") end - eq({'notification', 'stdout', - {0, {pathroot(), ''}}}, next_msg()) - eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) + eq({ 'notification', 'stdout', { 0, { pathroot(), '' } } }, next_msg()) + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 0 } }, next_msg()) end) it('changes to given `cwd` directory', function() - local dir = eval("resolve(tempname())"):gsub("/", get_pathsep()) + local dir = eval('resolve(tempname())'):gsub('/', get_pathsep()) mkdir(dir) - nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") + command("let g:job_opts.cwd = '" .. dir .. "'") if is_os('win') then - nvim('command', "let j = jobstart('cd', g:job_opts)") + command("let j = jobstart('cd', g:job_opts)") else - nvim('command', "let j = jobstart('pwd', g:job_opts)") + command("let j = jobstart('pwd', g:job_opts)") end expect_msg_seq( - { {'notification', 'stdout', {0, {dir, ''} } }, - {'notification', 'stdout', {0, {''} } }, - {'notification', 'exit', {0, 0} } + { + { '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} } + { + { 'notification', 'stdout', { 0, { dir } } }, + { 'notification', 'stdout', { 0, { '', '' } } }, + { 'notification', 'stdout', { 0, { '' } } }, + { 'notification', 'exit', { 0, 0 } }, } ) rmdir(dir) @@ -214,14 +220,14 @@ describe('jobs', function() it('fails to change to invalid `cwd`', function() local dir = eval('resolve(tempname())."-bogus"') local _, err = pcall(function() - nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") + command("let g:job_opts.cwd = '" .. dir .. "'") if is_os('win') then - nvim('command', "let j = jobstart('cd', g:job_opts)") + command("let j = jobstart('cd', g:job_opts)") else - nvim('command', "let j = jobstart('pwd', g:job_opts)") + command("let j = jobstart('pwd', g:job_opts)") end end) - ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil) + ok(string.find(err, 'E475: Invalid argument: expected valid directory$') ~= nil) end) it('error on non-executable `cwd`', function() @@ -229,17 +235,19 @@ describe('jobs', function() local dir = 'Xtest_not_executable_dir' mkdir(dir) - funcs.setfperm(dir, 'rw-------') - matches('^Vim%(call%):E903: Process failed to start: permission denied: .*', - pcall_err(nvim, 'command', "call jobstart(['pwd'], {'cwd': '"..dir.."'})")) + fn.setfperm(dir, 'rw-------') + matches( + '^Vim%(call%):E903: Process failed to start: permission denied: .*', + pcall_err(command, "call jobstart(['pwd'], {'cwd': '" .. dir .. "'})") + ) rmdir(dir) end) it('returns 0 when it fails to start', function() - eq("", eval("v:errmsg")) - feed_command("let g:test_jobid = jobstart([])") - eq(0, eval("g:test_jobid")) - eq("E474:", string.match(eval("v:errmsg"), "E%d*:")) + eq('', eval('v:errmsg')) + feed_command('let g:test_jobid = jobstart([])') + eq(0, eval('g:test_jobid')) + eq('E474:', string.match(eval('v:errmsg'), 'E%d*:')) end) it('returns -1 when target is not executable #5465', function() @@ -248,124 +256,131 @@ describe('jobs', function() end local executable_jobid = new_job() - local exe = is_os('win') and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt' - eq("Vim:E475: Invalid value for argument cmd: '"..exe.."' is not executable", - pcall_err(eval, "jobstart(['"..exe.."'])")) - eq("", eval("v:errmsg")) + local exe = is_os('win') and './test/functional/fixtures' + or './test/functional/fixtures/non_executable.txt' + eq( + "Vim:E475: Invalid value for argument cmd: '" .. exe .. "' is not executable", + pcall_err(eval, "jobstart(['" .. exe .. "'])") + ) + eq('', eval('v:errmsg')) -- Non-executable job should not increment the job ids. #5465 eq(executable_jobid + 1, new_job()) end) it('invokes callbacks when the job writes and exits', function() - nvim('command', "let g:job_opts.on_stderr = function('OnEvent')") - nvim('command', [[call jobstart(has('win32') ? 'echo:' : 'echo', g:job_opts)]]) - expect_twostreams({{'notification', 'stdout', {0, {'', ''}}}, - {'notification', 'stdout', {0, {''}}}}, - {{'notification', 'stderr', {0, {''}}}}) - eq({'notification', 'exit', {0, 0}}, next_msg()) + command("let g:job_opts.on_stderr = function('OnEvent')") + command([[call jobstart(has('win32') ? 'echo:' : 'echo', g:job_opts)]]) + expect_twostreams({ + { 'notification', 'stdout', { 0, { '', '' } } }, + { 'notification', 'stdout', { 0, { '' } } }, + }, { { 'notification', 'stderr', { 0, { '' } } } }) + eq({ 'notification', 'exit', { 0, 0 } }, next_msg()) end) it('interactive commands', function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + command("let j = jobstart(['cat', '-'], g:job_opts)") neq(0, eval('j')) - nvim('command', 'call jobsend(j, "abc\\n")') - eq({'notification', 'stdout', {0, {'abc', ''}}}, next_msg()) - nvim('command', 'call jobsend(j, "123\\nxyz\\n")') + command('call jobsend(j, "abc\\n")') + eq({ 'notification', 'stdout', { 0, { 'abc', '' } } }, next_msg()) + command('call jobsend(j, "123\\nxyz\\n")') expect_msg_seq( - { {'notification', 'stdout', {0, {'123', 'xyz', ''}}} - }, + { { 'notification', 'stdout', { 0, { '123', 'xyz', '' } } } }, -- Alternative sequence: - { {'notification', 'stdout', {0, {'123', ''}}}, - {'notification', 'stdout', {0, {'xyz', ''}}} + { + { 'notification', 'stdout', { 0, { '123', '' } } }, + { 'notification', 'stdout', { 0, { 'xyz', '' } } }, } ) - nvim('command', 'call jobsend(j, [123, "xyz", ""])') + command('call jobsend(j, [123, "xyz", ""])') expect_msg_seq( - { {'notification', 'stdout', {0, {'123', 'xyz', ''}}} - }, + { { 'notification', 'stdout', { 0, { '123', 'xyz', '' } } } }, -- Alternative sequence: - { {'notification', 'stdout', {0, {'123', ''}}}, - {'notification', 'stdout', {0, {'xyz', ''}}} + { + { 'notification', 'stdout', { 0, { '123', '' } } }, + { 'notification', 'stdout', { 0, { 'xyz', '' } } }, } ) - nvim('command', "call jobstop(j)") - eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 143}}, next_msg()) + command('call jobstop(j)') + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) it('preserves NULs', function() -- Make a file with NULs in it. local filename = helpers.tmpname() - write_file(filename, "abc\0def\n") + write_file(filename, 'abc\0def\n') - 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()) + 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) -- jobsend() preserves NULs. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - nvim('command', [[call jobsend(j, ["123\n456",""])]]) - eq({'notification', 'stdout', {0, {'123\n456', ''}}}, next_msg()) - nvim('command', "call jobstop(j)") + command("let j = jobstart(['cat', '-'], g:job_opts)") + command([[call jobsend(j, ["123\n456",""])]]) + eq({ 'notification', 'stdout', { 0, { '123\n456', '' } } }, next_msg()) + command('call jobstop(j)') end) - it("emits partial lines (does NOT buffer data lacking newlines)", function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - 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, 143}}, next_msg()) + it('emits partial lines (does NOT buffer data lacking newlines)', function() + command("let j = jobstart(['cat', '-'], g:job_opts)") + command('call jobsend(j, "abc\\nxyz")') + eq({ 'notification', 'stdout', { 0, { 'abc', 'xyz' } } }, next_msg()) + command('call jobstop(j)') + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) it('preserves newlines', function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - nvim('command', 'call jobsend(j, "a\\n\\nc\\n\\n\\n\\nb\\n\\n")') - eq({'notification', 'stdout', - {0, {'a', '', 'c', '', '', '', 'b', '', ''}}}, next_msg()) + command("let j = jobstart(['cat', '-'], g:job_opts)") + command('call jobsend(j, "a\\n\\nc\\n\\n\\n\\nb\\n\\n")') + eq({ 'notification', 'stdout', { 0, { 'a', '', 'c', '', '', '', 'b', '', '' } } }, next_msg()) end) it('preserves NULs', function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - nvim('command', 'call jobsend(j, ["\n123\n", "abc\\nxyz\n", ""])') - 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, 143}}, next_msg()) + command("let j = jobstart(['cat', '-'], g:job_opts)") + command('call jobsend(j, ["\n123\n", "abc\\nxyz\n", ""])') + eq({ 'notification', 'stdout', { 0, { '\n123\n', 'abc\nxyz\n', '' } } }, next_msg()) + command('call jobstop(j)') + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) it('avoids sending final newline', function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - nvim('command', 'call jobsend(j, ["some data", "without\nfinal nl"])') - 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, 143}}, next_msg()) + command("let j = jobstart(['cat', '-'], g:job_opts)") + command('call jobsend(j, ["some data", "without\nfinal nl"])') + eq({ 'notification', 'stdout', { 0, { 'some data', 'without\nfinal nl' } } }, next_msg()) + command('call jobstop(j)') + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) it('closes the job streams with jobclose', function() - 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()) + command("let j = jobstart(['cat', '-'], g:job_opts)") + command('call jobclose(j, "stdin")') + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 0 } }, next_msg()) end) - it("disallows jobsend on a job that closed stdin", function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - nvim('command', 'call jobclose(j, "stdin")') - eq(false, pcall(function() - nvim('command', 'call jobsend(j, ["some data"])') - end)) + it('disallows jobsend on a job that closed stdin', function() + command("let j = jobstart(['cat', '-'], g:job_opts)") + command('call jobclose(j, "stdin")') + eq( + false, + pcall(function() + command('call jobsend(j, ["some data"])') + end) + ) command("let g:job_opts.stdin = 'null'") - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - eq(false, pcall(function() - nvim('command', 'call jobsend(j, ["some data"])') - end)) + command("let j = jobstart(['cat', '-'], g:job_opts)") + eq( + false, + pcall(function() + command('call jobsend(j, ["some data"])') + end) + ) end) it('disallows jobsend on a non-existent job', function() @@ -374,91 +389,99 @@ describe('jobs', function() end) it('jobstop twice on the stopped or exited job return 0', function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + command("let j = jobstart(['cat', '-'], g:job_opts)") neq(0, eval('j')) - eq(1, eval("jobstop(j)")) - eq(0, eval("jobstop(j)")) + eq(1, eval('jobstop(j)')) + eq(0, eval('jobstop(j)')) end) it('will not leak memory if we leave a job running', function() - nvim('command', "call jobstart(['cat', '-'], g:job_opts)") + command("call jobstart(['cat', '-'], g:job_opts)") end) it('can get the pid value using getpid', function() - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + command("let j = jobstart(['cat', '-'], g:job_opts)") local pid = eval('jobpid(j)') - neq(NIL, meths.get_proc(pid)) - nvim('command', 'call jobstop(j)') - eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 143}}, next_msg()) - eq(NIL, meths.get_proc(pid)) + neq(NIL, api.nvim_get_proc(pid)) + command('call jobstop(j)') + eq({ 'notification', 'stdout', { 0, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) + eq(NIL, api.nvim_get_proc(pid)) end) - it("disposed on Nvim exit", function() + it('disposed on Nvim exit', function() -- use sleep, which doesn't die on stdin close - nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") + command( + "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)" + ) local pid = eval('jobpid(g:j)') - neq(NIL, meths.get_proc(pid)) + neq(NIL, api.nvim_get_proc(pid)) clear() - eq(NIL, meths.get_proc(pid)) + eq(NIL, api.nvim_get_proc(pid)) end) it('can survive the exit of nvim with "detach"', function() - nvim('command', 'let g:job_opts.detach = 1') - nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") + command('let g:job_opts.detach = 1') + command( + "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)" + ) local pid = eval('jobpid(g:j)') - neq(NIL, meths.get_proc(pid)) + neq(NIL, api.nvim_get_proc(pid)) clear() - neq(NIL, meths.get_proc(pid)) + neq(NIL, api.nvim_get_proc(pid)) -- clean up after ourselves eq(0, os_kill(pid)) end) it('can pass user data to the callback', 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}} + command('let g:job_opts.user = {"n": 5, "s": "str", "l": [1]}') + command([[call jobstart('echo foo', g:job_opts)]]) + local data = { n = 5, s = 'str', l = { 1 } } expect_msg_seq( - { {'notification', 'stdout', {data, {'foo', ''}}}, - {'notification', 'stdout', {data, {''}}}, + { + { 'notification', 'stdout', { data, { 'foo', '' } } }, + { 'notification', 'stdout', { data, { '' } } }, }, -- Alternative sequence: - { {'notification', 'stdout', {data, {'foo'}}}, - {'notification', 'stdout', {data, {'', ''}}}, - {'notification', 'stdout', {data, {''}}}, + { + { 'notification', 'stdout', { data, { 'foo' } } }, + { 'notification', 'stdout', { data, { '', '' } } }, + { 'notification', 'stdout', { data, { '' } } }, } ) - eq({'notification', 'exit', {data, 0}}, next_msg()) + eq({ 'notification', 'exit', { data, 0 } }, next_msg()) end) it('can omit data callbacks', function() - nvim('command', 'unlet g:job_opts.on_stdout') - nvim('command', 'let g:job_opts.user = 5') - nvim('command', [[call jobstart('echo foo', g:job_opts)]]) - eq({'notification', 'exit', {5, 0}}, next_msg()) + command('unlet g:job_opts.on_stdout') + command('let g:job_opts.user = 5') + command([[call jobstart('echo foo', g:job_opts)]]) + eq({ 'notification', 'exit', { 5, 0 } }, next_msg()) end) it('can omit exit callback', 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)]]) + command('unlet g:job_opts.on_exit') + command('let g:job_opts.user = 5') + command([[call jobstart('echo foo', g:job_opts)]]) expect_msg_seq( - { {'notification', 'stdout', {5, {'foo', ''} } }, - {'notification', 'stdout', {5, {''} } }, + { + { 'notification', 'stdout', { 5, { 'foo', '' } } }, + { 'notification', 'stdout', { 5, { '' } } }, }, -- Alternative sequence: - { {'notification', 'stdout', {5, {'foo'} } }, - {'notification', 'stdout', {5, {'', ''} } }, - {'notification', 'stdout', {5, {''} } }, + { + { '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()) + command('let g:job_opts.user = 5') + command("call jobstart('exit 55', g:job_opts)") + eq({ 'notification', 'stdout', { 5, { '' } } }, next_msg()) + eq({ 'notification', 'exit', { 5, 55 } }, next_msg()) end) it('can receive dictionary functions', function() @@ -469,14 +492,14 @@ describe('jobs', function() endfunction call jobstart('exit 45', g:dict) ]]) - eq({'notification', 'exit', {45, 10}}, next_msg()) + eq({ 'notification', 'exit', { 45, 10 } }, next_msg()) end) it('can redefine callbacks being used by a job', function() local screen = Screen.new() screen:attach() screen:set_default_attr_ids({ - [1] = {bold=true, foreground=Screen.colors.Blue}, + [1] = { bold = true, foreground = Screen.colors.Blue }, }) source([[ function! g:JobHandler(job_id, data, event) @@ -495,16 +518,16 @@ describe('jobs', function() endfunction ]]) - eq("", eval("v:errmsg")) + eq('', eval('v:errmsg')) 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} + [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(...)`. @@ -519,7 +542,7 @@ describe('jobs', function() \ }) ]]) - screen:expect{any="{2:E120: Using <SID> not in a script context: s:OnEvent}"} + screen:expect { any = '{2:E120: Using <SID> not in a script context: s:OnEvent}' } end) it('does not repeat output with slow output handlers', function() @@ -542,22 +565,22 @@ describe('jobs', function() call jobwait([g:id]) ]]) - local expected = {'1', '2', '3', '4', '5', ''} + local expected = { '1', '2', '3', '4', '5', '' } local chunks = eval('d.data') -- check nothing was received after exit, including EOF eq(eval('g:exit_data'), chunks) - local received = {''} + local received = { '' } for i, chunk in ipairs(chunks) do if i < #chunks then -- if chunks got joined, a spurious [''] callback was not sent - neq({''}, chunk) + neq({ '' }, chunk) else -- but EOF callback is still sent - eq({''}, chunk) + eq({ '' }, chunk) end - received[#received] = received[#received]..chunk[1] + received[#received] = received[#received] .. chunk[1] for j = 2, #chunk do - received[#received+1] = chunk[j] + received[#received + 1] = chunk[j] end end eq(expected, received) @@ -585,22 +608,22 @@ describe('jobs', function() call jobwait([g:id]) ]]) - local expected = {'1', '2', '3', '4', '5', ''} + local expected = { '1', '2', '3', '4', '5', '' } local chunks = eval('d.data') -- check nothing was received after exit, including EOF eq(eval('g:exit_data'), chunks) - local received = {''} + local received = { '' } for i, chunk in ipairs(chunks) do if i < #chunks then -- if chunks got joined, a spurious [''] callback was not sent - neq({''}, chunk) + neq({ '' }, chunk) else -- but EOF callback is still sent - eq({''}, chunk) + eq({ '' }, chunk) end - received[#received] = received[#received]..chunk[1] + received[#received] = received[#received] .. chunk[1] for j = 2, #chunk do - received[#received+1] = chunk[j] + received[#received + 1] = chunk[j] end end eq(expected, received) @@ -618,11 +641,11 @@ describe('jobs', function() call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( - { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, - }, + { { 'notification', '1', { 'foo', 'bar', { 'some text', '' }, 'stdout' } } }, -- Alternative sequence: - { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, - {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + { + { 'notification', '1', { 'foo', 'bar', { 'some text' }, 'stdout' } }, + { 'notification', '1', { 'foo', 'bar', { '', '' }, 'stdout' } }, } ) end) @@ -638,11 +661,11 @@ describe('jobs', function() call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( - { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, - }, + { { 'notification', '1', { 'foo', 'bar', { 'some text', '' }, 'stdout' } } }, -- Alternative sequence: - { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, - {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + { + { 'notification', '1', { 'foo', 'bar', { 'some text' }, 'stdout' } }, + { 'notification', '1', { 'foo', 'bar', { '', '' }, 'stdout' } }, } ) end) @@ -653,18 +676,19 @@ describe('jobs', function() call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( - { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, - }, + { { 'notification', '1', { 'foo', 'bar', { 'some text', '' }, 'stdout' } } }, -- Alternative sequence: - { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, - {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + { + { 'notification', '1', { 'foo', 'bar', { 'some text' }, 'stdout' } }, + { 'notification', '1', { 'foo', 'bar', { '', '' }, 'stdout' } }, } ) end) it('jobstart() environment: $NVIM, $NVIM_LISTEN_ADDRESS #11009', function() local function get_env_in_child_job(envname, env) - return exec_lua([[ + return exec_lua( + [[ local envname, env = ... local join = function(s) return vim.fn.join(s, '') end local stdout = {} @@ -676,12 +700,13 @@ describe('jobs', function() on_stderr = function(chan, data, name) stderr = data end, on_stdout = function(chan, data, name) stdout = data end, } - local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1',( '+echo "%s="..getenv("%s")'):format(envname, envname), '+qa!' }, opt) + local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1',('+echo "%s="..getenv("%s")'):format(envname, envname), '+qa!' }, opt) vim.fn.jobwait({ j1 }, 10000) return join({ join(stdout), join(stderr) }) ]], - envname, - env) + envname, + env + ) end local addr = eval('v:servername') @@ -689,14 +714,18 @@ describe('jobs', function() -- $NVIM is _not_ defined in the top-level Nvim process. eq('', eval('$NVIM')) -- jobstart() shares its v:servername with the child via $NVIM. - eq('NVIM='..addr, get_env_in_child_job('NVIM')) + eq('NVIM=' .. addr, get_env_in_child_job('NVIM')) -- $NVIM_LISTEN_ADDRESS is unset by server_init in the child. eq('NVIM_LISTEN_ADDRESS=v:null', get_env_in_child_job('NVIM_LISTEN_ADDRESS')) - eq('NVIM_LISTEN_ADDRESS=v:null', get_env_in_child_job('NVIM_LISTEN_ADDRESS', - { NVIM_LISTEN_ADDRESS='Xtest_jobstart_env' })) + eq( + 'NVIM_LISTEN_ADDRESS=v:null', + get_env_in_child_job('NVIM_LISTEN_ADDRESS', { NVIM_LISTEN_ADDRESS = 'Xtest_jobstart_env' }) + ) -- User can explicitly set $NVIM_LOG_FILE, $VIM, $VIMRUNTIME. - eq('NVIM_LOG_FILE=Xtest_jobstart_env', - get_env_in_child_job('NVIM_LOG_FILE', { NVIM_LOG_FILE='Xtest_jobstart_env' })) + eq( + 'NVIM_LOG_FILE=Xtest_jobstart_env', + get_env_in_child_job('NVIM_LOG_FILE', { NVIM_LOG_FILE = 'Xtest_jobstart_env' }) + ) os.remove('Xtest_jobstart_env') end) @@ -721,7 +750,7 @@ describe('jobs', function() \ jobstart('sleep 0.310; exit 7') \ ])) ]]) - eq({'notification', 'wait', {{4, 5, 6, 7}}}, next_msg()) + eq({ 'notification', 'wait', { { 4, 5, 6, 7 } } }, next_msg()) end) it('will run callbacks while waiting', function() @@ -751,8 +780,7 @@ describe('jobs', function() \ ]) call rpcnotify(g:channel, 'wait', sort(g:jobs), sort(g:exits)) ]]) - eq({'notification', 'wait', - {{3,4,5,6}, {3,4,5,6}}}, next_msg()) + eq({ 'notification', 'wait', { { 3, 4, 5, 6 }, { 3, 4, 5, 6 } } }, next_msg()) end) it('will return status codes in the order of passed ids', function() @@ -769,7 +797,7 @@ describe('jobs', function() \ jobstart('sleep 0.010; exit 7') \ ])) ]]) - eq({'notification', 'wait', {{4, 5, 6, 7}}}, next_msg()) + eq({ 'notification', 'wait', { { 4, 5, 6, 7 } } }, next_msg()) end) it('will return -3 for invalid job ids', function() @@ -779,29 +807,33 @@ describe('jobs', function() \ jobstart((has('win32') ? 'Start-Sleep -Milliseconds 100' : 'sleep 0.01').'; exit 5'), \ ])) ]]) - eq({'notification', 'wait', {{-3, 5}}}, next_msg()) + eq({ 'notification', 'wait', { { -3, 5 } } }, next_msg()) end) it('will return -2 when interrupted without timeout', function() - feed_command('call rpcnotify(g:channel, "ready") | '.. - 'call rpcnotify(g:channel, "wait", '.. - 'jobwait([jobstart("'.. - (is_os('win') and 'Start-Sleep 10' or 'sleep 10').. - '; exit 55")]))') - eq({'notification', 'ready', {}}, next_msg()) + feed_command( + 'call rpcnotify(g:channel, "ready") | ' + .. 'call rpcnotify(g:channel, "wait", ' + .. 'jobwait([jobstart("' + .. (is_os('win') and 'Start-Sleep 10' or 'sleep 10') + .. '; exit 55")]))' + ) + eq({ 'notification', 'ready', {} }, next_msg()) feed('<c-c>') - eq({'notification', 'wait', {{-2}}}, next_msg()) + 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("'.. - (is_os('win') and 'Start-Sleep 10' or 'sleep 10').. - '; exit 55")], 10000))') - eq({'notification', 'ready', {}}, next_msg()) + feed_command( + 'call rpcnotify(g:channel, "ready") | ' + .. 'call rpcnotify(g:channel, "wait", ' + .. 'jobwait([jobstart("' + .. (is_os('win') and 'Start-Sleep 10' or 'sleep 10') + .. '; exit 55")], 10000))' + ) + eq({ 'notification', 'ready', {} }, next_msg()) feed('<c-c>') - eq({'notification', 'wait', {{-2}}}, next_msg()) + eq({ 'notification', 'wait', { { -2 } } }, next_msg()) end) it('can be called recursively', function() @@ -844,11 +876,11 @@ describe('jobs', function() local r for i = 10, 1, -1 do r = next_msg() - eq('job '..i..' closed', r[3][1]) + eq('job ' .. i .. ' closed', r[3][1]) r = next_msg() - eq('job '..i..' exited', r[3][1]) + eq('job ' .. i .. ' exited', r[3][1]) end - eq(10, nvim('eval', 'g:counter')) + eq(10, api.nvim_eval('g:counter')) end) describe('with timeout argument', function() @@ -858,7 +890,7 @@ describe('jobs', function() \ jobstart((has('win32') ? 'Start-Sleep 10' : 'sleep 10').'; exit 5'), \ ], 100)) ]]) - eq({'notification', 'wait', {{-1}}}, next_msg()) + eq({ 'notification', 'wait', { { -1 } } }, next_msg()) end) it('can pass 0 to check if a job exists', function() @@ -871,16 +903,16 @@ describe('jobs', function() \ jobstart('sleep 0.3; exit 5'), \ ], 0)) ]]) - eq({'notification', 'wait', {{-1, -1}}}, next_msg()) + eq({ 'notification', 'wait', { { -1, -1 } } }, next_msg()) end) end) it('hides cursor and flushes messages before blocking', function() local screen = Screen.new(50, 6) screen:set_default_attr_ids({ - [0] = {foreground = Screen.colors.Blue, bold = true}; -- NonText - [1] = {bold = true, reverse = true}; -- MsgSeparator - [2] = {bold = true, foreground = Screen.colors.SeaGreen}; -- MoreMsg + [0] = { foreground = Screen.colors.Blue, bold = true }, -- NonText + [1] = { bold = true, reverse = true }, -- MsgSeparator + [2] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg }) screen:attach() command([[let g:id = jobstart([v:progpath, '--clean', '--headless'])]]) @@ -892,56 +924,66 @@ describe('jobs', function() endfunc ]]) feed_command('call PrintAndWait()') - screen:expect{grid=[[ + screen:expect { + grid = [[ | - {0:~ }| - {0:~ }| + {0:~ }|*2 {1: }| aaa | bbb | - ]], timeout=100} - screen:expect{grid=[[ + ]], + timeout = 100, + } + screen:expect { + grid = [[ | {1: }| aaa | bbb | ccc | {2:Press ENTER or type command to continue}^ | - ]]} + ]], + } feed('<CR>') - funcs.jobstop(meths.get_var('id')) + fn.jobstop(api.nvim_get_var('id')) end) end) pending('exit event follows stdout, stderr', function() - nvim('command', "let g:job_opts.on_stderr = function('OnEvent')") - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") - nvim('eval', 'jobsend(j, "abcdef")') - nvim('eval', 'jobstop(j)') + command("let g:job_opts.on_stderr = function('OnEvent')") + command("let j = jobstart(['cat', '-'], g:job_opts)") + api.nvim_eval('jobsend(j, "abcdef")') + api.nvim_eval('jobstop(j)') expect_msg_seq( - { {'notification', 'stdout', {0, {'abcdef'}}}, - {'notification', 'stdout', {0, {''}}}, - {'notification', 'stderr', {0, {''}}}, + { + { 'notification', 'stdout', { 0, { 'abcdef' } } }, + { 'notification', 'stdout', { 0, { '' } } }, + { 'notification', 'stderr', { 0, { '' } } }, }, -- Alternative sequence: - { {'notification', 'stderr', {0, {''}}}, - {'notification', 'stdout', {0, {'abcdef'}}}, - {'notification', 'stdout', {0, {''}}}, + { + { 'notification', 'stderr', { 0, { '' } } }, + { 'notification', 'stdout', { 0, { 'abcdef' } } }, + { 'notification', 'stdout', { 0, { '' } } }, }, -- Alternative sequence: - { {'notification', 'stdout', {0, {'abcdef'}}}, - {'notification', 'stderr', {0, {''}}}, - {'notification', 'stdout', {0, {''}}}, + { + { 'notification', 'stdout', { 0, { 'abcdef' } } }, + { 'notification', 'stderr', { 0, { '' } } }, + { 'notification', 'stdout', { 0, { '' } } }, } ) - eq({'notification', 'exit', {0, 143}}, next_msg()) + eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) it('cannot have both rpc and pty options', function() - command("let g:job_opts.pty = v:true") - command("let g:job_opts.rpc = v:true") + command('let g:job_opts.pty = v:true') + command('let g:job_opts.rpc = v:true') local _, err = pcall(command, "let j = jobstart(['cat', '-'], g:job_opts)") - ok(string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") ~= nil) + ok( + string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") + ~= nil + ) end) it('does not crash when repeatedly failing to start shell', function() @@ -954,7 +996,7 @@ describe('jobs', function() ]]) -- The crash only triggered if both jobs are cleaned up on the same event -- loop tick. This is also prevented by try-block, so feed must be used. - feed_command("call DoIt()") + feed_command('call DoIt()') feed('<cr>') -- press RETURN assert_alive() end) @@ -991,7 +1033,7 @@ describe('jobs', function() \ 'substitute(v:val, "\r", "", "")'), \ 'split(v:val, "\\s\\+")') if len(proc) == 6 - let s:procs[proc[1]] ..']]'..[[= {'name': proc[0], + let s:procs[proc[1]] .. ']]' .. [[= {'name': proc[0], \ 'Session Name': proc[2], \ 'Session': proc[3]} endif @@ -1015,15 +1057,13 @@ describe('jobs', function() endfunction ]]) end - local sleep_cmd = (is_os('win') - and 'ping -n 31 127.0.0.1' - or 'sleep 30') - local j = eval("jobstart('"..sleep_cmd..' | '..sleep_cmd..' | '..sleep_cmd.."')") - local ppid = funcs.jobpid(j) + local sleep_cmd = (is_os('win') and 'ping -n 31 127.0.0.1' or 'sleep 30') + local j = eval("jobstart('" .. sleep_cmd .. ' | ' .. sleep_cmd .. ' | ' .. sleep_cmd .. "')") + local ppid = fn.jobpid(j) local children if is_os('win') then local status, result = pcall(retry, nil, nil, function() - children = meths.get_proc_children(ppid) + children = api.nvim_get_proc_children(ppid) -- On Windows conhost.exe may exist, and -- e.g. vctip.exe might appear. #10783 ok(#children >= 3 and #children <= 5) @@ -1034,36 +1074,38 @@ describe('jobs', function() error(result) end else - retry(nil, nil, function() - children = meths.get_proc_children(ppid) + retry(nil, nil, function() + children = api.nvim_get_proc_children(ppid) eq(3, #children) end) end -- Assert that nvim_get_proc() sees the children. for _, child_pid in ipairs(children) do - local info = meths.get_proc(child_pid) + local info = api.nvim_get_proc(child_pid) -- eq((is_os('win') and 'nvim.exe' or 'nvim'), info.name) eq(ppid, info.ppid) end -- Kill the root of the tree. - eq(1, funcs.jobstop(j)) + eq(1, fn.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)) + eq(NIL, api.nvim_get_proc(child_pid)) end end) end) it('jobstop on same id before stopped', function() - nvim('command', 'let j = jobstart(["cat", "-"], g:job_opts)') + command('let j = jobstart(["cat", "-"], g:job_opts)') neq(0, eval('j')) - eq({1, 0}, eval('[jobstop(j), jobstop(j)]')) + eq({ 1, 0 }, eval('[jobstop(j), jobstop(j)]')) end) describe('running tty-test program', function() - if skip(is_os('win')) then return end + if skip(is_os('win')) then + return + end local function next_chunk() local rv while true do @@ -1084,7 +1126,7 @@ describe('jobs', function() local j local function send(str) -- check no nvim_chan_free double free with pty job (#14198) - meths.chan_send(j, str) + api.nvim_chan_send(j, str) end before_each(function() @@ -1095,10 +1137,10 @@ describe('jobs', function() endfunction ]]) insert(testprg('tty-test')) - nvim('command', 'let g:job_opts.pty = 1') - nvim('command', 'let exec = [expand("<cfile>:p")]') - nvim('command', "let j = jobstart(exec, g:job_opts)") - j = eval'j' + command('let g:job_opts.pty = 1') + command('let exec = [expand("<cfile>:p")]') + command('let j = jobstart(exec, g:job_opts)') + j = eval 'j' eq('tty ready', next_chunk()) end) @@ -1108,17 +1150,17 @@ describe('jobs', function() end) it('resizing window', function() - nvim('command', 'call jobresize(j, 40, 10)') + command('call jobresize(j, 40, 10)') eq('rows: 10, cols: 40', next_chunk()) - nvim('command', 'call jobresize(j, 10, 40)') + command('call jobresize(j, 10, 40)') eq('rows: 40, cols: 10', next_chunk()) end) it('jobclose() sends SIGHUP', function() - nvim('command', 'call jobclose(j)') + command('call jobclose(j)') local msg = next_msg() - msg = (msg[2] == 'stdout') and next_msg() or msg -- Skip stdout, if any. - eq({'notification', 'exit', {0, 42}}, msg) + msg = (msg[2] == 'stdout') and next_msg() or msg -- Skip stdout, if any. + eq({ 'notification', 'exit', { 0, 42 } }, msg) end) it('jobstart() does not keep ptmx file descriptor open', function() @@ -1132,7 +1174,7 @@ describe('jobs', function() -- Have to wait so that the SIGHUP can be processed by tty-test on time. -- Can't wait for the next message in case this test fails, if it fails -- there won't be any more messages, and the test would hang. - helpers.sleep(100) + vim.uv.sleep(100) local err = exc_exec('call jobpid(j)') eq('Vim(call):E900: Invalid channel id', err) @@ -1141,9 +1183,49 @@ describe('jobs', function() command('call jobstop(' .. other_jobid .. ')') end) end) + + it('does not close the same handle twice on exit #25086', function() + local filename = string.format('%s.lua', helpers.tmpname()) + write_file( + filename, + [[ + vim.api.nvim_create_autocmd('VimLeavePre', { + callback = function() + local id = vim.fn.jobstart('sleep 0') + vim.fn.jobwait({id}) + end, + }) + ]] + ) + + local screen = thelpers.setup_child_nvim({ + '--cmd', + 'set notermguicolors', + '-i', + 'NONE', + '-u', + filename, + }) + -- Wait for startup to complete, so that all terminal responses are received. + screen:expect([[ + {1: } | + ~ |*3 + {1:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + + feed(':q<CR>') + screen:expect([[ + | + [Process exited 0]{1: } | + |*4 + {3:-- TERMINAL --} | + ]]) + end) end) -describe("pty process teardown", function() +describe('pty process teardown', function() local screen before_each(function() clear() @@ -1151,31 +1233,33 @@ describe("pty process teardown", function() screen:attach() screen:expect([[ ^ | - ~ | - ~ | - ~ | - ~ | + ~ |*4 | ]]) end) - it("does not prevent/delay exit. #4798 #4900", function() - skip(is_os('win')) + it('does not prevent/delay exit. #4798 #4900', function() -- Use a nested nvim (in :term) to test without --headless. - feed_command(":terminal '"..helpers.nvim_prog - .."' -u NONE -i NONE --cmd '"..nvim_set.."' " + fn.termopen({ + 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', + }, { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } }) -- Exiting should terminate all descendants (PTY, its children, ...). screen:expect([[ ^ | [Process exited 0] | - | - | - | - | + |*4 ]]) end) end) |