123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- local t = require('test.testutil')
- local n = require('test.functional.testnvim')()
- local clear, eq, eval, next_msg, ok, source = n.clear, t.eq, n.eval, n.next_msg, t.ok, n.source
- local command, fn, api = n.command, n.fn, n.api
- local matches = t.matches
- local sleep = vim.uv.sleep
- local get_session, set_session = n.get_session, n.set_session
- local nvim_prog = n.nvim_prog
- local is_os = t.is_os
- local retry = t.retry
- local expect_twostreams = n.expect_twostreams
- local assert_alive = n.assert_alive
- local pcall_err = t.pcall_err
- local skip = t.skip
- 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 = n.new_session(true)
- set_session(server)
- local address = fn.serverlist()[1]
- local client = n.new_session(true)
- set_session(client)
- source(init)
- api.nvim_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)
- retry(nil, 1000, function()
- eq(23, api.nvim_get_var('code'))
- end)
- set_session(client)
- 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('dont crash due to garbage in rpc #23781', function()
- local client = get_session()
- local server = n.new_session(true)
- set_session(server)
- local address = fn.serverlist()[1]
- set_session(client)
- api.nvim_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, 'F')")
- eq({ 'notification', 'data', { id, { '' } } }, next_msg())
- set_session(server)
- assert_alive()
- set_session(client)
- command('call chanclose(g:id)')
- command("let g:id = sockconnect('pipe', address, {'on_data':'OnEvent'})")
- id = eval('g:id')
- ok(id > 0)
- command("call chansend(g:id, msgpackdump([[2, 'redraw', 'F']], 'B')[:-4])")
- set_session(server)
- assert_alive()
- set_session(client)
- command("call chansend(g:id, 'F')")
- eq({ 'notification', 'data', { id, { '' } } }, next_msg())
- set_session(server)
- assert_alive()
- set_session(client)
- command('call chanclose(g:id)')
- server:close()
- end)
- it('can use stdio channel', function()
- source([[
- let g:job_opts = {
- \ 'on_stdout': function('OnEvent'),
- \ 'on_stderr': function('OnEvent'),
- \ 'on_exit': function('OnEvent'),
- \ }
- ]])
- api.nvim_set_var('nvim_prog', nvim_prog)
- api.nvim_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 chansend(id, 0z686f6c61)')
- eq({ 'notification', 'stdout', { id, { "[1, ['hola'], '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)
- it('can use stdio channel and on_print callback', function()
- source([[
- let g:job_opts = {
- \ 'on_stdout': function('OnEvent'),
- \ 'on_stderr': function('OnEvent'),
- \ 'on_exit': function('OnEvent'),
- \ }
- ]])
- api.nvim_set_var('nvim_prog', nvim_prog)
- api.nvim_set_var(
- 'code',
- [[
- function! OnStdin(id, data, event) dict
- echo string([a:id, a:data, a:event])
- if a:data == ['']
- quit
- endif
- endfunction
- function! OnPrint(text) dict
- call chansend(g:x, ['OnPrint:' .. a:text])
- endfunction
- let g:x = stdioopen({'on_stdin': funcref('OnStdin'), 'on_print':'OnPrint'})
- 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, { "OnPrint:[1, ['howdy'], 'stdin']" } } }, 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()
- skip(is_os('win'))
- source([[
- let g:job_opts = {
- \ 'on_stdout': function('OnEvent'),
- \ 'on_exit': function('OnEvent'),
- \ 'pty': v:true,
- \ }
- ]])
- api.nvim_set_var('nvim_prog', nvim_prog)
- api.nvim_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, 0z426c6f6273210a)')
- expect_twoline(id, 'stdout', 'Blobs!\r', "[1, ['Blobs!', ''], '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 bsdlike = is_os('bsd') or is_os('mac')
- 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()
- skip(is_os('win'))
- source([[
- let g:job_opts = {
- \ 'on_stderr': function('OnEvent'),
- \ 'on_exit': function('OnEvent'),
- \ 'rpc': v:true,
- \ }
- ]])
- api.nvim_set_var('nvim_prog', nvim_prog)
- api.nvim_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\")')")
- matches('E906: invalid stream for channel', err)
- 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('stdio channel works with stdout redirected to file #30509', function()
- t.write_file(
- 'Xstdio_write.vim',
- [[
- let chan = stdioopen({})
- call chansend(chan, 'foo')
- call chansend(chan, 'bar')
- qall!
- ]]
- )
- local fd = assert(vim.uv.fs_open('Xstdio_redir', 'w', 420))
- local exit_code, exit_signal
- local handle = vim.uv.spawn(nvim_prog, {
- args = { '-u', 'NONE', '-i', 'NONE', '--headless', '-S', 'Xstdio_write.vim' },
- -- Simulate shell redirection: "nvim ... > Xstdio_redir". #30509
- stdio = { nil, fd, nil },
- }, function(code, signal)
- vim.uv.stop()
- exit_code, exit_signal = code, signal
- end)
- finally(function()
- handle:close()
- vim.uv.fs_close(fd)
- os.remove('Xstdio_write.vim')
- os.remove('Xstdio_redir')
- end)
- vim.uv.run('default')
- eq({ 0, 0 }, { exit_code, exit_signal })
- eq('foobar', t.read_file('Xstdio_redir'))
- end)
- it('can use buffered output mode', function()
- skip(fn.executable('grep') == 0, 'missing "grep" command')
- 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()
- skip(fn.executable('grep') == 0, 'missing "grep" command')
- 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)
- describe('loopback', function()
- before_each(function()
- clear()
- command("let chan = sockconnect('pipe', v:servername, {'rpc': v:true})")
- end)
- it('does not crash when sending raw data', function()
- eq(
- "Vim(call):Can't send raw data to rpc channel",
- pcall_err(command, "call chansend(chan, 'test')")
- )
- assert_alive()
- end)
- it('are released when closed', function()
- local chans = eval('len(nvim_list_chans())')
- command('call chanclose(chan)')
- eq(chans - 1, eval('len(nvim_list_chans())'))
- end)
- end)
|