aboutsummaryrefslogtreecommitdiff
path: root/test/functional/core
diff options
context:
space:
mode:
authorJames McCoy <jamessan@jamessan.com>2018-03-28 21:52:06 -0400
committerJames McCoy <jamessan@jamessan.com>2018-03-28 21:54:39 -0400
commit79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1 (patch)
tree4e0589d75801f3ff6a9678f84f5009102766661e /test/functional/core
parent4403864da3c48412595d439f36458d1e6ccfc49f (diff)
parent3f3de9b1a95d273463a87516365510dbffcaf3d2 (diff)
downloadrneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.tar.gz
rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.tar.bz2
rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.zip
Merge branch 'master' into yagebu/option-fixes
Diffstat (limited to 'test/functional/core')
-rw-r--r--test/functional/core/channels_spec.lua281
-rw-r--r--test/functional/core/exit_spec.lua62
-rw-r--r--test/functional/core/job_spec.lua198
-rw-r--r--test/functional/core/path_spec.lua56
-rw-r--r--test/functional/core/startup_spec.lua96
5 files changed, 644 insertions, 49 deletions
diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua
new file mode 100644
index 0000000000..f79f208b69
--- /dev/null
+++ b/test/functional/core/channels_spec.lua
@@ -0,0 +1,281 @@
+
+local helpers = require('test.functional.helpers')(after_each)
+local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq,
+ helpers.eval, helpers.next_msg, 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)
+
+ pending('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())
+
+ -- if dict is reused the new value is not stored,
+ -- but nvim also does not crash
+ command("let id = jobstart(['cat'], g:job_opts)")
+ id = eval("g:id")
+
+ command([[call chansend(id, "cat text\n")]])
+ sleep(10)
+ command("call chanclose(id, 'stdin')")
+
+ -- old value was not overwritten
+ eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
+ '20 GOTO 10', ''}}}, next_msg())
+
+ -- and an error was thrown.
+ eq("E5210: dict key 'stdout' already set for buffered stream in channel "..id, eval('v:errmsg'))
+
+ -- 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/exit_spec.lua b/test/functional/core/exit_spec.lua
index 3fb39f3e78..80c65e4544 100644
--- a/test/functional/core/exit_spec.lua
+++ b/test/functional/core/exit_spec.lua
@@ -1,9 +1,14 @@
local helpers = require('test.functional.helpers')(after_each)
local command = helpers.command
+local feed_command = helpers.feed_command
local eval = helpers.eval
-local eq, neq = helpers.eq, helpers.neq
+local eq = helpers.eq
local run = helpers.run
+local funcs = helpers.funcs
+local nvim_prog = helpers.nvim_prog
+local redir_exec = helpers.redir_exec
+local wait = helpers.wait
describe('v:exiting', function()
local cid
@@ -29,18 +34,65 @@ describe('v:exiting', function()
end
run(on_request, nil, on_setup)
end)
-
- it('is non-zero after :cquit', function()
+ it('is 0 on exit from ex-mode involving try-catch', function()
local function on_setup()
command('autocmd VimLeavePre * call rpcrequest('..cid..', "")')
command('autocmd VimLeave * call rpcrequest('..cid..', "")')
- command('cquit')
+ feed_command('call feedkey("Q")','try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit')
end
local function on_request()
- neq(0, eval('v:exiting'))
+ eq(0, eval('v:exiting'))
return ''
end
run(on_request, nil, on_setup)
end)
+end)
+
+describe(':cquit', function()
+ local function test_cq(cmdline, exit_code, redir_msg)
+ if redir_msg then
+ eq('\n' .. redir_msg, redir_exec(cmdline))
+ wait()
+ eq(2, eval("1+1")) -- Still alive?
+ else
+ funcs.system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline})
+ eq(exit_code, eval('v:shell_error'))
+ end
+ end
+
+ before_each(function()
+ helpers.clear()
+ end)
+
+ it('exits with non-zero after :cquit', function()
+ test_cq('cquit', 1, nil)
+ end)
+
+ it('exits with non-zero after :cquit 123', function()
+ test_cq('cquit 123', 123, nil)
+ end)
+
+ it('exits with non-zero after :123 cquit', function()
+ test_cq('123 cquit', 123, nil)
+ end)
+
+ it('exits with 0 after :cquit 0', function()
+ test_cq('cquit 0', 0, nil)
+ end)
+
+ it('exits with 0 after :0 cquit', function()
+ test_cq('0 cquit', 0, nil)
+ end)
+
+ it('exits with redir msg for multiple exit codes after :cquit 1 2', function()
+ test_cq('cquit 1 2', nil, 'E488: Trailing characters: cquit 1 2')
+ end)
+ it('exits with redir msg for non-number exit code after :cquit X', function()
+ test_cq('cquit X', nil, 'E488: Trailing characters: cquit X')
+ end)
+
+ it('exits with redir msg for negative exit code after :cquit -1', function()
+ test_cq('cquit -1', nil, 'E488: Trailing characters: cquit -1')
+ end)
end)
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)
diff --git a/test/functional/core/path_spec.lua b/test/functional/core/path_spec.lua
new file mode 100644
index 0000000000..669bc99136
--- /dev/null
+++ b/test/functional/core/path_spec.lua
@@ -0,0 +1,56 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local eval = helpers.eval
+local command = helpers.command
+local iswin = helpers.iswin
+
+describe('path collapse', function()
+ local targetdir
+ local expected_path
+
+ local function join_path(...)
+ local pathsep = (iswin() and '\\' or '/')
+ return table.concat({...}, pathsep)
+ end
+
+ before_each(function()
+ targetdir = join_path('test', 'functional', 'fixtures')
+ clear()
+ command('edit '..join_path(targetdir, 'tty-test.c'))
+ expected_path = eval('expand("%:p")')
+ end)
+
+ it('with /./ segment #7117', function()
+ command('edit '..join_path(targetdir, '.', 'tty-test.c'))
+ eq(expected_path, eval('expand("%:p")'))
+ end)
+
+ it('with ./ prefix #7117', function()
+ command('edit '..join_path('.', targetdir, 'tty-test.c'))
+ eq(expected_path, eval('expand("%:p")'))
+ end)
+
+ it('with ./ prefix, after directory change #7117', function()
+ command('edit '..join_path('.', targetdir, 'tty-test.c'))
+ command('cd test')
+ eq(expected_path, eval('expand("%:p")'))
+ end)
+
+ it('with /../ segment #7117', function()
+ command('edit '..join_path(targetdir, '..', 'fixtures', 'tty-test.c'))
+ eq(expected_path, eval('expand("%:p")'))
+ end)
+
+ it('with ../ and different starting directory #7117', function()
+ command('cd test')
+ command('edit '..join_path('..', targetdir, 'tty-test.c'))
+ eq(expected_path, eval('expand("%:p")'))
+ end)
+
+ it('with ./../ and different starting directory #7117', function()
+ command('cd test')
+ command('edit '..join_path('.', '..', targetdir, 'tty-test.c'))
+ eq(expected_path, eval('expand("%:p")'))
+ end)
+end)
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
new file mode 100644
index 0000000000..ae7f949e52
--- /dev/null
+++ b/test/functional/core/startup_spec.lua
@@ -0,0 +1,96 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local funcs = helpers.funcs
+local nvim_prog = helpers.nvim_prog
+local nvim_set = helpers.nvim_set
+local read_file = helpers.read_file
+local retry = helpers.retry
+local iswin = helpers.iswin
+
+describe('startup', function()
+ before_each(function()
+ clear()
+ end)
+ after_each(function()
+ os.remove('Xtest_startup_ttyout')
+ end)
+
+ it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
+ -- system() puts a pipe at both ends.
+ local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
+ '--cmd', nvim_set,
+ '-c', [[echo has('ttyin') has('ttyout')]],
+ '+q' })
+ eq('0 0', out)
+ end)
+ it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
+ local screen = Screen.new(25, 3)
+ -- Remote UI connected by --embed.
+ screen:attach()
+ command([[echo has('ttyin') has('ttyout')]])
+ screen:expect([[
+ ^ |
+ ~ |
+ 0 0 |
+ ]])
+ end)
+ it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
+ local screen = Screen.new(25, 3)
+ screen:attach()
+ if iswin() then
+ command([[set shellcmdflag=/s\ /c shellxquote=\"]])
+ end
+ -- Running in :terminal
+ command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
+ ..nvim_set..[[\" ]]
+ ..[[-c \"echo has('ttyin') has('ttyout')\""]]
+ ..[[, shellescape(v:progpath))]])
+ screen:expect([[
+ ^ |
+ 1 1 |
+ |
+ ]])
+ end)
+ it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
+ local screen = Screen.new(25, 5)
+ screen:attach()
+ if iswin() then
+ command([[set shellcmdflag=/s\ /c shellxquote=\"]])
+ end
+ -- Running in :terminal
+ command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
+ ..nvim_set..[[\" ]]
+ ..[[-c \"call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')\"]]
+ ..[[-c q | cat -v"]] -- Output to a pipe.
+ ..[[, shellescape(v:progpath))]])
+ retry(nil, 3000, function()
+ screen:sleep(1)
+ eq('1\n0\n', -- stdin is a TTY, stdout is a pipe
+ read_file('Xtest_startup_ttyout'))
+ end)
+ end)
+ it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
+ local screen = Screen.new(25, 5)
+ screen:attach()
+ if iswin() then
+ command([[set shellcmdflag=/s\ /c shellxquote=\"]])
+ end
+ -- Running in :terminal
+ command([[exe printf("terminal echo foo | ]] -- Input from a pipe.
+ ..[[%s -u NONE -i NONE --cmd \"]]
+ ..nvim_set..[[\" ]]
+ ..[[-c \"call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')\"]]
+ ..[[-c q -- -"]]
+ ..[[, shellescape(v:progpath))]])
+ retry(nil, 3000, function()
+ screen:sleep(1)
+ eq('0\n1\n', -- stdin is a pipe, stdout is a TTY
+ read_file('Xtest_startup_ttyout'))
+ end)
+ end)
+end)
+