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