xref: /vim-8.2.3635/src/testdir/test_channel.vim (revision 94688b8a)
1" Test for channel functions.
2
3if !has('channel')
4  finish
5endif
6
7source shared.vim
8
9let s:python = PythonProg()
10if s:python == ''
11  " Can't run this test without Python.
12  finish
13endif
14
15" Uncomment the next line to see what happens. Output is in
16" src/testdir/channellog.
17" call ch_logfile('channellog', 'w')
18
19let s:chopt = {}
20
21" Run "testfunc" after sarting the server and stop the server afterwards.
22func s:run_server(testfunc, ...)
23  call RunServer('test_channel.py', a:testfunc, a:000)
24endfunc
25
26" Return a list of open files.
27" Can be used to make sure no resources leaked.
28" Returns an empty list on systems where this is not supported.
29func s:get_resources()
30  let pid = getpid()
31
32  if executable('lsof')
33    return systemlist('lsof -p ' . pid . ' | awk ''$4~/^[0-9]*[rwu]$/&&$5=="REG"{print$NF}''')
34  elseif isdirectory('/proc/' . pid . '/fd/')
35    return systemlist('readlink /proc/' . pid . '/fd/* | grep -v ''^/dev/''')
36  else
37    return []
38  endif
39endfunc
40
41let g:Ch_responseMsg = ''
42func Ch_requestHandler(handle, msg)
43  let g:Ch_responseHandle = a:handle
44  let g:Ch_responseMsg = a:msg
45endfunc
46
47func Ch_communicate(port)
48  " Avoid dropping messages, since we don't use a callback here.
49  let s:chopt.drop = 'never'
50  " Also add the noblock flag to try it out.
51  let s:chopt.noblock = 1
52  let handle = ch_open('localhost:' . a:port, s:chopt)
53  unlet s:chopt.drop
54  unlet s:chopt.noblock
55  if ch_status(handle) == "fail"
56    call assert_report("Can't open channel")
57    return
58  endif
59  if has('job')
60    " check that getjob without a job is handled correctly
61    call assert_equal('no process', string(ch_getjob(handle)))
62  endif
63  let dict = ch_info(handle)
64  call assert_true(dict.id != 0)
65  call assert_equal('open', dict.status)
66  call assert_equal(a:port, string(dict.port))
67  call assert_equal('open', dict.sock_status)
68  call assert_equal('socket', dict.sock_io)
69
70  " Simple string request and reply.
71  call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
72
73  " Malformed command should be ignored.
74  call assert_equal('ok', ch_evalexpr(handle, 'malformed1'))
75  call assert_equal('ok', ch_evalexpr(handle, 'malformed2'))
76  call assert_equal('ok', ch_evalexpr(handle, 'malformed3'))
77
78  " split command should work
79  call assert_equal('ok', ch_evalexpr(handle, 'split'))
80  call WaitFor('exists("g:split")')
81  call assert_equal(123, g:split)
82
83  " string with ][ should work
84  call assert_equal('this][that', ch_evalexpr(handle, 'echo this][that'))
85
86  " nothing to read now
87  call assert_equal(0, ch_canread(handle))
88
89  " sending three messages quickly then reading should work
90  for i in range(3)
91    call ch_sendexpr(handle, 'echo hello ' . i)
92  endfor
93  call assert_equal('hello 0', ch_read(handle)[1])
94  call assert_equal('hello 1', ch_read(handle)[1])
95  call assert_equal('hello 2', ch_read(handle)[1])
96
97  " Request that triggers sending two ex commands.  These will usually be
98  " handled before getting the response, but it's not guaranteed, thus wait a
99  " tiny bit for the commands to get executed.
100  call assert_equal('ok', ch_evalexpr(handle, 'make change'))
101  call WaitForAssert({-> assert_equal("added2", getline("$"))})
102  call assert_equal('added1', getline(line('$') - 1))
103
104  " Request command "foo bar", which fails silently.
105  call assert_equal('ok', ch_evalexpr(handle, 'bad command'))
106  call WaitForAssert({-> assert_match("E492:.*foo bar", v:errmsg)})
107
108  call assert_equal('ok', ch_evalexpr(handle, 'do normal', {'timeout': 100}))
109  call WaitForAssert({-> assert_equal('added more', getline('$'))})
110
111  " Send a request with a specific handler.
112  call ch_sendexpr(handle, 'hello!', {'callback': 'Ch_requestHandler'})
113  call WaitFor('exists("g:Ch_responseHandle")')
114  if !exists('g:Ch_responseHandle')
115    call assert_report('g:Ch_responseHandle was not set')
116  else
117    call assert_equal(handle, g:Ch_responseHandle)
118    unlet g:Ch_responseHandle
119  endif
120  call assert_equal('got it', g:Ch_responseMsg)
121
122  let g:Ch_responseMsg = ''
123  call ch_sendexpr(handle, 'hello!', {'callback': function('Ch_requestHandler')})
124  call WaitFor('exists("g:Ch_responseHandle")')
125  if !exists('g:Ch_responseHandle')
126    call assert_report('g:Ch_responseHandle was not set')
127  else
128    call assert_equal(handle, g:Ch_responseHandle)
129    unlet g:Ch_responseHandle
130  endif
131  call assert_equal('got it', g:Ch_responseMsg)
132
133  " Using lambda.
134  let g:Ch_responseMsg = ''
135  call ch_sendexpr(handle, 'hello!', {'callback': {a, b -> Ch_requestHandler(a, b)}})
136  call WaitFor('exists("g:Ch_responseHandle")')
137  if !exists('g:Ch_responseHandle')
138    call assert_report('g:Ch_responseHandle was not set')
139  else
140    call assert_equal(handle, g:Ch_responseHandle)
141    unlet g:Ch_responseHandle
142  endif
143  call assert_equal('got it', g:Ch_responseMsg)
144
145  " Collect garbage, tests that our handle isn't collected.
146  call test_garbagecollect_now()
147
148  " check setting options (without testing the effect)
149  call ch_setoptions(handle, {'callback': 's:NotUsed'})
150  call ch_setoptions(handle, {'timeout': 1111})
151  call ch_setoptions(handle, {'mode': 'json'})
152  call assert_fails("call ch_setoptions(handle, {'waittime': 111})", "E475")
153  call ch_setoptions(handle, {'callback': ''})
154  call ch_setoptions(handle, {'drop': 'never'})
155  call ch_setoptions(handle, {'drop': 'auto'})
156  call assert_fails("call ch_setoptions(handle, {'drop': 'bad'})", "E475")
157
158  " Send an eval request that works.
159  call assert_equal('ok', ch_evalexpr(handle, 'eval-works'))
160  sleep 10m
161  call assert_equal([-1, 'foo123'], ch_evalexpr(handle, 'eval-result'))
162
163  " Send an eval request with special characters.
164  call assert_equal('ok', ch_evalexpr(handle, 'eval-special'))
165  sleep 10m
166  call assert_equal([-2, "foo\x7f\x10\x01bar"], ch_evalexpr(handle, 'eval-result'))
167
168  " Send an eval request to get a line with special characters.
169  call setline(3, "a\nb\<CR>c\x01d\x7fe")
170  call assert_equal('ok', ch_evalexpr(handle, 'eval-getline'))
171  sleep 10m
172  call assert_equal([-3, "a\nb\<CR>c\x01d\x7fe"], ch_evalexpr(handle, 'eval-result'))
173
174  " Send an eval request that fails.
175  call assert_equal('ok', ch_evalexpr(handle, 'eval-fails'))
176  sleep 10m
177  call assert_equal([-4, 'ERROR'], ch_evalexpr(handle, 'eval-result'))
178
179  " Send an eval request that works but can't be encoded.
180  call assert_equal('ok', ch_evalexpr(handle, 'eval-error'))
181  sleep 10m
182  call assert_equal([-5, 'ERROR'], ch_evalexpr(handle, 'eval-result'))
183
184  " Send a bad eval request. There will be no response.
185  call assert_equal('ok', ch_evalexpr(handle, 'eval-bad'))
186  sleep 10m
187  call assert_equal([-5, 'ERROR'], ch_evalexpr(handle, 'eval-result'))
188
189  " Send an expr request
190  call assert_equal('ok', ch_evalexpr(handle, 'an expr'))
191  call WaitForAssert({-> assert_equal('three', getline('$'))})
192  call assert_equal('one', getline(line('$') - 2))
193  call assert_equal('two', getline(line('$') - 1))
194
195  " Request a redraw, we don't check for the effect.
196  call assert_equal('ok', ch_evalexpr(handle, 'redraw'))
197  call assert_equal('ok', ch_evalexpr(handle, 'redraw!'))
198
199  call assert_equal('ok', ch_evalexpr(handle, 'empty-request'))
200
201  " Reading while there is nothing available.
202  call assert_equal(v:none, ch_read(handle, {'timeout': 0}))
203  let start = reltime()
204  call assert_equal(v:none, ch_read(handle, {'timeout': 333}))
205  let elapsed = reltime(start)
206  call assert_true(reltimefloat(elapsed) > 0.3)
207  call assert_true(reltimefloat(elapsed) < 0.6)
208
209  " Send without waiting for a response, then wait for a response.
210  call ch_sendexpr(handle, 'wait a bit')
211  let resp = ch_read(handle)
212  call assert_equal(type([]), type(resp))
213  call assert_equal(type(11), type(resp[0]))
214  call assert_equal('waited', resp[1])
215
216  " make the server quit, can't check if this works, should not hang.
217  call ch_sendexpr(handle, '!quit!')
218endfunc
219
220func Test_communicate()
221  call ch_log('Test_communicate()')
222  call s:run_server('Ch_communicate')
223endfunc
224
225" Test that we can open two channels.
226func Ch_two_channels(port)
227  let handle = ch_open('localhost:' . a:port, s:chopt)
228  call assert_equal(v:t_channel, type(handle))
229  if ch_status(handle) == "fail"
230    call assert_report("Can't open channel")
231    return
232  endif
233
234  call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
235
236  let newhandle = ch_open('localhost:' . a:port, s:chopt)
237  if ch_status(newhandle) == "fail"
238    call assert_report("Can't open second channel")
239    return
240  endif
241  call assert_equal('got it', ch_evalexpr(newhandle, 'hello!'))
242  call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
243
244  call ch_close(handle)
245  call assert_equal('got it', ch_evalexpr(newhandle, 'hello!'))
246
247  call ch_close(newhandle)
248endfunc
249
250func Test_two_channels()
251  call ch_log('Test_two_channels()')
252  call s:run_server('Ch_two_channels')
253endfunc
254
255" Test that a server crash is handled gracefully.
256func Ch_server_crash(port)
257  let handle = ch_open('localhost:' . a:port, s:chopt)
258  if ch_status(handle) == "fail"
259    call assert_report("Can't open channel")
260    return
261  endif
262
263  call ch_evalexpr(handle, '!crash!')
264
265  sleep 10m
266endfunc
267
268func Test_server_crash()
269  call ch_log('Test_server_crash()')
270  call s:run_server('Ch_server_crash')
271endfunc
272
273"""""""""
274
275func Ch_handler(chan, msg)
276  call ch_log('Ch_handler()')
277  unlet g:Ch_reply
278  let g:Ch_reply = a:msg
279endfunc
280
281func Ch_channel_handler(port)
282  let handle = ch_open('localhost:' . a:port, s:chopt)
283  if ch_status(handle) == "fail"
284    call assert_report("Can't open channel")
285    return
286  endif
287
288  " Test that it works while waiting on a numbered message.
289  call assert_equal('ok', ch_evalexpr(handle, 'call me'))
290  call WaitForAssert({-> assert_equal('we called you', g:Ch_reply)})
291
292  " Test that it works while not waiting on a numbered message.
293  call ch_sendexpr(handle, 'call me again')
294  call WaitForAssert({-> assert_equal('we did call you', g:Ch_reply)})
295endfunc
296
297func Test_channel_handler()
298  call ch_log('Test_channel_handler()')
299  let g:Ch_reply = ""
300  let s:chopt.callback = 'Ch_handler'
301  call s:run_server('Ch_channel_handler')
302  let g:Ch_reply = ""
303  let s:chopt.callback = function('Ch_handler')
304  call s:run_server('Ch_channel_handler')
305  unlet s:chopt.callback
306endfunc
307
308"""""""""
309
310let g:Ch_reply = ''
311func Ch_zeroHandler(chan, msg)
312  unlet g:Ch_reply
313  let g:Ch_reply = a:msg
314endfunc
315
316let g:Ch_zero_reply = ''
317func Ch_oneHandler(chan, msg)
318  unlet g:Ch_zero_reply
319  let g:Ch_zero_reply = a:msg
320endfunc
321
322func Ch_channel_zero(port)
323  let handle = ch_open('localhost:' . a:port, s:chopt)
324  if ch_status(handle) == "fail"
325    call assert_report("Can't open channel")
326    return
327  endif
328
329  " Check that eval works.
330  call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
331
332  " Check that eval works if a zero id message is sent back.
333  let g:Ch_reply = ''
334  call assert_equal('sent zero', ch_evalexpr(handle, 'send zero'))
335  if s:has_handler
336    call WaitForAssert({-> assert_equal('zero index', g:Ch_reply)})
337  else
338    sleep 20m
339    call assert_equal('', g:Ch_reply)
340  endif
341
342  " Check that handler works if a zero id message is sent back.
343  let g:Ch_reply = ''
344  let g:Ch_zero_reply = ''
345  call ch_sendexpr(handle, 'send zero', {'callback': 'Ch_oneHandler'})
346  call WaitForAssert({-> assert_equal('sent zero', g:Ch_zero_reply)})
347  if s:has_handler
348    call assert_equal('zero index', g:Ch_reply)
349  else
350    call assert_equal('', g:Ch_reply)
351  endif
352endfunc
353
354func Test_zero_reply()
355  call ch_log('Test_zero_reply()')
356  " Run with channel handler
357  let s:has_handler = 1
358  let s:chopt.callback = 'Ch_zeroHandler'
359  call s:run_server('Ch_channel_zero')
360  unlet s:chopt.callback
361
362  " Run without channel handler
363  let s:has_handler = 0
364  call s:run_server('Ch_channel_zero')
365endfunc
366
367"""""""""
368
369let g:Ch_reply1 = ""
370func Ch_handleRaw1(chan, msg)
371  unlet g:Ch_reply1
372  let g:Ch_reply1 = a:msg
373endfunc
374
375let g:Ch_reply2 = ""
376func Ch_handleRaw2(chan, msg)
377  unlet g:Ch_reply2
378  let g:Ch_reply2 = a:msg
379endfunc
380
381let g:Ch_reply3 = ""
382func Ch_handleRaw3(chan, msg)
383  unlet g:Ch_reply3
384  let g:Ch_reply3 = a:msg
385endfunc
386
387func Ch_raw_one_time_callback(port)
388  let handle = ch_open('localhost:' . a:port, s:chopt)
389  if ch_status(handle) == "fail"
390    call assert_report("Can't open channel")
391    return
392  endif
393  call ch_setoptions(handle, {'mode': 'raw'})
394
395  " The messages are sent raw, we do our own JSON strings here.
396  call ch_sendraw(handle, "[1, \"hello!\"]\n", {'callback': 'Ch_handleRaw1'})
397  call WaitForAssert({-> assert_equal("[1, \"got it\"]", g:Ch_reply1)})
398  call ch_sendraw(handle, "[2, \"echo something\"]\n", {'callback': 'Ch_handleRaw2'})
399  call ch_sendraw(handle, "[3, \"wait a bit\"]\n", {'callback': 'Ch_handleRaw3'})
400  call WaitForAssert({-> assert_equal("[2, \"something\"]", g:Ch_reply2)})
401  " wait for the 200 msec delayed reply
402  call WaitForAssert({-> assert_equal("[3, \"waited\"]", g:Ch_reply3)})
403endfunc
404
405func Test_raw_one_time_callback()
406  call ch_log('Test_raw_one_time_callback()')
407  call s:run_server('Ch_raw_one_time_callback')
408endfunc
409
410"""""""""
411
412" Test that trying to connect to a non-existing port fails quickly.
413func Test_connect_waittime()
414  call ch_log('Test_connect_waittime()')
415  let start = reltime()
416  let handle = ch_open('localhost:9876', s:chopt)
417  if ch_status(handle) != "fail"
418    " Oops, port does exists.
419    call ch_close(handle)
420  else
421    let elapsed = reltime(start)
422    call assert_true(reltimefloat(elapsed) < 1.0)
423  endif
424
425  " We intend to use a socket that doesn't exist and wait for half a second
426  " before giving up.  If the socket does exist it can fail in various ways.
427  " Check for "Connection reset by peer" to avoid flakyness.
428  let start = reltime()
429  try
430    let handle = ch_open('localhost:9867', {'waittime': 500})
431    if ch_status(handle) != "fail"
432      " Oops, port does exists.
433      call ch_close(handle)
434    else
435      " Failed connection should wait about 500 msec.  Can be longer if the
436      " computer is busy with other things.
437      let elapsed = reltime(start)
438      call assert_true(reltimefloat(elapsed) > 0.3)
439      call assert_true(reltimefloat(elapsed) < 1.5)
440    endif
441  catch
442    if v:exception !~ 'Connection reset by peer'
443      call assert_report("Caught exception: " . v:exception)
444    endif
445  endtry
446endfunc
447
448"""""""""
449
450func Test_raw_pipe()
451  if !has('job')
452    return
453  endif
454  call ch_log('Test_raw_pipe()')
455  " Add a dummy close callback to avoid that messages are dropped when calling
456  " ch_canread().
457  " Also test the non-blocking option.
458  let job = job_start(s:python . " test_channel_pipe.py",
459	\ {'mode': 'raw', 'drop': 'never', 'noblock': 1})
460  call assert_equal(v:t_job, type(job))
461  call assert_equal("run", job_status(job))
462
463  call assert_equal("open", ch_status(job))
464  call assert_equal("open", ch_status(job), {"part": "out"})
465  call assert_equal("open", ch_status(job), {"part": "err"})
466  call assert_fails('call ch_status(job, {"in_mode": "raw"})', 'E475:')
467  call assert_fails('call ch_status(job, {"part": "in"})', 'E475:')
468
469  let dict = ch_info(job)
470  call assert_true(dict.id != 0)
471  call assert_equal('open', dict.status)
472  call assert_equal('open', dict.out_status)
473  call assert_equal('RAW', dict.out_mode)
474  call assert_equal('pipe', dict.out_io)
475  call assert_equal('open', dict.err_status)
476  call assert_equal('RAW', dict.err_mode)
477  call assert_equal('pipe', dict.err_io)
478
479  try
480    " For a change use the job where a channel is expected.
481    call ch_sendraw(job, "echo something\n")
482    let msg = ch_readraw(job)
483    call assert_equal("something\n", substitute(msg, "\r", "", 'g'))
484
485    call ch_sendraw(job, "double this\n")
486    let g:handle = job_getchannel(job)
487    call WaitFor('ch_canread(g:handle)')
488    unlet g:handle
489    let msg = ch_readraw(job)
490    call assert_equal("this\nAND this\n", substitute(msg, "\r", "", 'g'))
491
492    let g:Ch_reply = ""
493    call ch_sendraw(job, "double this\n", {'callback': 'Ch_handler'})
494    call WaitForAssert({-> assert_equal("this\nAND this\n", substitute(g:Ch_reply, "\r", "", 'g'))})
495
496    let reply = ch_evalraw(job, "quit\n", {'timeout': 100})
497    call assert_equal("Goodbye!\n", substitute(reply, "\r", "", 'g'))
498  finally
499    call job_stop(job)
500  endtry
501
502  let g:Ch_job = job
503  call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
504  let info = job_info(job)
505  call assert_equal("dead", info.status)
506  call assert_equal("term", info.stoponexit)
507  call assert_equal(2, len(info.cmd))
508  call assert_equal("test_channel_pipe.py", info.cmd[1])
509
510  let found = 0
511  for j in job_info()
512    if j == job
513      let found += 1
514    endif
515  endfor
516  call assert_equal(1, found)
517endfunc
518
519func Test_raw_pipe_blob()
520  if !has('job')
521    return
522  endif
523  call ch_log('Test_raw_pipe_blob()')
524  " Add a dummy close callback to avoid that messages are dropped when calling
525  " ch_canread().
526  " Also test the non-blocking option.
527  let job = job_start(s:python . " test_channel_pipe.py",
528	\ {'mode': 'raw', 'drop': 'never', 'noblock': 1})
529  call assert_equal(v:t_job, type(job))
530  call assert_equal("run", job_status(job))
531
532  call assert_equal("open", ch_status(job))
533  call assert_equal("open", ch_status(job), {"part": "out"})
534
535  try
536    " Create a blob with the echo command and write it.
537    let blob = 0z00
538    let cmd = "echo something\n"
539    for i in range(0, len(cmd) - 1)
540      let blob[i] = char2nr(cmd[i])
541    endfor
542    call assert_equal(len(cmd), len(blob))
543    call ch_sendraw(job, blob)
544
545    " Read a blob with the reply.
546    let msg = ch_readblob(job)
547    let expected = 'something'
548    for i in range(0, len(expected) - 1)
549      call assert_equal(char2nr(expected[i]), msg[i])
550    endfor
551
552    let reply = ch_evalraw(job, "quit\n", {'timeout': 100})
553    call assert_equal("Goodbye!\n", substitute(reply, "\r", "", 'g'))
554  finally
555    call job_stop(job)
556  endtry
557
558  let g:Ch_job = job
559  call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
560  let info = job_info(job)
561  call assert_equal("dead", info.status)
562endfunc
563
564func Test_nl_pipe()
565  if !has('job')
566    return
567  endif
568  call ch_log('Test_nl_pipe()')
569  let job = job_start([s:python, "test_channel_pipe.py"])
570  call assert_equal("run", job_status(job))
571  try
572    let handle = job_getchannel(job)
573    call ch_sendraw(handle, "echo something\n")
574    call assert_equal("something", ch_readraw(handle))
575
576    call ch_sendraw(handle, "echoerr wrong\n")
577    call assert_equal("wrong", ch_readraw(handle, {'part': 'err'}))
578
579    call ch_sendraw(handle, "double this\n")
580    call assert_equal("this", ch_readraw(handle))
581    call assert_equal("AND this", ch_readraw(handle))
582
583    call ch_sendraw(handle, "split this line\n")
584    call assert_equal("this linethis linethis line", ch_read(handle))
585
586    let reply = ch_evalraw(handle, "quit\n")
587    call assert_equal("Goodbye!", reply)
588  finally
589    call job_stop(job)
590  endtry
591endfunc
592
593func Test_nl_err_to_out_pipe()
594  if !has('job')
595    return
596  endif
597  call ch_logfile('Xlog')
598  call ch_log('Test_nl_err_to_out_pipe()')
599  let job = job_start(s:python . " test_channel_pipe.py", {'err_io': 'out'})
600  call assert_equal("run", job_status(job))
601  try
602    let handle = job_getchannel(job)
603    call ch_sendraw(handle, "echo something\n")
604    call assert_equal("something", ch_readraw(handle))
605
606    call ch_sendraw(handle, "echoerr wrong\n")
607    call assert_equal("wrong", ch_readraw(handle))
608  finally
609    call job_stop(job)
610    call ch_logfile('')
611    let loglines = readfile('Xlog')
612    call assert_true(len(loglines) > 10)
613    let found_test = 0
614    let found_send = 0
615    let found_recv = 0
616    let found_stop = 0
617    for l in loglines
618      if l =~ 'Test_nl_err_to_out_pipe'
619	let found_test = 1
620      endif
621      if l =~ 'SEND on.*echo something'
622	let found_send = 1
623      endif
624      if l =~ 'RECV on.*something'
625	let found_recv = 1
626      endif
627      if l =~ 'Stopping job with'
628	let found_stop = 1
629      endif
630    endfor
631    call assert_equal(1, found_test)
632    call assert_equal(1, found_send)
633    call assert_equal(1, found_recv)
634    call assert_equal(1, found_stop)
635    " On MS-Windows need to sleep for a moment to be able to delete the file.
636    sleep 10m
637    call delete('Xlog')
638  endtry
639endfunc
640
641func Stop_g_job()
642  call job_stop(g:job)
643  if has('win32')
644    " On MS-Windows the server must close the file handle before we are able
645    " to delete the file.
646    call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
647    sleep 10m
648  endif
649endfunc
650
651func Test_nl_read_file()
652  if !has('job')
653    return
654  endif
655  call ch_log('Test_nl_read_file()')
656  call writefile(['echo something', 'echoerr wrong', 'double this'], 'Xinput')
657  let g:job = job_start(s:python . " test_channel_pipe.py",
658	\ {'in_io': 'file', 'in_name': 'Xinput'})
659  call assert_equal("run", job_status(g:job))
660  try
661    let handle = job_getchannel(g:job)
662    call assert_equal("something", ch_readraw(handle))
663    call assert_equal("wrong", ch_readraw(handle, {'part': 'err'}))
664    call assert_equal("this", ch_readraw(handle))
665    call assert_equal("AND this", ch_readraw(handle))
666  finally
667    call Stop_g_job()
668    call delete('Xinput')
669  endtry
670endfunc
671
672func Test_nl_write_out_file()
673  if !has('job')
674    return
675  endif
676  call ch_log('Test_nl_write_out_file()')
677  let g:job = job_start(s:python . " test_channel_pipe.py",
678	\ {'out_io': 'file', 'out_name': 'Xoutput'})
679  call assert_equal("run", job_status(g:job))
680  try
681    let handle = job_getchannel(g:job)
682    call ch_sendraw(handle, "echo line one\n")
683    call ch_sendraw(handle, "echo line two\n")
684    call ch_sendraw(handle, "double this\n")
685    call WaitForAssert({-> assert_equal(['line one', 'line two', 'this', 'AND this'], readfile('Xoutput'))})
686  finally
687    call Stop_g_job()
688    call assert_equal(-1, match(s:get_resources(), '\(^\|/\)Xoutput$'))
689    call delete('Xoutput')
690  endtry
691endfunc
692
693func Test_nl_write_err_file()
694  if !has('job')
695    return
696  endif
697  call ch_log('Test_nl_write_err_file()')
698  let g:job = job_start(s:python . " test_channel_pipe.py",
699	\ {'err_io': 'file', 'err_name': 'Xoutput'})
700  call assert_equal("run", job_status(g:job))
701  try
702    let handle = job_getchannel(g:job)
703    call ch_sendraw(handle, "echoerr line one\n")
704    call ch_sendraw(handle, "echoerr line two\n")
705    call ch_sendraw(handle, "doubleerr this\n")
706    call WaitForAssert({-> assert_equal(['line one', 'line two', 'this', 'AND this'], readfile('Xoutput'))})
707  finally
708    call Stop_g_job()
709    call delete('Xoutput')
710  endtry
711endfunc
712
713func Test_nl_write_both_file()
714  if !has('job')
715    return
716  endif
717  call ch_log('Test_nl_write_both_file()')
718  let g:job = job_start(s:python . " test_channel_pipe.py",
719	\ {'out_io': 'file', 'out_name': 'Xoutput', 'err_io': 'out'})
720  call assert_equal("run", job_status(g:job))
721  try
722    let handle = job_getchannel(g:job)
723    call ch_sendraw(handle, "echoerr line one\n")
724    call ch_sendraw(handle, "echo line two\n")
725    call ch_sendraw(handle, "double this\n")
726    call ch_sendraw(handle, "doubleerr that\n")
727    call WaitForAssert({-> assert_equal(['line one', 'line two', 'this', 'AND this', 'that', 'AND that'], readfile('Xoutput'))})
728  finally
729    call Stop_g_job()
730    call assert_equal(-1, match(s:get_resources(), '\(^\|/\)Xoutput$'))
731    call delete('Xoutput')
732  endtry
733endfunc
734
735func BufCloseCb(ch)
736  let g:Ch_bufClosed = 'yes'
737endfunc
738
739func Run_test_pipe_to_buffer(use_name, nomod, do_msg)
740  if !has('job')
741    return
742  endif
743  call ch_log('Test_pipe_to_buffer()')
744  let g:Ch_bufClosed = 'no'
745  let options = {'out_io': 'buffer', 'close_cb': 'BufCloseCb'}
746  let expected = ['', 'line one', 'line two', 'this', 'AND this', 'Goodbye!']
747  if a:use_name
748    let options['out_name'] = 'pipe-output'
749    if a:do_msg
750      let expected[0] = 'Reading from channel output...'
751    else
752      let options['out_msg'] = 0
753      call remove(expected, 0)
754    endif
755  else
756    sp pipe-output
757    let options['out_buf'] = bufnr('%')
758    quit
759    call remove(expected, 0)
760  endif
761  if a:nomod
762    let options['out_modifiable'] = 0
763  endif
764  let job = job_start(s:python . " test_channel_pipe.py", options)
765  call assert_equal("run", job_status(job))
766  try
767    let handle = job_getchannel(job)
768    call ch_sendraw(handle, "echo line one\n")
769    call ch_sendraw(handle, "echo line two\n")
770    call ch_sendraw(handle, "double this\n")
771    call ch_sendraw(handle, "quit\n")
772    sp pipe-output
773    call WaitFor('line("$") == ' . len(expected) . ' && g:Ch_bufClosed == "yes"')
774    call assert_equal(expected, getline(1, '$'))
775    if a:nomod
776      call assert_equal(0, &modifiable)
777    else
778      call assert_equal(1, &modifiable)
779    endif
780    call assert_equal('yes', g:Ch_bufClosed)
781    bwipe!
782  finally
783    call job_stop(job)
784  endtry
785endfunc
786
787func Test_pipe_to_buffer_name()
788  call Run_test_pipe_to_buffer(1, 0, 1)
789endfunc
790
791func Test_pipe_to_buffer_nr()
792  call Run_test_pipe_to_buffer(0, 0, 1)
793endfunc
794
795func Test_pipe_to_buffer_name_nomod()
796  call Run_test_pipe_to_buffer(1, 1, 1)
797endfunc
798
799func Test_pipe_to_buffer_name_nomsg()
800  call Run_test_pipe_to_buffer(1, 0, 1)
801endfunc
802
803func Test_close_output_buffer()
804  if !has('job')
805    return
806  endif
807  enew!
808  let test_lines = ['one', 'two']
809  call setline(1, test_lines)
810  call ch_log('Test_close_output_buffer()')
811  let options = {'out_io': 'buffer'}
812  let options['out_name'] = 'buffer-output'
813  let options['out_msg'] = 0
814  split buffer-output
815  let job = job_start(s:python . " test_channel_write.py", options)
816  call assert_equal("run", job_status(job))
817  try
818    call WaitForAssert({-> assert_equal(3, line('$'))})
819    quit!
820    sleep 100m
821    " Make sure the write didn't happen to the wrong buffer.
822    call assert_equal(test_lines, getline(1, line('$')))
823    call assert_equal(-1, bufwinnr('buffer-output'))
824    sbuf buffer-output
825    call assert_notequal(-1, bufwinnr('buffer-output'))
826    sleep 100m
827    close  " no more writes
828    bwipe!
829  finally
830    call job_stop(job)
831  endtry
832endfunc
833
834func Run_test_pipe_err_to_buffer(use_name, nomod, do_msg)
835  if !has('job')
836    return
837  endif
838  call ch_log('Test_pipe_err_to_buffer()')
839  let options = {'err_io': 'buffer'}
840  let expected = ['', 'line one', 'line two', 'this', 'AND this']
841  if a:use_name
842    let options['err_name'] = 'pipe-err'
843    if a:do_msg
844      let expected[0] = 'Reading from channel error...'
845    else
846      let options['err_msg'] = 0
847      call remove(expected, 0)
848    endif
849  else
850    sp pipe-err
851    let options['err_buf'] = bufnr('%')
852    quit
853    call remove(expected, 0)
854  endif
855  if a:nomod
856    let options['err_modifiable'] = 0
857  endif
858  let job = job_start(s:python . " test_channel_pipe.py", options)
859  call assert_equal("run", job_status(job))
860  try
861    let handle = job_getchannel(job)
862    call ch_sendraw(handle, "echoerr line one\n")
863    call ch_sendraw(handle, "echoerr line two\n")
864    call ch_sendraw(handle, "doubleerr this\n")
865    call ch_sendraw(handle, "quit\n")
866    sp pipe-err
867    call WaitForAssert({-> assert_equal(expected, getline(1, '$'))})
868    if a:nomod
869      call assert_equal(0, &modifiable)
870    else
871      call assert_equal(1, &modifiable)
872    endif
873    bwipe!
874  finally
875    call job_stop(job)
876  endtry
877endfunc
878
879func Test_pipe_err_to_buffer_name()
880  call Run_test_pipe_err_to_buffer(1, 0, 1)
881endfunc
882
883func Test_pipe_err_to_buffer_nr()
884  call Run_test_pipe_err_to_buffer(0, 0, 1)
885endfunc
886
887func Test_pipe_err_to_buffer_name_nomod()
888  call Run_test_pipe_err_to_buffer(1, 1, 1)
889endfunc
890
891func Test_pipe_err_to_buffer_name_nomsg()
892  call Run_test_pipe_err_to_buffer(1, 0, 0)
893endfunc
894
895func Test_pipe_both_to_buffer()
896  if !has('job')
897    return
898  endif
899  call ch_log('Test_pipe_both_to_buffer()')
900  let job = job_start(s:python . " test_channel_pipe.py",
901	\ {'out_io': 'buffer', 'out_name': 'pipe-err', 'err_io': 'out'})
902  call assert_equal("run", job_status(job))
903  try
904    let handle = job_getchannel(job)
905    call ch_sendraw(handle, "echo line one\n")
906    call ch_sendraw(handle, "echoerr line two\n")
907    call ch_sendraw(handle, "double this\n")
908    call ch_sendraw(handle, "doubleerr that\n")
909    call ch_sendraw(handle, "quit\n")
910    sp pipe-err
911    call WaitForAssert({-> assert_equal(['Reading from channel output...', 'line one', 'line two', 'this', 'AND this', 'that', 'AND that', 'Goodbye!'], getline(1, '$'))})
912    bwipe!
913  finally
914    call job_stop(job)
915  endtry
916endfunc
917
918func Run_test_pipe_from_buffer(use_name)
919  if !has('job')
920    return
921  endif
922  call ch_log('Test_pipe_from_buffer()')
923
924  sp pipe-input
925  call setline(1, ['echo one', 'echo two', 'echo three'])
926  let options = {'in_io': 'buffer', 'block_write': 1}
927  if a:use_name
928    let options['in_name'] = 'pipe-input'
929  else
930    let options['in_buf'] = bufnr('%')
931  endif
932
933  let job = job_start(s:python . " test_channel_pipe.py", options)
934  call assert_equal("run", job_status(job))
935  try
936    let handle = job_getchannel(job)
937    call assert_equal('one', ch_read(handle))
938    call assert_equal('two', ch_read(handle))
939    call assert_equal('three', ch_read(handle))
940    bwipe!
941  finally
942    call job_stop(job)
943  endtry
944endfunc
945
946func Test_pipe_from_buffer_name()
947  call Run_test_pipe_from_buffer(1)
948endfunc
949
950func Test_pipe_from_buffer_nr()
951  call Run_test_pipe_from_buffer(0)
952endfunc
953
954func Run_pipe_through_sort(all, use_buffer)
955  if !executable('sort') || !has('job')
956    return
957  endif
958  let options = {'out_io': 'buffer', 'out_name': 'sortout'}
959  if a:use_buffer
960    split sortin
961    call setline(1, ['ccc', 'aaa', 'ddd', 'bbb', 'eee'])
962    let options.in_io = 'buffer'
963    let options.in_name = 'sortin'
964  endif
965  if !a:all
966    let options.in_top = 2
967    let options.in_bot = 4
968  endif
969  let job = job_start('sort', options)
970
971  if !a:use_buffer
972    call assert_equal("run", job_status(job))
973    call ch_sendraw(job, "ccc\naaa\nddd\nbbb\neee\n")
974    call ch_close_in(job)
975  endif
976
977  call WaitForAssert({-> assert_equal("dead", job_status(job))})
978
979  sp sortout
980  call WaitFor('line("$") > 3')
981  call assert_equal('Reading from channel output...', getline(1))
982  if a:all
983    call assert_equal(['aaa', 'bbb', 'ccc', 'ddd', 'eee'], getline(2, 6))
984  else
985    call assert_equal(['aaa', 'bbb', 'ddd'], getline(2, 4))
986  endif
987
988  call job_stop(job)
989  if a:use_buffer
990    bwipe! sortin
991  endif
992  bwipe! sortout
993endfunc
994
995func Test_pipe_through_sort_all()
996  call ch_log('Test_pipe_through_sort_all()')
997  call Run_pipe_through_sort(1, 1)
998endfunc
999
1000func Test_pipe_through_sort_some()
1001  call ch_log('Test_pipe_through_sort_some()')
1002  call Run_pipe_through_sort(0, 1)
1003endfunc
1004
1005func Test_pipe_through_sort_feed()
1006  call ch_log('Test_pipe_through_sort_feed()')
1007  call Run_pipe_through_sort(1, 0)
1008endfunc
1009
1010func Test_pipe_to_nameless_buffer()
1011  if !has('job')
1012    return
1013  endif
1014  call ch_log('Test_pipe_to_nameless_buffer()')
1015  let job = job_start(s:python . " test_channel_pipe.py",
1016	\ {'out_io': 'buffer'})
1017  call assert_equal("run", job_status(job))
1018  try
1019    let handle = job_getchannel(job)
1020    call ch_sendraw(handle, "echo line one\n")
1021    call ch_sendraw(handle, "echo line two\n")
1022    exe ch_getbufnr(handle, "out") . 'sbuf'
1023    call WaitFor('line("$") >= 3')
1024    call assert_equal(['Reading from channel output...', 'line one', 'line two'], getline(1, '$'))
1025    bwipe!
1026  finally
1027    call job_stop(job)
1028  endtry
1029endfunc
1030
1031func Test_pipe_to_buffer_json()
1032  if !has('job')
1033    return
1034  endif
1035  call ch_log('Test_pipe_to_buffer_json()')
1036  let job = job_start(s:python . " test_channel_pipe.py",
1037	\ {'out_io': 'buffer', 'out_mode': 'json'})
1038  call assert_equal("run", job_status(job))
1039  try
1040    let handle = job_getchannel(job)
1041    call ch_sendraw(handle, "echo [0, \"hello\"]\n")
1042    call ch_sendraw(handle, "echo [-2, 12.34]\n")
1043    exe ch_getbufnr(handle, "out") . 'sbuf'
1044    call WaitFor('line("$") >= 3')
1045    call assert_equal(['Reading from channel output...', '[0,"hello"]', '[-2,12.34]'], getline(1, '$'))
1046    bwipe!
1047  finally
1048    call job_stop(job)
1049  endtry
1050endfunc
1051
1052" Wait a little while for the last line, minus "offset", to equal "line".
1053func s:wait_for_last_line(line, offset)
1054  for i in range(100)
1055    if getline(line('$') - a:offset) == a:line
1056      break
1057    endif
1058    sleep 10m
1059  endfor
1060endfunc
1061
1062func Test_pipe_io_two_buffers()
1063  if !has('job')
1064    return
1065  endif
1066  call ch_log('Test_pipe_io_two_buffers()')
1067
1068  " Create two buffers, one to read from and one to write to.
1069  split pipe-output
1070  set buftype=nofile
1071  split pipe-input
1072  set buftype=nofile
1073
1074  let job = job_start(s:python . " test_channel_pipe.py",
1075	\ {'in_io': 'buffer', 'in_name': 'pipe-input', 'in_top': 0,
1076	\  'out_io': 'buffer', 'out_name': 'pipe-output',
1077	\  'block_write': 1})
1078  call assert_equal("run", job_status(job))
1079  try
1080    exe "normal Gaecho hello\<CR>"
1081    exe bufwinnr('pipe-output') . "wincmd w"
1082    call s:wait_for_last_line('hello', 0)
1083    call assert_equal('hello', getline('$'))
1084
1085    exe bufwinnr('pipe-input') . "wincmd w"
1086    exe "normal Gadouble this\<CR>"
1087    exe bufwinnr('pipe-output') . "wincmd w"
1088    call s:wait_for_last_line('AND this', 0)
1089    call assert_equal('this', getline(line('$') - 1))
1090    call assert_equal('AND this', getline('$'))
1091
1092    bwipe!
1093    exe bufwinnr('pipe-input') . "wincmd w"
1094    bwipe!
1095  finally
1096    call job_stop(job)
1097  endtry
1098endfunc
1099
1100func Test_pipe_io_one_buffer()
1101  if !has('job')
1102    return
1103  endif
1104  call ch_log('Test_pipe_io_one_buffer()')
1105
1106  " Create one buffer to read from and to write to.
1107  split pipe-io
1108  set buftype=nofile
1109
1110  let job = job_start(s:python . " test_channel_pipe.py",
1111	\ {'in_io': 'buffer', 'in_name': 'pipe-io', 'in_top': 0,
1112	\  'out_io': 'buffer', 'out_name': 'pipe-io',
1113	\  'block_write': 1})
1114  call assert_equal("run", job_status(job))
1115  try
1116    exe "normal Goecho hello\<CR>"
1117    call s:wait_for_last_line('hello', 1)
1118    call assert_equal('hello', getline(line('$') - 1))
1119
1120    exe "normal Gadouble this\<CR>"
1121    call s:wait_for_last_line('AND this', 1)
1122    call assert_equal('this', getline(line('$') - 2))
1123    call assert_equal('AND this', getline(line('$') - 1))
1124
1125    bwipe!
1126  finally
1127    call job_stop(job)
1128  endtry
1129endfunc
1130
1131func Test_pipe_null()
1132  if !has('job')
1133    return
1134  endif
1135  call ch_log('Test_pipe_null()')
1136
1137  " We cannot check that no I/O works, we only check that the job starts
1138  " properly.
1139  let job = job_start(s:python . " test_channel_pipe.py something",
1140	\ {'in_io': 'null'})
1141  call assert_equal("run", job_status(job))
1142  try
1143    call assert_equal('something', ch_read(job))
1144  finally
1145    call job_stop(job)
1146  endtry
1147
1148  let job = job_start(s:python . " test_channel_pipe.py err-out",
1149	\ {'out_io': 'null'})
1150  call assert_equal("run", job_status(job))
1151  try
1152    call assert_equal('err-out', ch_read(job, {"part": "err"}))
1153  finally
1154    call job_stop(job)
1155  endtry
1156
1157  let job = job_start(s:python . " test_channel_pipe.py something",
1158	\ {'err_io': 'null'})
1159  call assert_equal("run", job_status(job))
1160  try
1161    call assert_equal('something', ch_read(job))
1162  finally
1163    call job_stop(job)
1164  endtry
1165
1166  let job = job_start(s:python . " test_channel_pipe.py something",
1167	\ {'out_io': 'null', 'err_io': 'out'})
1168  call assert_equal("run", job_status(job))
1169  call job_stop(job)
1170
1171  let job = job_start(s:python . " test_channel_pipe.py something",
1172	\ {'in_io': 'null', 'out_io': 'null', 'err_io': 'null'})
1173  call assert_equal("run", job_status(job))
1174  call assert_equal('channel fail', string(job_getchannel(job)))
1175  call assert_equal('fail', ch_status(job))
1176  call job_stop(job)
1177endfunc
1178
1179func Test_pipe_to_buffer_raw()
1180  if !has('job')
1181    return
1182  endif
1183  call ch_log('Test_raw_pipe_to_buffer()')
1184  let options = {'out_mode': 'raw', 'out_io': 'buffer', 'out_name': 'testout'}
1185  split testout
1186  let job = job_start([s:python, '-c',
1187        \ 'import sys; [sys.stdout.write(".") and sys.stdout.flush() for _ in range(10000)]'], options)
1188  " the job may be done quickly, also accept "dead"
1189  call assert_match('^\%(dead\|run\)$', job_status(job))
1190  call WaitFor('len(join(getline(1, "$"), "")) >= 10000')
1191  try
1192    let totlen = 0
1193    for line in getline(1, '$')
1194      call assert_equal('', substitute(line, '^\.*', '', ''))
1195      let totlen += len(line)
1196    endfor
1197    call assert_equal(10000, totlen)
1198  finally
1199    call job_stop(job)
1200    bwipe!
1201  endtry
1202endfunc
1203
1204func Test_reuse_channel()
1205  if !has('job')
1206    return
1207  endif
1208  call ch_log('Test_reuse_channel()')
1209
1210  let job = job_start(s:python . " test_channel_pipe.py")
1211  call assert_equal("run", job_status(job))
1212  let handle = job_getchannel(job)
1213  try
1214    call ch_sendraw(handle, "echo something\n")
1215    call assert_equal("something", ch_readraw(handle))
1216  finally
1217    call job_stop(job)
1218  endtry
1219
1220  let job = job_start(s:python . " test_channel_pipe.py", {'channel': handle})
1221  call assert_equal("run", job_status(job))
1222  let handle = job_getchannel(job)
1223  try
1224    call ch_sendraw(handle, "echo again\n")
1225    call assert_equal("again", ch_readraw(handle))
1226  finally
1227    call job_stop(job)
1228  endtry
1229endfunc
1230
1231func Test_out_cb()
1232  if !has('job')
1233    return
1234  endif
1235  call ch_log('Test_out_cb()')
1236
1237  let dict = {'thisis': 'dict: '}
1238  func dict.outHandler(chan, msg) dict
1239    if type(a:msg) == v:t_string
1240      let g:Ch_outmsg = self.thisis . a:msg
1241    else
1242      let g:Ch_outobj = a:msg
1243    endif
1244  endfunc
1245  func dict.errHandler(chan, msg) dict
1246    let g:Ch_errmsg = self.thisis . a:msg
1247  endfunc
1248  let job = job_start(s:python . " test_channel_pipe.py",
1249	\ {'out_cb': dict.outHandler,
1250	\  'out_mode': 'json',
1251	\  'err_cb': dict.errHandler,
1252	\  'err_mode': 'json'})
1253  call assert_equal("run", job_status(job))
1254  try
1255    let g:Ch_outmsg = ''
1256    let g:Ch_errmsg = ''
1257    call ch_sendraw(job, "echo [0, \"hello\"]\n")
1258    call ch_sendraw(job, "echoerr [0, \"there\"]\n")
1259    call WaitForAssert({-> assert_equal("dict: hello", g:Ch_outmsg)})
1260    call WaitForAssert({-> assert_equal("dict: there", g:Ch_errmsg)})
1261
1262    " Receive a json object split in pieces
1263    unlet! g:Ch_outobj
1264    call ch_sendraw(job, "echosplit [0, {\"one\": 1,| \"tw|o\": 2, \"three\": 3|}]\n")
1265    let g:Ch_outobj = ''
1266    call WaitForAssert({-> assert_equal({'one': 1, 'two': 2, 'three': 3}, g:Ch_outobj)})
1267  finally
1268    call job_stop(job)
1269  endtry
1270endfunc
1271
1272func Test_out_close_cb()
1273  if !has('job')
1274    return
1275  endif
1276  call ch_log('Test_out_close_cb()')
1277
1278  let s:counter = 1
1279  let g:Ch_msg1 = ''
1280  let g:Ch_closemsg = 0
1281  func! OutHandler(chan, msg)
1282    if s:counter == 1
1283      let g:Ch_msg1 = a:msg
1284    endif
1285    let s:counter += 1
1286  endfunc
1287  func! CloseHandler(chan)
1288    let g:Ch_closemsg = s:counter
1289    let s:counter += 1
1290  endfunc
1291  let job = job_start(s:python . " test_channel_pipe.py quit now",
1292	\ {'out_cb': 'OutHandler',
1293	\  'close_cb': 'CloseHandler'})
1294  " the job may be done quickly, also accept "dead"
1295  call assert_match('^\%(dead\|run\)$', job_status(job))
1296  try
1297    call WaitForAssert({-> assert_equal('quit', g:Ch_msg1)})
1298    call WaitForAssert({-> assert_equal(2, g:Ch_closemsg)})
1299  finally
1300    call job_stop(job)
1301    delfunc OutHandler
1302    delfunc CloseHandler
1303  endtry
1304endfunc
1305
1306func Test_read_in_close_cb()
1307  if !has('job')
1308    return
1309  endif
1310  call ch_log('Test_read_in_close_cb()')
1311
1312  let g:Ch_received = ''
1313  func! CloseHandler(chan)
1314    let g:Ch_received = ch_read(a:chan)
1315  endfunc
1316  let job = job_start(s:python . " test_channel_pipe.py quit now",
1317	\ {'close_cb': 'CloseHandler'})
1318  " the job may be done quickly, also accept "dead"
1319  call assert_match('^\%(dead\|run\)$', job_status(job))
1320  try
1321    call WaitForAssert({-> assert_equal('quit', g:Ch_received)})
1322  finally
1323    call job_stop(job)
1324    delfunc CloseHandler
1325  endtry
1326endfunc
1327
1328" Use channel in NL mode but received text does not end in NL.
1329func Test_read_in_close_cb_incomplete()
1330  if !has('job')
1331    return
1332  endif
1333  call ch_log('Test_read_in_close_cb_incomplete()')
1334
1335  let g:Ch_received = ''
1336  func! CloseHandler(chan)
1337    while ch_status(a:chan, {'part': 'out'}) == 'buffered'
1338      let g:Ch_received .= ch_read(a:chan)
1339    endwhile
1340  endfunc
1341  let job = job_start(s:python . " test_channel_pipe.py incomplete",
1342	\ {'close_cb': 'CloseHandler'})
1343  " the job may be done quickly, also accept "dead"
1344  call assert_match('^\%(dead\|run\)$', job_status(job))
1345  try
1346    call WaitForAssert({-> assert_equal('incomplete', g:Ch_received)})
1347  finally
1348    call job_stop(job)
1349    delfunc CloseHandler
1350  endtry
1351endfunc
1352
1353func Test_out_cb_lambda()
1354  if !has('job')
1355    return
1356  endif
1357  call ch_log('Test_out_cb_lambda()')
1358
1359  let job = job_start(s:python . " test_channel_pipe.py",
1360        \ {'out_cb': {ch, msg -> execute("let g:Ch_outmsg = 'lambda: ' . msg")},
1361        \  'out_mode': 'json',
1362        \  'err_cb': {ch, msg -> execute(":let g:Ch_errmsg = 'lambda: ' . msg")},
1363        \  'err_mode': 'json'})
1364  call assert_equal("run", job_status(job))
1365  try
1366    let g:Ch_outmsg = ''
1367    let g:Ch_errmsg = ''
1368    call ch_sendraw(job, "echo [0, \"hello\"]\n")
1369    call ch_sendraw(job, "echoerr [0, \"there\"]\n")
1370    call WaitForAssert({-> assert_equal("lambda: hello", g:Ch_outmsg)})
1371    call WaitForAssert({-> assert_equal("lambda: there", g:Ch_errmsg)})
1372  finally
1373    call job_stop(job)
1374  endtry
1375endfunc
1376
1377func Test_close_and_exit_cb()
1378  if !has('job')
1379    return
1380  endif
1381  call ch_log('Test_close_and_exit_cb')
1382
1383  let g:retdict = {'ret': {}}
1384  func g:retdict.close_cb(ch) dict
1385    let self.ret['close_cb'] = job_status(ch_getjob(a:ch))
1386  endfunc
1387  func g:retdict.exit_cb(job, status) dict
1388    let self.ret['exit_cb'] = job_status(a:job)
1389  endfunc
1390
1391  let job = job_start([&shell, &shellcmdflag, 'echo'],
1392        \ {'close_cb': g:retdict.close_cb,
1393        \  'exit_cb': g:retdict.exit_cb})
1394  " the job may be done quickly, also accept "dead"
1395  call assert_match('^\%(dead\|run\)$', job_status(job))
1396  call WaitForAssert({-> assert_equal(2, len(g:retdict.ret))})
1397  call assert_match('^\%(dead\|run\)$', g:retdict.ret['close_cb'])
1398  call assert_equal('dead', g:retdict.ret['exit_cb'])
1399  unlet g:retdict
1400endfunc
1401
1402""""""""""
1403
1404function ExitCbWipe(job, status)
1405  exe g:wipe_buf 'bw!'
1406endfunction
1407
1408" This caused a crash, because messages were handled while peeking for a
1409" character.
1410func Test_exit_cb_wipes_buf()
1411  if !has('timers')
1412    return
1413  endif
1414  set cursorline lazyredraw
1415  call test_override('redraw_flag', 1)
1416  new
1417  let g:wipe_buf = bufnr('')
1418
1419  let job = job_start(has('win32') ? 'cmd /c echo:' : ['true'],
1420	\ {'exit_cb': 'ExitCbWipe'})
1421  let timer = timer_start(300, {-> feedkeys("\<Esc>", 'nt')}, {'repeat': 5})
1422  call feedkeys(repeat('g', 1000) . 'o', 'ntx!')
1423  call WaitForAssert({-> assert_equal("dead", job_status(job))})
1424  call timer_stop(timer)
1425
1426  set nocursorline nolazyredraw
1427  unlet g:wipe_buf
1428  call test_override('ALL', 0)
1429endfunc
1430
1431""""""""""
1432
1433let g:Ch_unletResponse = ''
1434func s:UnletHandler(handle, msg)
1435  let g:Ch_unletResponse = a:msg
1436  unlet s:channelfd
1437endfunc
1438
1439" Test that "unlet handle" in a handler doesn't crash Vim.
1440func Ch_unlet_handle(port)
1441  let s:channelfd = ch_open('localhost:' . a:port, s:chopt)
1442  call ch_sendexpr(s:channelfd, "test", {'callback': function('s:UnletHandler')})
1443  call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)})
1444endfunc
1445
1446func Test_unlet_handle()
1447  call ch_log('Test_unlet_handle()')
1448  call s:run_server('Ch_unlet_handle')
1449endfunc
1450
1451""""""""""
1452
1453let g:Ch_unletResponse = ''
1454func Ch_CloseHandler(handle, msg)
1455  let g:Ch_unletResponse = a:msg
1456  call ch_close(s:channelfd)
1457endfunc
1458
1459" Test that "unlet handle" in a handler doesn't crash Vim.
1460func Ch_close_handle(port)
1461  let s:channelfd = ch_open('localhost:' . a:port, s:chopt)
1462  call ch_sendexpr(s:channelfd, "test", {'callback': function('Ch_CloseHandler')})
1463  call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)})
1464endfunc
1465
1466func Test_close_handle()
1467  call ch_log('Test_close_handle()')
1468  call s:run_server('Ch_close_handle')
1469endfunc
1470
1471""""""""""
1472
1473func Test_open_fail()
1474  call ch_log('Test_open_fail()')
1475  silent! let ch = ch_open("noserver")
1476  echo ch
1477  let d = ch
1478endfunc
1479
1480""""""""""
1481
1482func Ch_open_delay(port)
1483  " Wait up to a second for the port to open.
1484  let s:chopt.waittime = 1000
1485  let channel = ch_open('localhost:' . a:port, s:chopt)
1486  unlet s:chopt.waittime
1487  if ch_status(channel) == "fail"
1488    call assert_report("Can't open channel")
1489    return
1490  endif
1491  call assert_equal('got it', ch_evalexpr(channel, 'hello!'))
1492  call ch_close(channel)
1493endfunc
1494
1495func Test_open_delay()
1496  call ch_log('Test_open_delay()')
1497  " The server will wait half a second before creating the port.
1498  call s:run_server('Ch_open_delay', 'delay')
1499endfunc
1500
1501"""""""""
1502
1503function MyFunction(a,b,c)
1504  let g:Ch_call_ret = [a:a, a:b, a:c]
1505endfunc
1506
1507function Ch_test_call(port)
1508  let handle = ch_open('localhost:' . a:port, s:chopt)
1509  if ch_status(handle) == "fail"
1510    call assert_report("Can't open channel")
1511    return
1512  endif
1513
1514  let g:Ch_call_ret = []
1515  call assert_equal('ok', ch_evalexpr(handle, 'call-func'))
1516  call WaitForAssert({-> assert_equal([1, 2, 3], g:Ch_call_ret)})
1517endfunc
1518
1519func Test_call()
1520  call ch_log('Test_call()')
1521  call s:run_server('Ch_test_call')
1522endfunc
1523
1524"""""""""
1525
1526let g:Ch_job_exit_ret = 'not yet'
1527function MyExitCb(job, status)
1528  let g:Ch_job_exit_ret = 'done'
1529endfunc
1530
1531function Ch_test_exit_callback(port)
1532  call job_setoptions(g:currentJob, {'exit_cb': 'MyExitCb'})
1533  let g:Ch_exit_job = g:currentJob
1534  call assert_equal('MyExitCb', job_info(g:currentJob)['exit_cb'])
1535endfunc
1536
1537func Test_exit_callback()
1538  if has('job')
1539    call ch_log('Test_exit_callback()')
1540    call s:run_server('Ch_test_exit_callback')
1541
1542    " wait up to a second for the job to exit
1543    for i in range(100)
1544      if g:Ch_job_exit_ret == 'done'
1545	break
1546      endif
1547      sleep 10m
1548      " calling job_status() triggers the callback
1549      call job_status(g:Ch_exit_job)
1550    endfor
1551
1552    call assert_equal('done', g:Ch_job_exit_ret)
1553    call assert_equal('dead', job_info(g:Ch_exit_job).status)
1554    unlet g:Ch_exit_job
1555  endif
1556endfunc
1557
1558function MyExitTimeCb(job, status)
1559  if job_info(a:job).process == g:exit_cb_val.process
1560    let g:exit_cb_val.end = reltime(g:exit_cb_val.start)
1561  endif
1562  call Resume()
1563endfunction
1564
1565func Test_exit_callback_interval()
1566  if !has('job')
1567    return
1568  endif
1569
1570  let g:exit_cb_val = {'start': reltime(), 'end': 0, 'process': 0}
1571  let job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'})
1572  let g:exit_cb_val.process = job_info(job).process
1573  call WaitFor('type(g:exit_cb_val.end) != v:t_number || g:exit_cb_val.end != 0')
1574  let elapsed = reltimefloat(g:exit_cb_val.end)
1575  call assert_true(elapsed > 0.5)
1576  call assert_true(elapsed < 1.0)
1577
1578  " case: unreferenced job, using timer
1579  if !has('timers')
1580    return
1581  endif
1582
1583  let g:exit_cb_val = {'start': reltime(), 'end': 0, 'process': 0}
1584  let g:job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'})
1585  let g:exit_cb_val.process = job_info(g:job).process
1586  unlet g:job
1587  call Standby(1000)
1588  if type(g:exit_cb_val.end) != v:t_number || g:exit_cb_val.end != 0
1589    let elapsed = reltimefloat(g:exit_cb_val.end)
1590  else
1591    let elapsed = 1.0
1592  endif
1593  call assert_true(elapsed > 0.5)
1594  call assert_true(elapsed < 1.0)
1595endfunc
1596
1597"""""""""
1598
1599let g:Ch_close_ret = 'alive'
1600function MyCloseCb(ch)
1601  let g:Ch_close_ret = 'closed'
1602endfunc
1603
1604function Ch_test_close_callback(port)
1605  let handle = ch_open('localhost:' . a:port, s:chopt)
1606  if ch_status(handle) == "fail"
1607    call assert_report("Can't open channel")
1608    return
1609  endif
1610  call ch_setoptions(handle, {'close_cb': 'MyCloseCb'})
1611
1612  call assert_equal('', ch_evalexpr(handle, 'close me'))
1613  call WaitForAssert({-> assert_equal('closed', g:Ch_close_ret)})
1614endfunc
1615
1616func Test_close_callback()
1617  call ch_log('Test_close_callback()')
1618  call s:run_server('Ch_test_close_callback')
1619endfunc
1620
1621function Ch_test_close_partial(port)
1622  let handle = ch_open('localhost:' . a:port, s:chopt)
1623  if ch_status(handle) == "fail"
1624    call assert_report("Can't open channel")
1625    return
1626  endif
1627  let g:Ch_d = {}
1628  func g:Ch_d.closeCb(ch) dict
1629    let self.close_ret = 'closed'
1630  endfunc
1631  call ch_setoptions(handle, {'close_cb': g:Ch_d.closeCb})
1632
1633  call assert_equal('', ch_evalexpr(handle, 'close me'))
1634  call WaitForAssert({-> assert_equal('closed', g:Ch_d.close_ret)})
1635  unlet g:Ch_d
1636endfunc
1637
1638func Test_close_partial()
1639  call ch_log('Test_close_partial()')
1640  call s:run_server('Ch_test_close_partial')
1641endfunc
1642
1643func Test_job_start_invalid()
1644  call assert_fails('call job_start($x)', 'E474:')
1645  call assert_fails('call job_start("")', 'E474:')
1646endfunc
1647
1648func Test_job_stop_immediately()
1649  if !has('job')
1650    return
1651  endif
1652
1653  let g:job = job_start([s:python, '-c', 'import time;time.sleep(10)'])
1654  try
1655    call job_stop(g:job)
1656    call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
1657  finally
1658    call job_stop(g:job, 'kill')
1659    unlet g:job
1660  endtry
1661endfunc
1662
1663" This was leaking memory.
1664func Test_partial_in_channel_cycle()
1665  let d = {}
1666  let d.a = function('string', [d])
1667  try
1668    let d.b = ch_open('nowhere:123', {'close_cb': d.a})
1669  catch
1670    call assert_exception('E901:')
1671  endtry
1672  unlet d
1673endfunc
1674
1675func Test_using_freed_memory()
1676  let g:a = job_start(['ls'])
1677  sleep 10m
1678  call test_garbagecollect_now()
1679endfunc
1680
1681func Test_collapse_buffers()
1682  if !executable('cat') || !has('job')
1683    return
1684  endif
1685  sp test_channel.vim
1686  let g:linecount = line('$')
1687  close
1688  split testout
1689  1,$delete
1690  call job_start('cat test_channel.vim', {'out_io': 'buffer', 'out_name': 'testout'})
1691  call WaitForAssert({-> assert_inrange(g:linecount, g:linecount + 1, line('$'))})
1692  bwipe!
1693endfunc
1694
1695func Test_write_to_deleted_buffer()
1696  if !executable('echo') || !has('job')
1697    return
1698  endif
1699  let job = job_start('echo hello', {'out_io': 'buffer', 'out_name': 'test_buffer', 'out_msg': 0})
1700  let bufnr = bufnr('test_buffer')
1701  call WaitForAssert({-> assert_equal(['hello'], getbufline(bufnr, 1, '$'))})
1702  call assert_equal('nofile', getbufvar(bufnr, '&buftype'))
1703  call assert_equal('hide', getbufvar(bufnr, '&bufhidden'))
1704
1705  bdel test_buffer
1706  call assert_equal([], getbufline(bufnr, 1, '$'))
1707
1708  let job = job_start('echo hello', {'out_io': 'buffer', 'out_name': 'test_buffer', 'out_msg': 0})
1709  call WaitForAssert({-> assert_equal(['hello'], getbufline(bufnr, 1, '$'))})
1710  call assert_equal('nofile', getbufvar(bufnr, '&buftype'))
1711  call assert_equal('hide', getbufvar(bufnr, '&bufhidden'))
1712
1713  bwipe! test_buffer
1714endfunc
1715
1716func Test_cmd_parsing()
1717  if !has('unix')
1718    return
1719  endif
1720  call assert_false(filereadable("file with space"))
1721  let job = job_start('touch "file with space"')
1722  call WaitForAssert({-> assert_true(filereadable("file with space"))})
1723  call delete("file with space")
1724
1725  let job = job_start('touch file\ with\ space')
1726  call WaitForAssert({-> assert_true(filereadable("file with space"))})
1727  call delete("file with space")
1728endfunc
1729
1730func Test_raw_passes_nul()
1731  if !executable('cat') || !has('job')
1732    return
1733  endif
1734
1735  " Test lines from the job containing NUL are stored correctly in a buffer.
1736  new
1737  call setline(1, ["asdf\nasdf", "xxx\n", "\nyyy"])
1738  w! Xtestread
1739  bwipe!
1740  split testout
1741  1,$delete
1742  call job_start('cat Xtestread', {'out_io': 'buffer', 'out_name': 'testout'})
1743  call WaitFor('line("$") > 2')
1744  call assert_equal("asdf\nasdf", getline(1))
1745  call assert_equal("xxx\n", getline(2))
1746  call assert_equal("\nyyy", getline(3))
1747
1748  call delete('Xtestread')
1749  bwipe!
1750
1751  " Test lines from a buffer with NUL bytes are written correctly to the job.
1752  new mybuffer
1753  call setline(1, ["asdf\nasdf", "xxx\n", "\nyyy"])
1754  let g:Ch_job = job_start('cat', {'in_io': 'buffer', 'in_name': 'mybuffer', 'out_io': 'file', 'out_name': 'Xtestwrite'})
1755  call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
1756  bwipe!
1757  split Xtestwrite
1758  call assert_equal("asdf\nasdf", getline(1))
1759  call assert_equal("xxx\n", getline(2))
1760  call assert_equal("\nyyy", getline(3))
1761  call assert_equal(-1, match(s:get_resources(), '\(^\|/\)Xtestwrite$'))
1762
1763  call delete('Xtestwrite')
1764  bwipe!
1765endfunc
1766
1767func MyLineCountCb(ch, msg)
1768  let g:linecount += 1
1769endfunc
1770
1771func Test_read_nonl_line()
1772  if !has('job')
1773    return
1774  endif
1775
1776  let g:linecount = 0
1777  let arg = 'import sys;sys.stdout.write("1\n2\n3")'
1778  call job_start([s:python, '-c', arg], {'callback': 'MyLineCountCb'})
1779  call WaitForAssert({-> assert_equal(3, g:linecount)})
1780endfunc
1781
1782func Test_read_from_terminated_job()
1783  if !has('job')
1784    return
1785  endif
1786
1787  let g:linecount = 0
1788  let arg = 'import os,sys;os.close(1);sys.stderr.write("test\n")'
1789  call job_start([s:python, '-c', arg], {'callback': 'MyLineCountCb'})
1790  call WaitForAssert({-> assert_equal(1, g:linecount)})
1791endfunc
1792
1793func Test_job_start_windows()
1794  if !has('job') || !has('win32')
1795    return
1796  endif
1797
1798  " Check that backslash in $COMSPEC is handled properly.
1799  let g:echostr = ''
1800  let cmd = $COMSPEC . ' /c echo 123'
1801  let job = job_start(cmd, {'callback': {ch,msg -> execute(":let g:echostr .= msg")}})
1802  let info = job_info(job)
1803  call assert_equal([$COMSPEC, '/c', 'echo', '123'], info.cmd)
1804
1805  call WaitForAssert({-> assert_equal("123", g:echostr)})
1806  unlet g:echostr
1807endfunc
1808
1809func Test_env()
1810  if !has('job')
1811    return
1812  endif
1813
1814  let g:envstr = ''
1815  if has('win32')
1816    let cmd = ['cmd', '/c', 'echo %FOO%']
1817  else
1818    let cmd = [&shell, &shellcmdflag, 'echo $FOO']
1819  endif
1820  call assert_fails('call job_start(cmd, {"env": 1})', 'E475:')
1821  call job_start(cmd, {'callback': {ch,msg -> execute(":let g:envstr .= msg")}, 'env': {'FOO': 'bar'}})
1822  call WaitForAssert({-> assert_equal("bar", g:envstr)})
1823  unlet g:envstr
1824endfunc
1825
1826func Test_cwd()
1827  if !has('job')
1828    return
1829  endif
1830
1831  let g:envstr = ''
1832  if has('win32')
1833    let expect = $TEMP
1834    let cmd = ['cmd', '/c', 'echo %CD%']
1835  else
1836    let expect = $HOME
1837    let cmd = ['pwd']
1838  endif
1839  let job = job_start(cmd, {'callback': {ch,msg -> execute(":let g:envstr .= msg")}, 'cwd': expect})
1840  try
1841    call WaitForAssert({-> assert_notequal("", g:envstr)})
1842    let expect = substitute(expect, '[/\\]$', '', '')
1843    let g:envstr = substitute(g:envstr, '[/\\]$', '', '')
1844    if $CI != '' && stridx(g:envstr, '/private/') == 0
1845      let g:envstr = g:envstr[8:]
1846    endif
1847    call assert_equal(expect, g:envstr)
1848  finally
1849    call job_stop(job)
1850    unlet g:envstr
1851  endtry
1852endfunc
1853
1854function Ch_test_close_lambda(port)
1855  let handle = ch_open('localhost:' . a:port, s:chopt)
1856  if ch_status(handle) == "fail"
1857    call assert_report("Can't open channel")
1858    return
1859  endif
1860  let g:Ch_close_ret = ''
1861  call ch_setoptions(handle, {'close_cb': {ch -> execute("let g:Ch_close_ret = 'closed'")}})
1862
1863  call assert_equal('', ch_evalexpr(handle, 'close me'))
1864  call WaitForAssert({-> assert_equal('closed', g:Ch_close_ret)})
1865endfunc
1866
1867func Test_close_lambda()
1868  call ch_log('Test_close_lambda()')
1869  call s:run_server('Ch_test_close_lambda')
1870endfunc
1871
1872func s:test_list_args(cmd, out, remove_lf)
1873  try
1874    let g:out = ''
1875    let job = job_start([s:python, '-c', a:cmd], {'callback': {ch, msg -> execute('let g:out .= msg')}, 'out_mode': 'raw'})
1876    call WaitFor('"" != g:out')
1877    if has('win32')
1878      let g:out = substitute(g:out, '\r', '', 'g')
1879    endif
1880    if a:remove_lf
1881      let g:out = substitute(g:out, '\n$', '', 'g')
1882    endif
1883    call assert_equal(a:out, g:out)
1884  finally
1885    call job_stop(job)
1886    unlet g:out
1887  endtry
1888endfunc
1889
1890func Test_list_args()
1891  if !has('job')
1892    return
1893  endif
1894
1895  call s:test_list_args('import sys;sys.stdout.write("hello world")', "hello world", 0)
1896  call s:test_list_args('import sys;sys.stdout.write("hello\nworld")', "hello\nworld", 0)
1897  call s:test_list_args('import sys;sys.stdout.write(''hello\nworld'')', "hello\nworld", 0)
1898  call s:test_list_args('import sys;sys.stdout.write(''hello"world'')', "hello\"world", 0)
1899  call s:test_list_args('import sys;sys.stdout.write(''hello^world'')', "hello^world", 0)
1900  call s:test_list_args('import sys;sys.stdout.write("hello&&world")', "hello&&world", 0)
1901  call s:test_list_args('import sys;sys.stdout.write(''hello\\world'')', "hello\\world", 0)
1902  call s:test_list_args('import sys;sys.stdout.write(''hello\\\\world'')', "hello\\\\world", 0)
1903  call s:test_list_args('import sys;sys.stdout.write("hello\"world\"")', 'hello"world"', 0)
1904  call s:test_list_args('import sys;sys.stdout.write("h\"ello worl\"d")', 'h"ello worl"d', 0)
1905  call s:test_list_args('import sys;sys.stdout.write("h\"e\\\"llo wor\\\"l\"d")', 'h"e\"llo wor\"l"d', 0)
1906  call s:test_list_args('import sys;sys.stdout.write("h\"e\\\"llo world")', 'h"e\"llo world', 0)
1907  call s:test_list_args('import sys;sys.stdout.write("hello\tworld")', "hello\tworld", 0)
1908
1909  " tests which not contain spaces in the argument
1910  call s:test_list_args('print("hello\nworld")', "hello\nworld", 1)
1911  call s:test_list_args('print(''hello\nworld'')', "hello\nworld", 1)
1912  call s:test_list_args('print(''hello"world'')', "hello\"world", 1)
1913  call s:test_list_args('print(''hello^world'')', "hello^world", 1)
1914  call s:test_list_args('print("hello&&world")', "hello&&world", 1)
1915  call s:test_list_args('print(''hello\\world'')', "hello\\world", 1)
1916  call s:test_list_args('print(''hello\\\\world'')', "hello\\\\world", 1)
1917  call s:test_list_args('print("hello\"world\"")', 'hello"world"', 1)
1918  call s:test_list_args('print("hello\tworld")', "hello\tworld", 1)
1919endfunc
1920
1921" Do this last, it stops any channel log.
1922func Test_zz_ch_log()
1923  call ch_logfile('Xlog', 'w')
1924  call ch_log('hello there')
1925  call ch_log('%s%s')
1926  call ch_logfile('')
1927  let text = readfile('Xlog')
1928  call assert_match("hello there", text[1])
1929  call assert_match("%s%s", text[2])
1930  call delete('Xlog')
1931endfunc
1932
1933func Test_keep_pty_open()
1934  if !has('unix')
1935    return
1936  endif
1937
1938  let job = job_start(s:python . ' -c "import time;time.sleep(0.2)"',
1939        \ {'out_io': 'null', 'err_io': 'null', 'pty': 1})
1940  let elapsed = WaitFor({-> job_status(job) ==# 'dead'})
1941  call assert_inrange(200, 1000, elapsed)
1942  call job_stop(job)
1943endfunc
1944
1945func Test_job_start_in_timer()
1946  if !has('job') || !has('timers')
1947    return
1948  endif
1949
1950  func OutCb(chan, msg)
1951    let g:val += 1
1952  endfunc
1953
1954  func ExitCb(job, status)
1955    let g:val += 1
1956    call Resume()
1957  endfunc
1958
1959  func TimerCb(timer)
1960    if has('win32')
1961      let cmd = ['cmd', '/c', 'echo.']
1962    else
1963      let cmd = ['echo']
1964    endif
1965    let g:job = job_start(cmd, {'out_cb': 'OutCb', 'exit_cb': 'ExitCb'})
1966    call substitute(repeat('a', 100000), '.', '', 'g')
1967  endfunc
1968
1969  " We should be interrupted before 'updatetime' elapsed.
1970  let g:val = 0
1971  call timer_start(1, 'TimerCb')
1972  let elapsed = Standby(&ut)
1973  call assert_inrange(1, &ut / 2, elapsed)
1974
1975  " Wait for both OutCb() and ExitCb() to have been called before deleting
1976  " them.
1977  call WaitForAssert({-> assert_equal(2, g:val)})
1978  call job_stop(g:job)
1979
1980  delfunc OutCb
1981  delfunc ExitCb
1982  delfunc TimerCb
1983  unlet! g:val
1984  unlet! g:job
1985endfunc
1986
1987func Test_raw_large_data()
1988  try
1989    let g:out = ''
1990    let job = job_start(s:python . " test_channel_pipe.py",
1991          \ {'mode': 'raw', 'drop': 'never', 'noblock': 1,
1992          \  'callback': {ch, msg -> execute('let g:out .= msg')}})
1993
1994    let outlen = 79999
1995    let want = repeat('X', outlen) . "\n"
1996    call ch_sendraw(job, want)
1997    call WaitFor({-> len(g:out) >= outlen}, 10000)
1998    call WaitForAssert({-> assert_equal("dead", job_status(job))})
1999    call assert_equal(want, substitute(g:out, '\r', '', 'g'))
2000  finally
2001    call job_stop(job)
2002    unlet g:out
2003  endtry
2004endfunc
2005
2006func Test_job_exitval_and_termsig()
2007  if !has('unix')
2008    return
2009  endif
2010
2011  " Terminate job normally
2012  let cmd = ['echo']
2013  let job = job_start(cmd)
2014  call WaitForAssert({-> assert_equal("dead", job_status(job))})
2015  let info = job_info(job)
2016  call assert_equal(0, info.exitval)
2017  call assert_equal("", info.termsig)
2018
2019  " Terminate job by signal
2020  let cmd = ['sleep', '10']
2021  let job = job_start(cmd)
2022  sleep 10m
2023  call job_stop(job)
2024  call WaitForAssert({-> assert_equal("dead", job_status(job))})
2025  let info = job_info(job)
2026  call assert_equal(-1, info.exitval)
2027  call assert_equal("term", info.termsig)
2028endfunc
2029