xref: /vim-8.2.3635/src/testdir/shared.vim (revision 50182fa8)
1" Functions shared by several tests.
2
3" Only load this script once.
4if exists('*WaitFor')
5  finish
6endif
7
8" Get the name of the Python executable.
9" Also keeps it in s:python.
10func PythonProg()
11  " This test requires the Python command to run the test server.
12  " This most likely only works on Unix and Windows.
13  if has('unix')
14    " We also need the job feature or the pkill command to make sure the server
15    " can be stopped.
16    if !(executable('python') && (has('job') || executable('pkill')))
17      return ''
18    endif
19    let s:python = 'python'
20  elseif has('win32')
21    " Use Python Launcher for Windows (py.exe) if available.
22    if executable('py.exe')
23      let s:python = 'py.exe'
24    elseif executable('python.exe')
25      let s:python = 'python.exe'
26    else
27      return ''
28    endif
29  else
30    return ''
31  endif
32  return s:python
33endfunc
34
35" Run "cmd".  Returns the job if using a job.
36func RunCommand(cmd)
37  let job = 0
38  if has('job')
39    let job = job_start(a:cmd, {"stoponexit": "hup"})
40    call job_setoptions(job, {"stoponexit": "kill"})
41  elseif has('win32')
42    exe 'silent !start cmd /c start "test_channel" ' . a:cmd
43  else
44    exe 'silent !' . a:cmd . '&'
45  endif
46  return job
47endfunc
48
49" Read the port number from the Xportnr file.
50func GetPort()
51  let l = []
52  for i in range(200)
53    try
54      let l = readfile("Xportnr")
55    catch
56    endtry
57    if len(l) >= 1
58      break
59    endif
60    sleep 10m
61  endfor
62  call delete("Xportnr")
63
64  if len(l) == 0
65    " Can't make the connection, give up.
66    return 0
67  endif
68  return l[0]
69endfunc
70
71" Run a Python server for "cmd" and call "testfunc".
72" Always kills the server before returning.
73func RunServer(cmd, testfunc, args)
74  " The Python program writes the port number in Xportnr.
75  call delete("Xportnr")
76
77  if len(a:args) == 1
78    let arg = ' ' . a:args[0]
79  else
80    let arg = ''
81  endif
82  let pycmd = s:python . " " . a:cmd . arg
83
84  try
85    let g:currentJob = RunCommand(pycmd)
86
87    " Wait for up to 2 seconds for the port number to be there.
88    let port = GetPort()
89    if port == 0
90      call assert_false(1, "Can't start " . a:cmd)
91      return
92    endif
93
94    call call(function(a:testfunc), [port])
95  catch
96    call assert_false(1, 'Caught exception: "' . v:exception . '" in ' . v:throwpoint)
97  finally
98    call s:kill_server(a:cmd)
99  endtry
100endfunc
101
102func s:kill_server(cmd)
103  if has('job')
104    if exists('g:currentJob')
105      call job_stop(g:currentJob)
106      unlet g:currentJob
107    endif
108  elseif has('win32')
109    let cmd = substitute(a:cmd, ".py", '', '')
110    call system('taskkill /IM ' . s:python . ' /T /F /FI "WINDOWTITLE eq ' . cmd . '"')
111  else
112    call system("pkill -f " . a:cmd)
113  endif
114endfunc
115
116" Wait for up to five seconds for "expr" to become true.  "expr" can be a
117" stringified expression to evaluate, or a funcref without arguments.
118" Using a lambda works best.  Example:
119"	call WaitFor({-> status == "ok"})
120"
121" A second argument can be used to specify a different timeout in msec.
122"
123" When successful the time slept is returned.
124" When running into the timeout an exception is thrown, thus the function does
125" not return.
126func WaitFor(expr, ...)
127  let timeout = get(a:000, 0, 5000)
128  let slept = s:WaitForCommon(a:expr, v:null, timeout)
129  if slept < 0
130    throw 'WaitFor() timed out after ' . timeout . ' msec'
131  endif
132  return slept
133endfunc
134
135" Wait for up to five seconds for "assert" to return zero.  "assert" must be a
136" (lambda) function containing one assert function.  Example:
137"	call WaitForAssert({-> assert_equal("dead", job_status(job)})
138"
139" A second argument can be used to specify a different timeout in msec.
140"
141" Return zero for success, one for failure (like the assert function).
142func WaitForAssert(assert, ...)
143  let timeout = get(a:000, 0, 5000)
144  if s:WaitForCommon(v:null, a:assert, timeout) < 0
145    return 1
146  endif
147  return 0
148endfunc
149
150" Common implementation of WaitFor() and WaitForAssert().
151" Either "expr" or "assert" is not v:null
152" Return the waiting time for success, -1 for failure.
153func s:WaitForCommon(expr, assert, timeout)
154  " using reltime() is more accurate, but not always available
155  let slept = 0
156  if has('reltime')
157    let start = reltime()
158  endif
159
160  while 1
161    if type(a:expr) == v:t_func
162      let success = a:expr()
163    elseif type(a:assert) == v:t_func
164      let success = a:assert() == 0
165    else
166      let success = eval(a:expr)
167    endif
168    if success
169      return slept
170    endif
171
172    if slept >= a:timeout
173      break
174    endif
175    if type(a:assert) == v:t_func
176      " Remove the error added by the assert function.
177      call remove(v:errors, -1)
178    endif
179
180    sleep 10m
181    if has('reltime')
182      let slept = float2nr(reltimefloat(reltime(start)) * 1000)
183    else
184      let slept += 10
185    endif
186  endwhile
187
188  return -1  " timed out
189endfunc
190
191
192" Wait for up to a given milliseconds.
193" With the +timers feature this waits for key-input by getchar(), Resume()
194" feeds key-input and resumes process. Return time waited in milliseconds.
195" Without +timers it uses simply :sleep.
196func Standby(msec)
197  if has('timers')
198    let start = reltime()
199    let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
200    call getchar()
201    return float2nr(reltimefloat(reltime(start)) * 1000)
202  else
203    execute 'sleep ' a:msec . 'm'
204    return a:msec
205  endif
206endfunc
207
208func Resume()
209  if exists('g:_standby_timer')
210    call timer_stop(g:_standby_timer)
211    call s:feedkeys(0)
212    unlet g:_standby_timer
213  endif
214endfunc
215
216func s:feedkeys(timer)
217  call feedkeys('x', 'nt')
218endfunc
219
220" Get $VIMPROG to run Vim executable.
221" The Makefile writes it as the first line in the "vimcmd" file.
222func GetVimProg()
223  if !filereadable('vimcmd')
224    " Assume the script was sourced instead of running "make".
225    return '../vim'
226  endif
227  return readfile('vimcmd')[0]
228endfunc
229
230" Get the command to run Vim, with -u NONE and --not-a-term arguments.
231" If there is an argument use it instead of "NONE".
232func GetVimCommand(...)
233  if !filereadable('vimcmd')
234    echo 'Cannot read the "vimcmd" file, falling back to ../vim.'
235    let lines = ['../vim']
236  else
237    let lines = readfile('vimcmd')
238  endif
239  if a:0 == 0
240    let name = 'NONE'
241  else
242    let name = a:1
243  endif
244  " For Unix Makefile writes the command to use in the second line of the
245  " "vimcmd" file, including environment options.
246  " Other Makefiles just write the executable in the first line, so fall back
247  " to that if there is no second line.
248  let cmd = get(lines, 1, lines[0])
249  let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '')
250  if cmd !~ '-u '. name
251    let cmd = cmd . ' -u ' . name
252  endif
253  let cmd .= ' --not-a-term'
254  let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
255  return cmd
256endfunc
257
258" Get the command to run Vim, with --clean.
259func GetVimCommandClean()
260  let cmd = GetVimCommand()
261  let cmd = substitute(cmd, '-u NONE', '--clean', '')
262  let cmd = substitute(cmd, '--not-a-term', '', '')
263  return cmd
264endfunc
265
266" Run Vim, using the "vimcmd" file and "-u NORC".
267" "before" is a list of Vim commands to be executed before loading plugins.
268" "after" is a list of Vim commands to be executed after loading plugins.
269" Plugins are not loaded, unless 'loadplugins' is set in "before".
270" Return 1 if Vim could be executed.
271func RunVim(before, after, arguments)
272  return RunVimPiped(a:before, a:after, a:arguments, '')
273endfunc
274
275func RunVimPiped(before, after, arguments, pipecmd)
276  let cmd = GetVimCommand()
277  if cmd == ''
278    return 0
279  endif
280  let args = ''
281  if len(a:before) > 0
282    call writefile(a:before, 'Xbefore.vim')
283    let args .= ' --cmd "so Xbefore.vim"'
284  endif
285  if len(a:after) > 0
286    call writefile(a:after, 'Xafter.vim')
287    let args .= ' -S Xafter.vim'
288  endif
289
290  exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments
291
292  if len(a:before) > 0
293    call delete('Xbefore.vim')
294  endif
295  if len(a:after) > 0
296    call delete('Xafter.vim')
297  endif
298  return 1
299endfunc
300
301func CanRunGui()
302  return has('gui') && ($DISPLAY != "" || has('gui_running'))
303endfunc
304
305func WorkingClipboard()
306  if !has('clipboard')
307    return 0
308  endif
309  if has('x11')
310    return $DISPLAY != ""
311  endif
312  return 1
313endfunc
314
315" Get line "lnum" as displayed on the screen.
316" Trailing white space is trimmed.
317func! Screenline(lnum)
318  let chars = []
319  for c in range(1, winwidth(0))
320    call add(chars, nr2char(screenchar(a:lnum, c)))
321  endfor
322  let line = join(chars, '')
323  return matchstr(line, '^.\{-}\ze\s*$')
324endfunc
325
326" Stops the shell running in terminal "buf".
327func Stop_shell_in_terminal(buf)
328  call term_sendkeys(a:buf, "exit\r")
329  let job = term_getjob(a:buf)
330  call WaitFor({-> job_status(job) == "dead"})
331endfunc
332