channels_spec.lua 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. local helpers = require('test.functional.helpers')(after_each)
  2. local uname = helpers.uname
  3. local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq,
  4. helpers.eval, helpers.next_msg, helpers.ok, helpers.source
  5. local command, funcs, meths = helpers.command, helpers.funcs, helpers.meths
  6. local sleep = helpers.sleep
  7. local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv
  8. local set_session = helpers.set_session
  9. local nvim_prog = helpers.nvim_prog
  10. local is_os = helpers.is_os
  11. local retry = helpers.retry
  12. local expect_twostreams = helpers.expect_twostreams
  13. describe('channels', function()
  14. local init = [[
  15. function! Normalize(data) abort
  16. " Windows: remove ^M
  17. return type([]) == type(a:data)
  18. \ ? map(a:data, 'substitute(v:val, "\r", "", "g")')
  19. \ : a:data
  20. endfunction
  21. function! OnEvent(id, data, event) dict
  22. call rpcnotify(1, a:event, a:id, a:data)
  23. endfunction
  24. ]]
  25. before_each(function()
  26. clear()
  27. source(init)
  28. end)
  29. pending('can connect to socket', function()
  30. local server = spawn(nvim_argv)
  31. set_session(server)
  32. local address = funcs.serverlist()[1]
  33. local client = spawn(nvim_argv)
  34. set_session(client, true)
  35. source(init)
  36. meths.set_var('address', address)
  37. command("let g:id = sockconnect('pipe', address, {'on_data':'OnEvent'})")
  38. local id = eval("g:id")
  39. ok(id > 0)
  40. command("call chansend(g:id, msgpackdump([[2,'nvim_set_var',['code',23]]]))")
  41. set_session(server, true)
  42. retry(nil, 1000, function()
  43. eq(23, meths.get_var('code'))
  44. end)
  45. set_session(client, true)
  46. command("call chansend(g:id, msgpackdump([[0,0,'nvim_eval',['2+3']]]))")
  47. local res = eval("msgpackdump([[1,0,v:null,5]])")
  48. eq({"\148\001\n\192\005"}, res)
  49. eq({'notification', 'data', {id, res}}, next_msg())
  50. command("call chansend(g:id, msgpackdump([[2,'nvim_command',['quit']]]))")
  51. eq({'notification', 'data', {id, {''}}}, next_msg())
  52. end)
  53. it('can use stdio channel', function()
  54. source([[
  55. let g:job_opts = {
  56. \ 'on_stdout': function('OnEvent'),
  57. \ 'on_stderr': function('OnEvent'),
  58. \ 'on_exit': function('OnEvent'),
  59. \ }
  60. ]])
  61. meths.set_var("nvim_prog", nvim_prog)
  62. meths.set_var("code", [[
  63. function! OnEvent(id, data, event) dict
  64. let text = string([a:id, a:data, a:event])
  65. call chansend(g:x, text)
  66. if a:data == ['']
  67. call chansend(v:stderr, "*dies*")
  68. quit
  69. endif
  70. endfunction
  71. let g:x = stdioopen({'on_stdin':'OnEvent'})
  72. call chansend(x, "hello")
  73. ]])
  74. command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)")
  75. local id = eval("g:id")
  76. ok(id > 0)
  77. eq({ "notification", "stdout", {id, { "hello" } } }, next_msg())
  78. command("call chansend(id, 'howdy')")
  79. eq({"notification", "stdout", {id, {"[1, ['howdy'], 'stdin']"}}}, next_msg())
  80. command("call chanclose(id, 'stdin')")
  81. expect_twostreams({{"notification", "stdout", {id, {"[1, [''], 'stdin']"}}},
  82. {'notification', 'stdout', {id, {''}}}},
  83. {{"notification", "stderr", {id, {"*dies*"}}},
  84. {'notification', 'stderr', {id, {''}}}})
  85. eq({"notification", "exit", {3,0}}, next_msg())
  86. end)
  87. local function expect_twoline(id, stream, line1, line2, nobr)
  88. local msg = next_msg()
  89. local joined = nobr and {line1..line2} or {line1, line2}
  90. if not pcall(eq, {"notification", stream, {id, joined}}, msg) then
  91. local sep = (not nobr) and "" or nil
  92. eq({"notification", stream, {id, {line1, sep}}}, msg)
  93. eq({"notification", stream, {id, {line2}}}, next_msg())
  94. end
  95. end
  96. it('can use stdio channel with pty', function()
  97. if helpers.pending_win32(pending) then return end
  98. source([[
  99. let g:job_opts = {
  100. \ 'on_stdout': function('OnEvent'),
  101. \ 'on_exit': function('OnEvent'),
  102. \ 'pty': v:true,
  103. \ }
  104. ]])
  105. meths.set_var("nvim_prog", nvim_prog)
  106. meths.set_var("code", [[
  107. function! OnEvent(id, data, event) dict
  108. let text = string([a:id, a:data, a:event])
  109. call chansend(g:x, text)
  110. endfunction
  111. let g:x = stdioopen({'on_stdin':'OnEvent'})
  112. ]])
  113. command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)")
  114. local id = eval("g:id")
  115. ok(id > 0)
  116. command("call chansend(id, 'TEXT\n')")
  117. expect_twoline(id, "stdout", "TEXT\r", "[1, ['TEXT', ''], 'stdin']")
  118. command("call chansend(id, 'neovan')")
  119. eq({"notification", "stdout", {id, {"neovan"}}}, next_msg())
  120. command("call chansend(id, '\127\127im\n')")
  121. expect_twoline(id, "stdout", "\b \b\b \bim\r", "[1, ['neovim', ''], 'stdin']")
  122. command("call chansend(id, 'incomplet\004')")
  123. local is_bsd = not not string.find(uname(), 'bsd')
  124. local bsdlike = is_bsd or is_os('mac')
  125. local extra = bsdlike and "^D\008\008" or ""
  126. expect_twoline(id, "stdout",
  127. "incomplet"..extra, "[1, ['incomplet'], 'stdin']", true)
  128. command("call chansend(id, '\004')")
  129. if bsdlike then
  130. expect_twoline(id, "stdout", extra, "[1, [''], 'stdin']", true)
  131. else
  132. eq({"notification", "stdout", {id, {"[1, [''], 'stdin']"}}}, next_msg())
  133. end
  134. -- channel is still open
  135. command("call chansend(id, 'hi again!\n')")
  136. eq({"notification", "stdout", {id, {"hi again!\r", ""}}}, next_msg())
  137. end)
  138. it('stdio channel can use rpc and stderr simultaneously', function()
  139. if helpers.pending_win32(pending) then return end
  140. source([[
  141. let g:job_opts = {
  142. \ 'on_stderr': function('OnEvent'),
  143. \ 'on_exit': function('OnEvent'),
  144. \ 'rpc': v:true,
  145. \ }
  146. ]])
  147. meths.set_var("nvim_prog", nvim_prog)
  148. meths.set_var("code", [[
  149. let id = stdioopen({'rpc':v:true})
  150. call rpcnotify(id,"nvim_call_function", "rpcnotify", [1, "message", "hi there!", id])
  151. call chansend(v:stderr, "trouble!")
  152. ]])
  153. command("let id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)")
  154. eq({"notification", "message", {"hi there!", 1}}, next_msg())
  155. eq({"notification", "stderr", {3, {"trouble!"}}}, next_msg())
  156. eq(30, eval("rpcrequest(id, 'nvim_eval', '[chansend(v:stderr, \"math??\"), 5*6][1]')"))
  157. eq({"notification", "stderr", {3, {"math??"}}}, next_msg())
  158. local _, err = pcall(command,"call rpcrequest(id, 'nvim_command', 'call chanclose(v:stderr, \"stdin\")')")
  159. ok(string.find(err,"E906: invalid stream for channel") ~= nil)
  160. eq(1, eval("rpcrequest(id, 'nvim_eval', 'chanclose(v:stderr, \"stderr\")')"))
  161. eq({"notification", "stderr", {3, {""}}}, next_msg())
  162. command("call rpcnotify(id, 'nvim_command', 'quit')")
  163. eq({"notification", "exit", {3, 0}}, next_msg())
  164. end)
  165. it('can use buffered output mode', function()
  166. source([[
  167. let g:job_opts = {
  168. \ 'on_stdout': function('OnEvent'),
  169. \ 'on_exit': function('OnEvent'),
  170. \ 'stdout_buffered': v:true,
  171. \ }
  172. ]])
  173. command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
  174. local id = eval("g:id")
  175. command([[call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")]])
  176. sleep(10)
  177. command([[call chansend(id, "xx\n20 GOTO 10\nzz\n")]])
  178. command("call chanclose(id, 'stdin')")
  179. eq({"notification", "stdout", {id, {'10 PRINT "NVIM"',
  180. '20 GOTO 10', ''}}}, next_msg())
  181. eq({"notification", "exit", {id, 0}}, next_msg())
  182. command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
  183. id = eval("g:id")
  184. command([[call chansend(id, "is no number\nnot at all")]])
  185. command("call chanclose(id, 'stdin')")
  186. -- works correctly with no output
  187. eq({"notification", "stdout", {id, {''}}}, next_msg())
  188. eq({"notification", "exit", {id, 1}}, next_msg())
  189. end)
  190. it('can use buffered output mode with no stream callback', function()
  191. source([[
  192. function! OnEvent(id, data, event) dict
  193. call rpcnotify(1, a:event, a:id, a:data, self.stdout)
  194. endfunction
  195. let g:job_opts = {
  196. \ 'on_exit': function('OnEvent'),
  197. \ 'stdout_buffered': v:true,
  198. \ }
  199. ]])
  200. command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
  201. local id = eval("g:id")
  202. command([[call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")]])
  203. sleep(10)
  204. command([[call chansend(id, "xx\n20 GOTO 10\nzz\n")]])
  205. command("call chanclose(id, 'stdin')")
  206. eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
  207. '20 GOTO 10', ''}}}, next_msg())
  208. -- if dict is reused the new value is not stored,
  209. -- but nvim also does not crash
  210. command("let id = jobstart(['cat'], g:job_opts)")
  211. id = eval("g:id")
  212. command([[call chansend(id, "cat text\n")]])
  213. sleep(10)
  214. command("call chanclose(id, 'stdin')")
  215. -- old value was not overwritten
  216. eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
  217. '20 GOTO 10', ''}}}, next_msg())
  218. -- and an error was thrown.
  219. eq("E5210: dict key 'stdout' already set for buffered stream in channel "..id, eval('v:errmsg'))
  220. -- reset dictionary
  221. source([[
  222. let g:job_opts = {
  223. \ 'on_exit': function('OnEvent'),
  224. \ 'stdout_buffered': v:true,
  225. \ }
  226. ]])
  227. command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
  228. id = eval("g:id")
  229. command([[call chansend(id, "is no number\nnot at all")]])
  230. command("call chanclose(id, 'stdin')")
  231. -- works correctly with no output
  232. eq({"notification", "exit", {id, 1, {''}}}, next_msg())
  233. end)
  234. end)