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