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