patch 9.1.1525: tests: testdir/ is a bit messy
Problem: tests: testdir is a bit messy
Solution: move test scripts into testdir/util/ directory
src/testdir/ has become a dumping ground mixing test cases with utility
functions. Let's fix this by moving all utility functions into the
testdir/util/ directory
Also a few related changes had to be done:
- Update Filelist
- update README.txt and mention the new directory layout
- fix shadowbuild by linking the util directory into the shadow dir
closes: #17677
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/testdir/util/amiga.vim b/src/testdir/util/amiga.vim
new file mode 100644
index 0000000..2837fe5
--- /dev/null
+++ b/src/testdir/util/amiga.vim
@@ -0,0 +1,6 @@
+" Settings for test script execution
+set shell=csh
+map! /tmp t:
+cmap !rm !Delete all
+
+source util/setup.vim
diff --git a/src/testdir/util/check.vim b/src/testdir/util/check.vim
new file mode 100644
index 0000000..aa8eceb
--- /dev/null
+++ b/src/testdir/util/check.vim
@@ -0,0 +1,334 @@
+source util/shared.vim
+source util/term_util.vim
+
+" uses line-continuation
+let s:cpo_save = &cpo
+set cpo&vim
+
+command -nargs=1 MissingFeature throw 'Skipped: ' .. <args> .. ' feature missing'
+
+" Command to check for the presence of a feature.
+command -nargs=1 CheckFeature call CheckFeature(<f-args>)
+func CheckFeature(name)
+ if !has(a:name, 1)
+ throw 'Checking for non-existent feature ' .. a:name
+ endif
+ if !has(a:name)
+ MissingFeature a:name
+ endif
+endfunc
+
+" Command to check for the absence of a feature.
+command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>)
+func CheckNotFeature(name)
+ if !has(a:name, 1)
+ throw 'Checking for non-existent feature ' .. a:name
+ endif
+ if has(a:name)
+ throw 'Skipped: ' .. a:name .. ' feature present'
+ endif
+endfunc
+
+" Command to check for the presence of a working option.
+command -nargs=1 CheckOption call CheckOption(<f-args>)
+func CheckOption(name)
+ if !exists('&' .. a:name)
+ throw 'Checking for non-existent option ' .. a:name
+ endif
+ if !exists('+' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' option not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a built-in function.
+command -nargs=1 CheckFunction call CheckFunction(<f-args>)
+func CheckFunction(name)
+ if !exists('?' .. a:name)
+ throw 'Checking for non-existent function ' .. a:name
+ endif
+ if !exists('*' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' function missing'
+ endif
+endfunc
+
+" Command to check for the presence of an Ex command
+command -nargs=1 CheckCommand call CheckCommand(<f-args>)
+func CheckCommand(name)
+ if !exists(':' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' command not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a shell command
+command -nargs=1 CheckExecutable call CheckExecutable(<f-args>)
+func CheckExecutable(name)
+ if !executable(a:name)
+ throw 'Skipped: ' .. a:name .. ' program not executable'
+ endif
+endfunc
+
+" Command to check for the presence of python. Argument should have been
+" obtained with PythonProg()
+func CheckPython(name)
+ if a:name == ''
+ throw 'Skipped: python command not available'
+ endif
+endfunc
+
+" Command to check for running on MS-Windows
+command CheckMSWindows call CheckMSWindows()
+func CheckMSWindows()
+ if !has('win32')
+ throw 'Skipped: only works on MS-Windows'
+ endif
+endfunc
+
+" Command to check for NOT running on MS-Windows
+command CheckNotMSWindows call CheckNotMSWindows()
+func CheckNotMSWindows()
+ if has('win32')
+ throw 'Skipped: does not work on MS-Windows'
+ endif
+endfunc
+
+" Command to check for running on Unix
+command CheckUnix call CheckUnix()
+func CheckUnix()
+ if !has('unix')
+ throw 'Skipped: only works on Unix'
+ endif
+endfunc
+
+" Command to check for running on Linux
+command CheckLinux call CheckLinux()
+func CheckLinux()
+ if !has('linux')
+ throw 'Skipped: only works on Linux'
+ endif
+endfunc
+
+" Command to check for not running on a BSD system.
+command CheckNotBSD call CheckNotBSD()
+func CheckNotBSD()
+ if has('bsd')
+ throw 'Skipped: does not work on BSD'
+ endif
+endfunc
+
+" Command to check for not running on a MacOS
+command CheckNotMac call CheckNotMac()
+func CheckNotMac()
+ if has('mac')
+ throw 'Skipped: does not work on MacOS'
+ endif
+endfunc
+
+" Command to check for not running on a MacOS M1 system.
+command CheckNotMacM1 call CheckNotMacM1()
+func CheckNotMacM1()
+ if has('mac') && system('uname -a') =~ '\<arm64\>'
+ throw 'Skipped: does not work on MacOS M1'
+ endif
+endfunc
+
+func SetupWindowSizeToForVisualDumps()
+ " The dumps used as reference in these tests were created with a terminal
+ " width of 75 columns. The vim window that uses the remainder of the GUI
+ " window width must be at least 3 columns. In theory this means we need the
+ " GUI shell to provide 78+ columns. However the GTK3 resize logic is flaky,
+ " sometimes resulting in X11 Configure events that are narrower than
+ " expected by a number of pixels equal to 2 column widths. Therefore
+ " setting 80 columns ensures that the GUI shell can still provide 78+
+ " columns. This is very likely papering over a GTK3 resize bug but one that
+ " has existed for a very long time. Establishing this workaround is meant to
+ " get the GTK3 code working under CI so that we can focus on removing this
+ " over the long term.
+ if &columns != 80
+ set columns=80
+ endif
+ " Without resetting lines, some GTK3 resize events can carry over between
+ " tests, which invalidate assumptions in the scrollbar offset calculations.
+ if &lines != 25
+ set lines=25
+ endif
+endfunc
+
+" Command to check that making screendumps is supported.
+" Caller must source util/screendump.vim
+command CheckScreendump call CheckScreendump()
+func CheckScreendump()
+ let g:check_screendump_called = v:true
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ if has('gui_running')
+ call SetupWindowSizeToForVisualDumps()
+ endif
+endfunc
+
+" Command to check that we can Run Vim in a terminal window
+command CheckRunVimInTerminal call CheckRunVimInTerminal()
+func CheckRunVimInTerminal()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+endfunc
+
+" Command to check that we can run the GUI
+command CheckCanRunGui call CheckCanRunGui()
+func CheckCanRunGui()
+ if !has('gui') || ($DISPLAY == "" && !has('gui_running'))
+ throw 'Skipped: cannot start the GUI'
+ endif
+endfunc
+
+" Command to Check for an environment variable
+command -nargs=1 CheckEnv call CheckEnv(<f-args>)
+func CheckEnv(name)
+ if empty(eval('$' .. a:name))
+ throw 'Skipped: Environment variable ' .. a:name .. ' is not set'
+ endif
+endfunc
+
+" Command to Check for pure X11 (no Wayland)
+command -nargs=0 CheckX11 call CheckX11()
+func CheckX11()
+ if !empty($WAYLAND_DISPLAY) || empty($DISPLAY)
+ throw 'Skipped: not pure X11 environment'
+ endif
+endfunc
+
+" Command to check that we are using the GUI
+command CheckGui call CheckGui()
+func CheckGui()
+ if !has('gui_running')
+ throw 'Skipped: only works in the GUI'
+ endif
+endfunc
+
+" Command to check that not currently using the GUI
+command CheckNotGui call CheckNotGui()
+func CheckNotGui()
+ if has('gui_running')
+ throw 'Skipped: only works in the terminal'
+ endif
+endfunc
+
+" Command to check that test is not running as root
+command CheckNotRoot call CheckNotRoot()
+func CheckNotRoot()
+ if IsRoot()
+ throw 'Skipped: cannot run test as root'
+ endif
+endfunc
+
+" Command to check that the current language is English
+command CheckEnglish call CheckEnglish()
+func CheckEnglish()
+ if v:lang != "C" && v:lang !~ '^[Ee]n'
+ throw 'Skipped: only works in English language environment'
+ endif
+endfunc
+
+" Command to check that loopback device has IPv6 address
+command CheckIPv6 call CheckIPv6()
+func CheckIPv6()
+ if !has('ipv6')
+ throw 'Skipped: cannot use IPv6 networking'
+ endif
+ if !exists('s:ipv6_loopback')
+ let s:ipv6_loopback = s:CheckIPv6Loopback()
+ endif
+ if !s:ipv6_loopback
+ throw 'Skipped: no IPv6 address for loopback device'
+ endif
+endfunc
+
+func s:CheckIPv6Loopback()
+ if has('win32')
+ return system('netsh interface ipv6 show interface') =~? '\<Loopback\>'
+ elseif filereadable('/proc/net/if_inet6')
+ return (match(readfile('/proc/net/if_inet6'), '\slo$') >= 0)
+ elseif executable('ifconfig')
+ for dev in ['lo0', 'lo', 'loop']
+ " NOTE: On SunOS, need specify address family 'inet6' to get IPv6 info.
+ if system('ifconfig ' .. dev .. ' inet6 2>/dev/null') =~? '\<inet6\>'
+ \ || system('ifconfig ' .. dev .. ' 2>/dev/null') =~? '\<inet6\>'
+ return v:true
+ endif
+ endfor
+ else
+ " TODO: How to check it in other platforms?
+ endif
+ return v:false
+endfunc
+
+" Command to check for not running under ASAN
+command CheckNotAsan call CheckNotAsan()
+func CheckNotAsan()
+ if execute('version') =~# '-fsanitize=[a-z,]*\<address\>'
+ throw 'Skipped: does not work with ASAN'
+ endif
+endfunc
+
+" Command to check for not running under valgrind
+command CheckNotValgrind call CheckNotValgrind()
+func CheckNotValgrind()
+ if RunningWithValgrind()
+ throw 'Skipped: does not work well with valgrind'
+ endif
+endfunc
+
+" Command to check for X11 based GUI
+command CheckX11BasedGui call CheckX11BasedGui()
+func CheckX11BasedGui()
+ if !g:x11_based_gui
+ throw 'Skipped: requires X11 based GUI'
+ endif
+endfunc
+
+" Command to check that there are two clipboards
+command CheckTwoClipboards call CheckTwoClipboards()
+func CheckTwoClipboards()
+ " avoid changing the clipboard here, only X11 supports both
+ if !has('X11')
+ throw 'Skipped: requires two clipboards'
+ endif
+endfunc
+
+" Command to check for satisfying any of the conditions.
+" e.g. CheckAnyOf Feature:bsd Feature:sun Linux
+command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>)
+func CheckAnyOf(...)
+ let excp = []
+ for arg in a:000
+ try
+ exe 'Check' .. substitute(arg, ':', ' ', '')
+ return
+ catch /^Skipped:/
+ let excp += [substitute(v:exception, '^Skipped:\s*', '', '')]
+ endtry
+ endfor
+ throw 'Skipped: ' .. join(excp, '; ')
+endfunc
+
+" Command to check for satisfying all of the conditions.
+" e.g. CheckAllOf Unix Gui Option:ballooneval
+command -nargs=+ CheckAllOf call CheckAllOf(<f-args>)
+func CheckAllOf(...)
+ for arg in a:000
+ exe 'Check' .. substitute(arg, ':', ' ', '')
+ endfor
+endfunc
+
+" Check if running under Github Actions
+command CheckGithubActions call CheckGithubActions()
+func CheckGithubActions()
+ if expand('$GITHUB_ACTIONS') ==# 'true'
+ throw "Skipped: FIXME: this test doesn't work on Github Actions CI"
+ endif
+endfunc
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/util/color_ramp.vim b/src/testdir/util/color_ramp.vim
new file mode 100644
index 0000000..8eed8f9
--- /dev/null
+++ b/src/testdir/util/color_ramp.vim
@@ -0,0 +1,85 @@
+" Script to generate a file that shows al 256 xterm colors
+
+new
+let lnum = 1
+
+" | in original color pair to see white background.
+let trail_bar = "\033[m|"
+
+" ANSI colors
+call setline(lnum, 'ANSI background')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 7)
+ let s .= "\033[4" . nr . "m "
+endfor
+for nr in range(8, 15)
+ let s .= "\033[10" . (nr - 8) . "m "
+endfor
+let s .= trail_bar
+
+call setline(lnum, s)
+let lnum += 1
+
+" ANSI text colors
+call setline(lnum, 'ANSI text')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 7)
+ let s .= "\033[0;3" . nr . "mxxxx"
+endfor
+for nr in range(8, 15)
+ let s .= "\033[0;9" . (nr - 8) . "mxxxx"
+endfor
+let s .= trail_bar
+
+call setline(lnum, s)
+let lnum += 1
+
+" ANSI with bold text
+call setline(lnum, 'ANSI bold text')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 7)
+ let s .= "\033[1;3" . nr . "mxxxx"
+endfor
+for nr in range(8, 15)
+ let s .= "\033[1;9" . (nr - 8) . "mxxxx"
+endfor
+let s .= trail_bar
+
+call setline(lnum, s)
+let lnum += 1
+
+" 6 x 6 x 6 color cube
+call setline(lnum, 'color cube')
+let lnum += 1
+
+for high in range(0, 5)
+ let s = ''
+ for low in range(0, 35)
+ let nr = low + high * 36
+ let s .= "\033[48;5;" . (nr + 16) . "m "
+ endfor
+ let s .= trail_bar
+ call setline(lnum + high, s)
+endfor
+let lnum += 6
+
+" 24 shades of grey
+call setline(lnum, 'grey ramp')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 23)
+ let s .= "\033[48;5;" . (nr + 232) . "m "
+endfor
+let s .= trail_bar
+call setline(lnum, s)
+
+set binary
+write! <sfile>:h/color_ramp.txt
+quit
diff --git a/src/testdir/util/dos.vim b/src/testdir/util/dos.vim
new file mode 100644
index 0000000..3134d34
--- /dev/null
+++ b/src/testdir/util/dos.vim
@@ -0,0 +1,9 @@
+" Settings for test script execution
+" Always use "COMMAND.COM", don't use the value of "$SHELL".
+set shell=c:\COMMAND.COM shellquote= shellxquote= shellcmdflag=/c shellredir=>
+" This is used only when the +eval feature is available.
+if executable("cmd.exe")
+ set shell=cmd.exe shellcmdflag=/D\ /c
+endif
+
+source util/setup.vim
diff --git a/src/testdir/util/gen_opt_test.vim b/src/testdir/util/gen_opt_test.vim
new file mode 100644
index 0000000..1e0f39c
--- /dev/null
+++ b/src/testdir/util/gen_opt_test.vim
@@ -0,0 +1,517 @@
+" Script to generate src/testdir/opt_test.vim from src/optiondefs.h and
+" runtime/doc/options.txt
+
+set cpo&vim
+
+" Only do this when build with the +eval feature.
+if 1
+
+try
+
+set nomore
+
+const K_KENTER = -16715
+
+" Get global-local options.
+" "key" is full-name of the option.
+" "value" is the local value to switch back to the global value.
+b options.txt
+call cursor(1, 1)
+let global_locals = {}
+while search("^'[^']*'.*\\n.*|global-local", 'W')
+ let fullname = getline('.')->matchstr("^'\\zs[^']*")
+ let global_locals[fullname] = ''
+endwhile
+call extend(global_locals, #{
+ \ scrolloff: -1,
+ \ sidescrolloff: -1,
+ \ undolevels: -123456,
+ \})
+
+" Get local-noglobal options.
+" "key" is full-name of the option.
+" "value" is no used.
+b options.txt
+call cursor(1, 1)
+let local_noglobals = {}
+while search("^'[^']*'.*\\n.*|local-noglobal", 'W')
+ let fullname = getline('.')->matchstr("^'\\zs[^']*")
+ let local_noglobals[fullname] = v:true
+endwhile
+
+" Options to skip `setglobal` tests.
+" "key" is full-name of the option.
+" "value" is the reason.
+let skip_setglobal_reasons = #{
+ \ iminsert: 'The global value is always overwritten by the local value',
+ \ imsearch: 'The global value is always overwritten by the local value',
+ \}
+
+" Script header.
+" The test values contains multibyte characters.
+let script = [
+ \ '" DO NOT EDIT: Generated with util/gen_opt_test.vim',
+ \ '" Used by test_options_all.vim.',
+ \ '',
+ \ 'scriptencoding utf-8',
+ \ ]
+
+b optiondefs.h
+const end = search('#define p_term', 'nw')
+
+" font name that works everywhere (hopefully)
+let fontname = has('win32') ? 'fixedsys' : 'fixed'
+
+" Two lists with values: values that work and values that fail.
+" When not listed, "othernum" or "otherstring" is used.
+" When both lists are empty, skip tests for the option.
+" For boolean options, if non-empty a fixed test will be run, otherwise skipped.
+let test_values = {
+ "\ boolean options
+ \ 'termguicolors': [
+ \ has('vtp') && !has('vcon') && !has('gui_running') ? [] : [1],
+ \ []],
+ \
+ "\ number options
+ \ 'chistory': [[1, 2, 10, 50], [1000, -1]],
+ \ 'cmdheight': [[1, 2, 10], [-1, 0]],
+ \ 'cmdwinheight': [[1, 2, 10], [-1, 0]],
+ \ 'columns': [[12, 80, 10000], [-1, 0, 10]],
+ \ 'conceallevel': [[0, 1, 2, 3], [-1, 4, 99]],
+ \ 'foldcolumn': [[0, 1, 4, 12], [-1, 13, 999]],
+ \ 'helpheight': [[0, 10, 100], [-1]],
+ \ 'history': [[0, 1, 100, 10000], [-1, 10001]],
+ \ 'iminsert': [[0, 1, 2], [-1, 3, 999]],
+ \ 'imsearch': [[-1, 0, 1, 2], [-2, 3, 999]],
+ \ 'imstyle': [[0, 1], [-1, 2, 999]],
+ \ 'lhistory': [[1, 2, 10, 50], [1000, -1]],
+ \ 'lines': [[2, 24, 1000], [-1, 0, 1]],
+ \ 'linespace': [[-1, 0, 2, 4, 999], ['']],
+ \ 'numberwidth': [[1, 4, 8, 10, 11, 20], [-1, 0, 21]],
+ \ 'regexpengine': [[0, 1, 2], [-1, 3, 999]],
+ \ 'report': [[0, 1, 2, 9999], [-1]],
+ \ 'scroll': [[0, 1, 2, 15], [-1, 999]],
+ \ 'scrolljump': [[-100, -1, 0, 1, 2, 15], [-101, 999]],
+ \ 'scrolloff': [[0, 1, 8, 999], [-1]],
+ \ 'shiftwidth': [[0, 1, 8, 999], [-1]],
+ \ 'showtabpanel': [[0, 1, 2], []],
+ \ 'sidescroll': [[0, 1, 8, 999], [-1]],
+ \ 'sidescrolloff': [[0, 1, 8, 999], [-1]],
+ \ 'tabstop': [[1, 4, 8, 12, 9999], [-1, 0, 10000]],
+ \ 'termwinscroll': [[1, 100, 99999], [-1, 0]],
+ \ 'textwidth': [[0, 1, 8, 99], [-1]],
+ \ 'timeoutlen': [[0, 8, 99999], [-1]],
+ \ 'titlelen': [[0, 1, 8, 9999], [-1]],
+ \ 'updatecount': [[0, 1, 8, 9999], [-1]],
+ \ 'updatetime': [[0, 1, 8, 9999], [-1]],
+ \ 'verbose': [[-1, 0, 1, 8, 9999], ['']],
+ \ 'wildchar': [[-1, 0, 100, 'x', '^Y', '^@', '<Esc>', '<t_xx>', '<', '^'],
+ \ ['', 'xxx', '<xxx>', '<t_xxx>', '<Esc', '<t_xx', '<C-C>',
+ \ '<NL>', '<CR>', K_KENTER]],
+ \ 'wildcharm': [[-1, 0, 100, 'x', '^Y', '^@', '<Esc>', '<', '^'],
+ \ ['', 'xxx', '<xxx>', '<t_xxx>', '<Esc', '<t_xx', '<C-C>',
+ \ '<NL>', '<CR>', K_KENTER]],
+ \ 'winheight': [[1, 10, 999], [-1, 0]],
+ \ 'winminheight': [[0, 1], [-1]],
+ \ 'winminwidth': [[0, 1, 10], [-1]],
+ \ 'winwidth': [[1, 10, 999], [-1, 0]],
+ \ 'wltimeoutlen': [[1, 10, 999],[-1]],
+ \
+ "\ string options
+ \ 'ambiwidth': [['', 'single', 'double'], ['xxx']],
+ \ 'background': [['', 'light', 'dark'], ['xxx']],
+ \ 'backspace': [[0, 1, 2, 3, '', 'indent', 'eol', 'start', 'nostop',
+ \ 'eol,start', 'indent,eol,nostop'],
+ \ [-1, 4, 'xxx']],
+ \ 'backupcopy': [['yes', 'no', 'auto'], ['', 'xxx', 'yes,no']],
+ \ 'backupext': [['xxx'], [&patchmode, '*']],
+ \ 'belloff': [['', 'all', 'backspace', 'cursor', 'complete', 'copy',
+ \ 'ctrlg', 'error', 'esc', 'ex', 'hangul', 'insertmode', 'lang',
+ \ 'mess', 'showmatch', 'operator', 'register', 'shell', 'spell',
+ \ 'term', 'wildmode', 'copy,error,shell'],
+ \ ['xxx']],
+ \ 'breakindentopt': [['', 'min:3', 'shift:4', 'shift:-2', 'sbr', 'list:5',
+ \ 'list:-1', 'column:10', 'column:-5', 'min:1,sbr,shift:2'],
+ \ ['xxx', 'min', 'min:x', 'min:-1', 'shift:x', 'sbr:1', 'list:x',
+ \ 'column:x']],
+ \ 'browsedir': [['', 'last', 'buffer', 'current', './Xdir\ with\ space'],
+ \ ['xxx']],
+ \ 'bufhidden': [['', 'hide', 'unload', 'delete', 'wipe'],
+ \ ['xxx', 'hide,wipe']],
+ \ 'buftype': [['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help',
+ \ 'terminal', 'prompt', 'popup'],
+ \ ['xxx', 'help,nofile']],
+ \ 'casemap': [['', 'internal', 'keepascii', 'internal,keepascii'],
+ \ ['xxx']],
+ \ 'cedit': [['', '^Y', '^@', '<Esc>', '<t_xx>'],
+ \ ['xxx', 'f', '<xxx>', '<t_xxx>', '<Esc', '<t_xx']],
+ \ 'clipboard': [['', 'unnamed', 'unnamedplus', 'autoselect',
+ \ 'autoselectplus', 'autoselectml', 'html', 'exclude:vimdisplay',
+ \ 'autoselect,unnamed', 'unnamed,exclude:.*'],
+ \ ['xxx', 'exclude:\\ze*', 'exclude:\\%(']],
+ \ 'clipmethod': [['wayland', 'x11', 'wayland,x11', ''],['xxx', '--', 'wayland,,', ',x11']],
+ \ 'colorcolumn': [['', '8', '+2', '1,+1,+3'], ['xxx', '-a', '1,', '1;']],
+ \ 'comments': [['', 'b:#', 'b:#,:%'], ['xxx', '-']],
+ \ 'commentstring': [['', '/*\ %s\ */'], ['xxx']],
+ \ 'complete': [['', '.', 'w', 'b', 'u', 'U', 'i', 'd', ']', 't',
+ \ 'k', 'kspell', 'k/tmp/dir\\\ with\\\ space/*',
+ \ 's', 's/tmp/dir\\\ with\\\ space/*',
+ \ 'w,b,k/tmp/dir\\\ with\\\ space/*,s'],
+ \ ['xxx']],
+ \ 'concealcursor': [['', 'n', 'v', 'i', 'c', 'nvic'], ['xxx']],
+ \ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup',
+ \ 'popuphidden', 'noinsert', 'noselect', 'fuzzy', "preinsert", 'menu,longest'],
+ \ ['xxx', 'menu,,,longest,']],
+ \ 'completefuzzycollect': [['', 'keyword', 'files', 'whole_line',
+ \ 'keyword,whole_line', 'files,whole_line', 'keyword,files,whole_line'],
+ \ ['xxx', 'keyword,,,whole_line,']],
+ \ 'completeitemalign': [['abbr,kind,menu', 'menu,abbr,kind'],
+ \ ['', 'xxx', 'abbr', 'abbr,menu', 'abbr,menu,kind,abbr',
+ \ 'abbr1234,kind,menu']],
+ \ 'completepopup': [['', 'height:13', 'width:20', 'highlight:That',
+ \ 'align:item', 'align:menu', 'border:on', 'border:off',
+ \ 'width:10,height:234,highlight:Mine'],
+ \ ['xxx', 'xxx:99', 'height:yes', 'width:no', 'align:xxx',
+ \ 'border:maybe', 'border:1', 'border:']],
+ \ 'completeslash': [['', 'slash', 'backslash'], ['xxx']],
+ \ 'cryptmethod': [['', 'zip'], ['xxx']],
+ \ 'cscopequickfix': [['', 's-', 'g-', 'd-', 'c-', 't-', 'e-', 'f-', 'i-',
+ \ 'a-', 's-,c+,e0'],
+ \ ['xxx', 's,g,d']],
+ \ 'cursorlineopt': [['both', 'line', 'number', 'screenline',
+ \ 'line,number'],
+ \ ['', 'xxx', 'line,screenline']],
+ \ 'debug': [['', 'msg', 'throw', 'beep'], ['xxx']],
+ \ 'diffopt': [['', 'filler', 'context:0', 'context:999', 'iblank',
+ \ 'icase', 'iwhite', 'iwhiteall', 'horizontal', 'vertical',
+ \ 'closeoff', 'hiddenoff', 'foldcolumn:0', 'foldcolumn:12',
+ \ 'followwrap', 'internal', 'indent-heuristic', 'algorithm:myers',
+ \ 'icase,iwhite', 'algorithm:minimal', 'algorithm:patience',
+ \ 'algorithm:histogram', 'inline:none', 'inline:simple',
+ \ 'inline:char', 'inline:word', 'inline:char,inline:word', 'linematch:5'],
+ \ ['xxx', 'foldcolumn:', 'foldcolumn:x', 'foldcolumn:xxx',
+ \ 'linematch:', 'linematch:x', 'linematch:xxx', 'algorithm:',
+ \ 'algorithm:xxx', 'context:', 'context:x', 'context:xxx',
+ \ 'inline:xxx']],
+ \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'],
+ \ ['xxx']],
+ \ 'eadirection': [['', 'both', 'ver', 'hor'], ['xxx', 'ver,hor']],
+ \ 'encoding': [['latin1'], ['xxx', '']],
+ \ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter', 'all,-WinLeave'],
+ \ ['xxx']],
+ \ 'eventignorewin': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter', 'all,-WinLeave'],
+ \ ['xxx', 'WinNew']],
+ \ 'fileencoding': [['', 'latin1', 'xxx'], []],
+ \ 'fileformat': [['', 'dos', 'unix', 'mac'], ['xxx']],
+ \ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']],
+ \ 'fillchars': [['', 'stl:x', 'stlnc:x', 'vert:x', 'fold:x', 'foldopen:x',
+ \ 'foldclose:x', 'foldsep:x', 'diff:x', 'eob:x', 'lastline:x',
+ \ 'trunc:_', 'trunc:_,eob:x,trunc:_',
+ \ 'stl:\ ,vert:\|,fold:\\,trunc:…,diff:x'],
+ \ ['xxx', 'vert:', 'trunc:', "trunc:\b"]],
+ \ 'foldclose': [['', 'all'], ['xxx']],
+ \ 'foldmethod': [['manual', 'indent', 'expr', 'marker', 'syntax', 'diff'],
+ \ ['', 'xxx', 'expr,diff']],
+ \ 'foldopen': [['', 'all', 'block', 'hor', 'insert', 'jump', 'mark',
+ \ 'percent', 'quickfix', 'search', 'tag', 'undo', 'hor,jump'],
+ \ ['xxx']],
+ \ 'foldmarker': [['((,))'], ['', 'xxx', '{{{,']],
+ \ 'formatoptions': [['', 't', 'c', 'r', 'o', '/', 'q', 'w', 'a', 'n', '2',
+ \ 'v', 'b', 'l', 'm', 'M', 'B', '1', ']', 'j', 'p', 'vt', 'v,t'],
+ \ ['xxx']],
+ \ 'guicursor': [['', 'n:block-Cursor'], ['xxx']],
+ \ 'guifont': [['', fontname], []],
+ \ 'guifontwide': [['', fontname], []],
+ \ 'guifontset': [['', fontname], []],
+ \ 'guioptions': [['', '!', 'a', 'P', 'A', 'c', 'd', 'e', 'f', 'i', 'm',
+ \ 'M', 'g', 't', 'T', 'r', 'R', 'l', 'L', 'b', 'h', 'v', 'p', 'F',
+ \ 'k', '!abvR'],
+ \ ['xxx', 'a,b']],
+ \ 'helplang': [['', 'de', 'de,it'], ['xxx']],
+ \ 'highlight': [['', 'e:Error'], ['xxx']],
+ \ 'imactivatekey': [['', 'S-space'], ['xxx']],
+ \ 'isfname': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'isident': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'isexpand': [['', '.,->', '/,/*,\\,'], [',,', '\\,,']],
+ \ 'iskeyword': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'isprint': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'jumpoptions': [['', 'stack'], ['xxx']],
+ \ 'keymap': [['', 'accents'], ['/']],
+ \ 'keymodel': [['', 'startsel', 'stopsel', 'startsel,stopsel'], ['xxx']],
+ \ 'keyprotocol': [['', 'xxx:none', 'yyy:mok2', 'zzz:kitty'],
+ \ ['xxx', ':none', 'xxx:', 'x:non', 'y:mok3', 'z:kittty']],
+ \ 'langmap': [['', 'xX', 'aA,bB'], ['xxx']],
+ \ 'lispoptions': [['', 'expr:0', 'expr:1'], ['xxx', 'expr:x', 'expr:']],
+ \ 'listchars': [['', 'eol:x', 'tab:xy', 'tab:xyz', 'space:x',
+ \ 'multispace:xxxy', 'lead:x', 'leadmultispace:xxxy', 'trail:x',
+ \ 'extends:x', 'precedes:x', 'conceal:x', 'nbsp:x', 'eol:\\x24',
+ \ 'eol:\\u21b5', 'eol:\\U000021b5', 'eol:x,space:y'],
+ \ ['xxx', 'eol:']],
+ \ 'matchpairs': [['', '(:)', '(:),<:>'], ['xxx']],
+ \ 'messagesopt': [['hit-enter,history:1', 'hit-enter,history:10000',
+ \ 'history:100,wait:100', 'history:0,wait:0',
+ \ 'hit-enter,history:1,wait:1'],
+ \ ['xxx', 'history:500', 'hit-enter,history:-1',
+ \ 'hit-enter,history:10001', 'history:0,wait:10001',
+ \ 'hit-enter', 'history:10,wait:99999999999999999999',
+ \ 'history:99999999999999999999,wait:10', 'wait:10',
+ \ 'history:-10', 'history:10,wait:-10']],
+ \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']],
+ \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'],
+ \ ['xxx', 'n,v,i']],
+ \ 'mousemodel': [['', 'extend', 'popup', 'popup_setpos'], ['xxx']],
+ \ 'mouseshape': [['', 'n:arrow'], ['xxx']],
+ \ 'nrformats': [['', 'alpha', 'octal', 'hex', 'bin', 'unsigned', 'blank',
+ \ 'alpha,hex,bin'],
+ \ ['xxx']],
+ \ 'patchmode': [['', 'xxx', '.x'], [&backupext, '*']],
+ \ 'previewpopup': [['', 'height:13', 'width:20', 'highlight:That',
+ \ 'align:item', 'align:menu', 'border:on', 'border:off',
+ \ 'width:10,height:234,highlight:Mine'],
+ \ ['xxx', 'xxx:99', 'height:yes', 'width:no', 'align:xxx',
+ \ 'border:maybe', 'border:1', 'border:']],
+ \ 'printmbfont': [['', 'r:some', 'b:some', 'i:some', 'o:some', 'c:yes',
+ \ 'c:no', 'a:yes', 'a:no', 'b:Bold,c:yes'],
+ \ ['xxx', 'xxx,c:yes', 'xxx:', 'xxx:,c:yes']],
+ \ 'printoptions': [['', 'header:0', 'left:10pc,top:5pc'],
+ \ ['xxx', 'header:-1']],
+ \ 'scrollopt': [['', 'ver', 'hor', 'jump', 'ver,hor'], ['xxx']],
+ \ 'renderoptions': [[''], ['xxx']],
+ \ 'rightleftcmd': [['search'], ['xxx']],
+ \ 'rulerformat': [['', 'xxx'], ['%-', '%(', '%15(%%']],
+ \ 'selection': [['old', 'inclusive', 'exclusive'], ['', 'xxx']],
+ \ 'selectmode': [['', 'mouse', 'key', 'cmd', 'key,cmd'], ['xxx']],
+ \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir',
+ \ 'help,options,slash'],
+ \ ['xxx', 'curdir,sesdir']],
+ \ 'showcmdloc': [['', 'last', 'statusline', 'tabline'], ['xxx']],
+ \ 'signcolumn': [['', 'auto', 'no', 'yes', 'number'], ['xxx', 'no,yes']],
+ \ 'spellfile': [['', 'file.en.add', 'xxx.en.add,yyy.gb.add,zzz.ja.add',
+ \ '/tmp/dir\ with\ space/en.utf-8.add',
+ \ '/tmp/dir\\,with\\,comma/en.utf-8.add'],
+ \ ['xxx', '/tmp/file', '/tmp/dir*with:invalid?char/file.en.add',
+ \ ',file.en.add', 'xxx,yyy.en.add', 'xxx.en.add,yyy,zzz.ja.add']],
+ \ 'spelllang': [['', 'xxx', 'sr@latin'], ['not&lang', "that\\\rthere"]],
+ \ 'spelloptions': [['', 'camel'], ['xxx']],
+ \ 'spellsuggest': [['', 'best', 'double', 'fast', '100', 'timeout:100',
+ \ 'timeout:-1', 'file:/tmp/file', 'expr:Func()', 'double,33'],
+ \ ['xxx', '-1', 'timeout:', 'best,double', 'double,fast']],
+ \ 'splitkeep': [['', 'cursor', 'screen', 'topline'], ['xxx']],
+ \ 'statusline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']],
+ \ 'swapsync': [['', 'sync', 'fsync'], ['xxx']],
+ \ 'switchbuf': [['', 'useopen', 'usetab', 'split', 'vsplit', 'newtab',
+ \ 'uselast', 'split,newtab'],
+ \ ['xxx']],
+ \ 'tabclose': [['', 'left', 'uselast', 'left,uselast'], ['xxx']],
+ \ 'tabline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']],
+ \ 'tabpanel': [['', 'aaa', 'bbb'], []],
+ \ 'tabpanelopt': [['', 'align:left', 'align:right', 'vert', 'columns:0',
+ \ 'columns:20', 'columns:999'],
+ \ ['xxx', 'align:', 'align:middle', 'colomns:', 'cols:10',
+ \ 'cols:-1']],
+ \ 'tagcase': [['followic', 'followscs', 'ignore', 'match', 'smart'],
+ \ ['', 'xxx', 'smart,match']],
+ \ 'termencoding': [has('gui_gtk') ? [] : ['', 'utf-8'], ['xxx']],
+ \ 'termwinkey': [['', 'f', '^Y', '^@', '<Esc>', '<t_xx>', "\u3042", '<',
+ \ '^'],
+ \ ['<xxx>', '<t_xxx>', '<Esc', '<t_xx']],
+ \ 'termwinsize': [['', '24x80', '0x80', '32x0', '0x0'],
+ \ ['xxx', '80', '8ax9', '24x80b']],
+ \ 'termwintype': [['', 'winpty', 'conpty'], ['xxx']],
+ \ 'titlestring': [['', 'xxx', '%('], []],
+ \ 'toolbar': [['', 'icons', 'text', 'horiz', 'tooltips', 'icons,text'],
+ \ ['xxx']],
+ \ 'toolbariconsize': [['', 'tiny', 'small', 'medium', 'large', 'huge',
+ \ 'giant'],
+ \ ['xxx']],
+ \ 'ttymouse': [['', 'xterm'], ['xxx']],
+ \ 'varsofttabstop': [['8', '4,8,16,32'], ['xxx', '-1', '4,-1,20', '1,']],
+ \ 'vartabstop': [['8', '4,8,16,32'], ['xxx', '-1', '4,-1,20', '1,']],
+ \ 'verbosefile': [['', './Xfile'], []],
+ \ 'viewoptions': [['', 'cursor', 'folds', 'options', 'localoptions',
+ \ 'slash', 'unix', 'curdir', 'unix,slash'], ['xxx']],
+ \ 'viminfo': [['', '''50', '"30', "'100,<50,s10,h"], ['xxx', 'h']],
+ \ 'virtualedit': [['', 'block', 'insert', 'all', 'onemore', 'none',
+ \ 'NONE', 'all,block'],
+ \ ['xxx']],
+ \ 'whichwrap': [['', 'b', 's', 'h', 'l', '<', '>', '~', '[', ']', 'b,s',
+ \ 'bs'],
+ \ ['xxx']],
+ \ 'wildmode': [['', 'full', 'longest', 'list', 'lastused', 'list:full',
+ \ 'noselect', 'noselect,full', 'noselect:lastused,full',
+ \ 'full,longest', 'full,full,full,full'],
+ \ ['xxx', 'a4', 'full,full,full,full,full']],
+ \ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']],
+ \ 'winaltkeys': [['no', 'yes', 'menu'], ['', 'xxx']],
+ \
+ "\ skipped options
+ \ 'luadll': [[], []],
+ \ 'perldll': [[], []],
+ \ 'pythondll': [[], []],
+ \ 'pythonthreedll': [[], []],
+ \ 'pyxversion': [[], []],
+ \ 'rubydll': [[], []],
+ \ 'tcldll': [[], []],
+ \ 'term': [[], []],
+ \ 'ttytype': [[], []],
+ \
+ "\ default behaviours
+ \ 'othernum': [[-1, 0, 100], ['']],
+ \ 'otherstring': [['', 'xxx'], []],
+ \}
+
+" Two lists with values: values that pre- and post-processing in test.
+" Clear out t_WS: we don't want to resize the actual terminal.
+let test_prepost = {
+ \ 'browsedir': [["call mkdir('Xdir with space', 'D')"], []],
+ \ 'columns': [[
+ \ 'set t_WS=',
+ \ 'let save_columns = &columns'
+ \ ], [
+ \ 'let &columns = save_columns',
+ \ 'set t_WS&'
+ \ ]],
+ \ 'lines': [[
+ \ 'set t_WS=',
+ \ 'let save_lines = &lines'
+ \ ], [
+ \ 'let &lines = save_lines',
+ \ 'set t_WS&'
+ \ ]],
+ \ 'verbosefile': [[], ['call delete("Xfile")']],
+ \}
+
+let invalid_options = test_values->keys()
+ \->filter({-> v:val !~# '^other' && !exists($"&{v:val}")})
+for s:skip_option in [
+ \ [!has('tabpanel'), 'tabpanel'],
+ \ [!has('tabpanel'), 'tabpanelopt'],
+ \ [!has('tabpanel'), 'showtabpanel'],
+ \ ]
+ if s:skip_option[0]
+ call remove(invalid_options, s:skip_option[1])
+ endif
+endfor
+if !empty(invalid_options)
+ throw $"Invalid option name in test_values: '{invalid_options->join("', '")}'"
+endif
+
+1
+call search('struct vimoption options')
+while 1
+ if search('{"', 'W') > end
+ break
+ endif
+ let line = getline('.')
+ let fullname = substitute(line, '.*{"\([^"]*\)".*', '\1', '')
+ let shortname = substitute(line, '.*"\([^"]*\)".*', '\1', '')
+
+ let [valid_values, invalid_values] = test_values[
+ \ has_key(test_values, fullname) ? fullname
+ \ : line =~ 'P_NUM' ? 'othernum'
+ \ : 'otherstring']
+
+ if empty(valid_values) && empty(invalid_values)
+ continue
+ endif
+
+ call add(script, $"func Test_opt_set_{fullname}()")
+ call add(script, $"if exists('+{fullname}') && execute('set!') =~# '\\n..{fullname}\\([=\\n]\\|$\\)'")
+ call add(script, $"let l:saved = [&g:{fullname}, &l:{fullname}]")
+ call add(script, 'endif')
+
+ let [pre_processing, post_processing] = get(test_prepost, fullname, [[], []])
+ let script += pre_processing
+
+ " Setting an option can only fail when it's implemented.
+ call add(script, $"if exists('+{fullname}')")
+ if line =~ 'P_BOOL'
+ for opt in [fullname, shortname]
+ for cmd in ['set', 'setlocal', 'setglobal']
+ call add(script, $'{cmd} {opt}')
+ call add(script, $'{cmd} no{opt}')
+ call add(script, $'{cmd} inv{opt}')
+ call add(script, $'{cmd} {opt}!')
+ endfor
+ endfor
+ else " P_NUM || P_STRING
+ " Normal tests
+ for opt in [fullname, shortname]
+ for cmd in ['set', 'setlocal', 'setglobal']
+ for val in valid_values
+ if local_noglobals->has_key(fullname) && cmd ==# 'setglobal'
+ " Skip `:setglobal {option}={val}` for local-noglobal option.
+ " It has no effect.
+ let pre = '" Skip local-noglobal: '
+ else
+ let pre = ''
+ endif
+ call add(script, $'{pre}{cmd} {opt}={val}')
+ endfor
+ endfor
+ " Testing to clear the local value and switch back to the global value.
+ if global_locals->has_key(fullname)
+ let switchback_val = global_locals[fullname]
+ call add(script, $'setlocal {opt}={switchback_val}')
+ call add(script, $'call assert_equal(&g:{fullname}, &{fullname})')
+ endif
+ endfor
+
+ " Failure tests
+ for opt in [fullname, shortname]
+ for cmd in ['set', 'setlocal', 'setglobal']
+ for val in invalid_values
+ if val is# global_locals->get(fullname, {}) && cmd ==# 'setlocal'
+ " Skip setlocal switchback-value to global-local option. It will
+ " not result in failure.
+ let pre = '" Skip global-local: '
+ elseif local_noglobals->has_key(fullname) && cmd ==# 'setglobal'
+ " Skip setglobal to local-noglobal option. It will not result in
+ " failure.
+ let pre = '" Skip local-noglobal: '
+ elseif skip_setglobal_reasons->has_key(fullname) && cmd ==# 'setglobal'
+ " Skip setglobal to reasoned option. It will not result in failure.
+ let reason = skip_setglobal_reasons[fullname]
+ let pre = $'" Skip {reason}: '
+ else
+ let pre = ''
+ endif
+ let cmdline = $'{cmd} {opt}={val}'
+ call add(script, $"{pre}silent! call assert_fails({string(cmdline)})")
+ endfor
+ endfor
+ endfor
+ endif
+
+ " Cannot change 'termencoding' in GTK
+ if fullname != 'termencoding' || !has('gui_gtk')
+ call add(script, $'set {fullname}&')
+ call add(script, $'set {shortname}&')
+ call add(script, $"if exists('l:saved')")
+ call add(script, $"let [&g:{fullname}, &l:{fullname}] = l:saved")
+ call add(script, 'endif')
+ endif
+
+ call add(script, "endif")
+
+ let script += post_processing
+ call add(script, 'endfunc')
+endwhile
+
+call writefile(script, 'opt_test.vim')
+
+" Write error messages if error occurs.
+catch
+ " Append errors to test.log
+ let error = $'Error: {v:exception} in {v:throwpoint}'
+ echoc error
+ split test.log
+ call append('$', error)
+ write
+endtry
+
+endif
+
+qa!
+
+" vim:sw=2:ts=8:noet:nosta:
diff --git a/src/testdir/util/gui_init.vim b/src/testdir/util/gui_init.vim
new file mode 100644
index 0000000..4fa6cbc
--- /dev/null
+++ b/src/testdir/util/gui_init.vim
@@ -0,0 +1,6 @@
+" gvimrc for test_gui_init.vim
+
+if has('gui_motif') || has('gui_gtk2') || has('gui_gtk3')
+ set guiheadroom=0
+ set guioptions+=p
+endif
diff --git a/src/testdir/util/gui_preinit.vim b/src/testdir/util/gui_preinit.vim
new file mode 100644
index 0000000..c351b72
--- /dev/null
+++ b/src/testdir/util/gui_preinit.vim
@@ -0,0 +1,7 @@
+" vimrc for test_gui_init.vim
+
+" Note that this flag must be added in the .vimrc file, before switching on
+" syntax or filetype recognition (when the |gvimrc| file is sourced the system
+" menu has already been loaded; the ":syntax on" and ":filetype on" commands
+" load the menu too).
+set guioptions+=M
diff --git a/src/testdir/util/mouse.vim b/src/testdir/util/mouse.vim
new file mode 100644
index 0000000..e2979b7
--- /dev/null
+++ b/src/testdir/util/mouse.vim
@@ -0,0 +1,372 @@
+" Helper functions for generating mouse events
+
+" xterm2 and sgr always work, urxvt is optional.
+let g:Ttymouse_values = ['xterm2', 'sgr']
+if has('mouse_urxvt')
+ call add(g:Ttymouse_values, 'urxvt')
+endif
+
+" dec doesn't support all the functionality
+if has('mouse_dec')
+ let g:Ttymouse_dec = ['dec']
+else
+ let g:Ttymouse_dec = []
+endif
+
+" netterm only supports left click
+if has('mouse_netterm')
+ let g:Ttymouse_netterm = ['netterm']
+else
+ let g:Ttymouse_netterm = []
+endif
+
+" Vim Mouse Codes.
+" Used by the GUI and by MS-Windows Consoles.
+" Keep these in sync with vim.h
+let s:MOUSE_CODE = {
+ \ 'BTN_LEFT' : 0x00,
+ \ 'BTN_MIDDLE' : 0x01,
+ \ 'BTN_RIGHT' : 0x02,
+ \ 'BTN_RELEASE' : 0x03,
+ \ 'BTN_X1' : 0x300,
+ \ 'BTN_X2' : 0x400,
+ \ 'SCRL_DOWN' : 0x100,
+ \ 'SCRL_UP' : 0x200,
+ \ 'SCRL_LEFT' : 0x500,
+ \ 'SCRL_RIGHT' : 0x600,
+ \ 'MOVE' : 0x700,
+ \ 'MOD_SHIFT' : 0x04,
+ \ 'MOD_ALT' : 0x08,
+ \ 'MOD_CTRL' : 0x10,
+ \ }
+
+
+" Helper function to emit a terminal escape code.
+func TerminalEscapeCode(code, row, col, m)
+ if &ttymouse ==# 'xterm2'
+ " need to use byte encoding here.
+ let str = list2str([a:code + 0x20, a:col + 0x20, a:row + 0x20])
+ if has('iconv')
+ let bytes = str->iconv('utf-8', 'latin1')
+ else
+ " Hopefully the numbers are not too big.
+ let bytes = str
+ endif
+ return "\<Esc>[M" .. bytes
+ elseif &ttymouse ==# 'sgr'
+ return printf("\<Esc>[<%d;%d;%d%s", a:code, a:col, a:row, a:m)
+ elseif &ttymouse ==# 'urxvt'
+ return printf("\<Esc>[%d;%d;%dM", a:code + 0x20, a:col, a:row)
+ endif
+endfunc
+
+func DecEscapeCode(code, down, row, col)
+ return printf("\<Esc>[%d;%d;%d;%d&w", a:code, a:down, a:row, a:col)
+endfunc
+
+func NettermEscapeCode(row, col)
+ return printf("\<Esc>}%d,%d\r", a:row, a:col)
+endfunc
+
+" Send low level mouse event to MS-Windows consoles or GUI
+func MSWinMouseEvent(button, row, col, move, multiclick, modifiers)
+ let args = { }
+ let args.button = a:button
+ " Scroll directions are inverted in the GUI, no idea why.
+ if has('gui_running')
+ if a:button == s:MOUSE_CODE.SCRL_UP
+ let args.button = s:MOUSE_CODE.SCRL_DOWN
+ elseif a:button == s:MOUSE_CODE.SCRL_DOWN
+ let args.button = s:MOUSE_CODE.SCRL_UP
+ elseif a:button == s:MOUSE_CODE.SCRL_LEFT
+ let args.button = s:MOUSE_CODE.SCRL_RIGHT
+ elseif a:button == s:MOUSE_CODE.SCRL_RIGHT
+ let args.button = s:MOUSE_CODE.SCRL_LEFT
+ endif
+ endif
+ let args.row = a:row
+ let args.col = a:col
+ let args.move = a:move
+ let args.multiclick = a:multiclick
+ let args.modifiers = a:modifiers
+ call test_mswin_event("mouse", args)
+ unlet args
+endfunc
+
+func MouseLeftClickCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(2, 4, a:row, a:col)
+ elseif &ttymouse ==# 'netterm'
+ return NettermEscapeCode(a:row, a:col)
+ else
+ return TerminalEscapeCode(0, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseLeftClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseLeftClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseMiddleClickCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(4, 2, a:row, a:col)
+ else
+ return TerminalEscapeCode(1, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseMiddleClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_MIDDLE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseMiddleClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseRightClickCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(6, 1, a:row, a:col)
+ else
+ return TerminalEscapeCode(2, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseRightClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RIGHT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseRightClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseCtrlLeftClickCode(row, col)
+ let ctrl = 0x10
+ return TerminalEscapeCode(0 + ctrl, a:row, a:col, 'M')
+endfunc
+
+func MouseCtrlLeftClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_CTRL)
+ else
+ call feedkeys(MouseCtrlLeftClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseCtrlRightClickCode(row, col)
+ let ctrl = 0x10
+ return TerminalEscapeCode(2 + ctrl, a:row, a:col, 'M')
+endfunc
+
+func MouseCtrlRightClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RIGHT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_CTRL)
+ else
+ call feedkeys(MouseCtrlRightClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseAltLeftClickCode(row, col)
+ let alt = 0x8
+ return TerminalEscapeCode(0 + alt, a:row, a:col, 'M')
+endfunc
+
+func MouseAltLeftClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_ALT)
+ else
+ call feedkeys(MouseAltLeftClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseAltRightClickCode(row, col)
+ let alt = 0x8
+ return TerminalEscapeCode(2 + alt, a:row, a:col, 'M')
+endfunc
+
+func MouseAltRightClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RIGHT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_ALT)
+ else
+ call feedkeys(MouseAltRightClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseLeftReleaseCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(3, 0, a:row, a:col)
+ elseif &ttymouse ==# 'netterm'
+ return ''
+ else
+ return TerminalEscapeCode(3, a:row, a:col, 'm')
+ endif
+endfunc
+
+func MouseLeftRelease(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RELEASE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseLeftReleaseCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseMiddleReleaseCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(5, 0, a:row, a:col)
+ else
+ return TerminalEscapeCode(3, a:row, a:col, 'm')
+ endif
+endfunc
+
+func MouseMiddleRelease(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RELEASE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseMiddleReleaseCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseRightReleaseCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(7, 0, a:row, a:col)
+ else
+ return TerminalEscapeCode(3, a:row, a:col, 'm')
+ endif
+endfunc
+
+func MouseRightRelease(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RELEASE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseRightReleaseCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseLeftDragCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(1, 4, a:row, a:col)
+ else
+ return TerminalEscapeCode(0x20, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseLeftDrag(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 1, 0, 0)
+ else
+ call feedkeys(MouseLeftDragCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelUpCode(row, col)
+ return TerminalEscapeCode(0x40, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelUp(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_UP, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelUpCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelDownCode(row, col)
+ return TerminalEscapeCode(0x41, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelDown(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_DOWN, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelDownCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelLeftCode(row, col)
+ return TerminalEscapeCode(0x42, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelLeft(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_LEFT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelLeftCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelRightCode(row, col)
+ return TerminalEscapeCode(0x43, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelRight(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_RIGHT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelRightCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelUpCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x40, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelUp(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_UP, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelUpCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelDownCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x41, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelDown(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_DOWN, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelDownCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelLeftCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x42, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelLeft(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_LEFT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelLeftCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelRightCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x43, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelRight(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_RIGHT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelRightCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/util/popupbounce.vim b/src/testdir/util/popupbounce.vim
new file mode 100644
index 0000000..5e63aca
--- /dev/null
+++ b/src/testdir/util/popupbounce.vim
@@ -0,0 +1,80 @@
+" Use this script to measure the redrawing performance when a popup is being
+" displayed. Usage with gcc:
+" cd src
+" # Edit Makefile to uncomment PROFILE_CFLAGS and PROFILE_LIBS
+" make reconfig
+" ./vim --clean -S testdir/util/popupbounce.vim main.c
+" gprof vim gmon.out | vim -
+
+" using line continuation
+set nocp
+
+" don't switch screens when quitting, so we can read the frames/sec
+set t_te=
+
+let winid = popup_create(['line1', 'line2', 'line3', 'line4'], {
+ \ 'line' : 1,
+ \ 'col' : 1,
+ \ 'zindex' : 101,
+ \ })
+redraw
+
+let start = reltime()
+let framecount = 0
+
+let line = 1.0
+let col = 1
+let downwards = 1
+let col_inc = 1
+let initial_speed = 0.2
+let speed = initial_speed
+let accel = 1.1
+let time = 0.1
+
+let countdown = 0
+
+while 1
+ if downwards
+ let speed += time * accel
+ let line += speed
+ else
+ let speed -= time * accel
+ let line -= speed
+ endif
+
+ if line + 3 >= &lines
+ let downwards = 0
+ let speed = speed * 0.8
+ let line = &lines - 3
+ endif
+ if !downwards && speed < 1.0
+ let downwards = 1
+ let speed = initial_speed
+ if line + 4 > &lines && countdown == 0
+ let countdown = 50
+ endif
+ endif
+
+ let col += col_inc
+ if col + 4 >= &columns
+ let col_inc = -1
+ elseif col <= 1
+ let col_inc = 1
+ endif
+
+ call popup_move(winid, {'line': float2nr(line), 'col': col})
+ redraw
+ let framecount += 1
+ if countdown > 0
+ let countdown -= 1
+ if countdown == 0
+ break
+ endif
+ endif
+
+endwhile
+
+let elapsed = reltimefloat(reltime(start))
+echomsg framecount .. ' frames in ' .. string(elapsed) .. ' seconds, ' .. string(framecount / elapsed) .. ' frames/sec'
+
+qa
diff --git a/src/testdir/util/screendump.vim b/src/testdir/util/screendump.vim
new file mode 100644
index 0000000..be6af86
--- /dev/null
+++ b/src/testdir/util/screendump.vim
@@ -0,0 +1,139 @@
+" Functions shared by tests making screen dumps.
+
+" Only load this script once.
+if exists('*VerifyScreenDump')
+ finish
+endif
+
+" Skip the rest if there is no terminal feature at all.
+if !has('terminal')
+ finish
+endif
+
+" Read a dump file "fname" and if "filter" exists apply it to the text.
+def ReadAndFilter(fname: string, filter: string): list<string>
+ var contents = readfile(fname)
+
+ if filereadable(filter)
+ # do this in the bottom window so that the terminal window is unaffected
+ wincmd j
+ enew
+ setline(1, contents)
+ exe "source " .. filter
+ contents = getline(1, '$')
+ enew!
+ wincmd k
+ redraw
+ endif
+
+ return contents
+enddef
+
+
+" Verify that Vim running in terminal buffer "buf" matches the screen dump.
+" "options" is passed to term_dumpwrite().
+" Additionally, the "wait" entry can specify the maximum time to wait for the
+" screen dump to match in msec (default 1000 msec).
+" The file name used is "dumps/{filename}.dump".
+"
+" To ignore part of the dump, provide a "dumps/{filename}.vim" file with
+" Vim commands to be applied to both the reference and the current dump, so
+" that parts that are irrelevant are not used for the comparison. The result
+" is NOT written, thus "term_dumpdiff()" shows the difference anyway.
+"
+" Optionally an extra argument can be passed which is prepended to the error
+" message. Use this when using the same dump file with different options.
+" Returns non-zero when verification fails.
+func VerifyScreenDump(buf, filename, options, ...)
+ if has('gui_running') && exists("g:check_screendump_called") && g:check_screendump_called == v:false
+ echoerr "VerifyScreenDump() called from a test that lacks a CheckScreendump guard."
+ return 1
+ endif
+ let reference = 'dumps/' . a:filename . '.dump'
+ let filter = 'dumps/' . a:filename . '.vim'
+ let testfile = 'failed/' . a:filename . '.dump'
+
+ let max_loops = get(a:options, 'wait', 1000) / 1
+
+ " Starting a terminal to make a screendump is always considered flaky.
+ let g:test_is_flaky = 1
+ let g:giveup_same_error = 0
+
+ " wait for the pending updates to be handled.
+ call TermWait(a:buf, 0)
+
+ " Redraw to execute the code that updates the screen. Otherwise we get the
+ " text and attributes only from the internal buffer.
+ redraw
+
+ if filereadable(reference)
+ let refdump = ReadAndFilter(reference, filter)
+ else
+ " Must be a new screendump, always fail
+ let refdump = []
+ endif
+
+ let did_mkdir = 0
+ if !isdirectory('failed')
+ let did_mkdir = 1
+ call mkdir('failed')
+ endif
+
+ let i = 0
+ while 1
+ " leave a bit of time for updating the original window while we spin wait.
+ sleep 1m
+ call delete(testfile)
+ call term_dumpwrite(a:buf, testfile, a:options)
+
+ if refdump->empty()
+ let msg = 'See new dump file: call term_dumpload("testdir/' .. testfile .. '")'
+ call assert_report(msg)
+ " no point in retrying
+ let g:run_nr = 10
+ return 1
+ endif
+
+ let testdump = ReadAndFilter(testfile, filter)
+ if refdump == testdump
+ call delete(testfile)
+ if did_mkdir
+ call delete('failed', 'd')
+ endif
+ if i > 0
+ call remove(v:errors, -1)
+ endif
+ break
+ endif
+
+ " Leave the failed dump around for inspection.
+ let msg = 'See dump file difference: call term_dumpdiff("testdir/' .. testfile .. '", "testdir/' .. reference .. '")'
+ if a:0 == 1
+ let msg = a:1 . ': ' . msg
+ endif
+ if len(testdump) != len(refdump)
+ let msg = msg . '; line count is ' . len(testdump) . ' instead of ' . len(refdump)
+ endif
+ for j in range(len(refdump))
+ if j >= len(testdump)
+ break
+ endif
+ if testdump[j] != refdump[j]
+ let msg = msg . '; difference in line ' . (j + 1) . ': "' . testdump[j] . '"'
+ endif
+ endfor
+
+ " Always add the last error so that it is displayed on timeout.
+ " See TestTimeout() in runtest.vim.
+ if i > 0
+ call remove(v:errors, -1)
+ endif
+ call assert_report(msg)
+
+ let i += 1
+ if i >= max_loops
+ return 1
+ endif
+ endwhile
+ return 0
+endfunc
diff --git a/src/testdir/util/script_util.vim b/src/testdir/util/script_util.vim
new file mode 100644
index 0000000..a300b67
--- /dev/null
+++ b/src/testdir/util/script_util.vim
@@ -0,0 +1,69 @@
+" Functions shared by the tests for Vim script
+
+" Commands to track the execution path of a script
+com! XpathINIT let g:Xpath = ''
+com! -nargs=1 -bar Xpath let g:Xpath ..= <args>
+com! XloopINIT let g:Xloop = 1
+com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop
+com! XloopNEXT let g:Xloop += 1
+
+" MakeScript() - Make a script file from a function. {{{2
+"
+" Create a script that consists of the body of the function a:funcname.
+" Replace any ":return" by a ":finish", any argument variable by a global
+" variable, and every ":call" by a ":source" for the next following argument
+" in the variable argument list. This function is useful if similar tests are
+" to be made for a ":return" from a function call or a ":finish" in a script
+" file.
+func MakeScript(funcname, ...)
+ let script = tempname()
+ execute "redir! >" . script
+ execute "function" a:funcname
+ redir END
+ execute "edit" script
+ " Delete the "function" and the "endfunction" lines. Do not include the
+ " word "function" in the pattern since it might be translated if LANG is
+ " set. When MakeScript() is being debugged, this deletes also the debugging
+ " output of its line 3 and 4.
+ exec '1,/.*' . a:funcname . '(.*)/d'
+ /^\d*\s*endfunction\>/,$d
+ %s/^\d*//e
+ %s/return/finish/e
+ %s/\<a:\(\h\w*\)/g:\1/ge
+ normal gg0
+ let cnt = 0
+ while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0
+ let cnt = cnt + 1
+ s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/
+ endwhile
+ g/^\s*$/d
+ write
+ bwipeout
+ return script
+endfunc
+
+" ExecAsScript - Source a temporary script made from a function. {{{2
+"
+" Make a temporary script file from the function a:funcname, ":source" it, and
+" delete it afterwards. However, if an exception is thrown the file may remain,
+" the caller should call DeleteTheScript() afterwards.
+let s:script_name = ''
+func ExecAsScript(funcname)
+ " Make a script from the function passed as argument.
+ let s:script_name = MakeScript(a:funcname)
+
+ " Source and delete the script.
+ exec "source" s:script_name
+ call delete(s:script_name)
+ let s:script_name = ''
+endfunc
+
+func DeleteTheScript()
+ if s:script_name
+ call delete(s:script_name)
+ let s:script_name = ''
+ endif
+endfunc
+
+com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>)
+
diff --git a/src/testdir/util/setup.vim b/src/testdir/util/setup.vim
new file mode 100644
index 0000000..485675a
--- /dev/null
+++ b/src/testdir/util/setup.vim
@@ -0,0 +1,47 @@
+" Common preparations for running tests.
+
+" Only load this once.
+if 1
+
+ " When using xterm version 377 the response to the modifyOtherKeys status
+ " interferes with some tests. Remove the request from the t_TI termcap
+ " entry.
+ let &t_TI = substitute(&t_TI, "\<Esc>\\[?4m", '', '')
+
+ if exists('s:did_load')
+ finish
+ endif
+ let s:did_load = 1
+endif
+
+" Make sure 'runtimepath' and 'packpath' does not include $HOME.
+set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after
+if has('packages')
+ let &packpath = &rtp
+endif
+
+" Only when the +eval feature is present.
+if 1
+ " Make sure the .Xauthority file can be found after changing $HOME.
+ if $XAUTHORITY == ''
+ let $XAUTHORITY = $HOME . '/.Xauthority'
+ endif
+
+ " Avoid storing shell history.
+ let $HISTFILE = ""
+
+ " Have current $HOME available as $ORIGHOME. $HOME is used for option
+ " defaults before we get here, and test_mksession checks that.
+ let $ORIGHOME = $HOME
+
+ if !exists('$XDG_CONFIG_HOME')
+ let $XDG_CONFIG_HOME = $HOME .. '/.config'
+ endif
+
+ " Make sure $HOME does not get read or written.
+ " It must exist, gnome tries to create $HOME/.gnome2
+ let $HOME = getcwd() . '/XfakeHOME'
+ if !isdirectory($HOME)
+ call mkdir($HOME)
+ endif
+endif
diff --git a/src/testdir/util/setup_gui.vim b/src/testdir/util/setup_gui.vim
new file mode 100644
index 0000000..2e5e777
--- /dev/null
+++ b/src/testdir/util/setup_gui.vim
@@ -0,0 +1,31 @@
+" Common preparations for running GUI tests.
+
+let g:x11_based_gui = has('gui_motif')
+ \ || has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+
+" Reasons for 'skipped'.
+let g:not_supported = "Skipped: Feature/Option not supported by this GUI: "
+let g:not_hosted = "Skipped: Test not hosted by the system/environment"
+
+" For KDE set a font, empty 'guifont' may cause a hang.
+func GUISetUpCommon()
+ if has("gui_kde")
+ set guifont=Courier\ 10\ Pitch/8/-1/5/50/0/0/0/0/0
+ endif
+
+ " Gnome insists on creating $HOME/.gnome2/, set $HOME to avoid changing the
+ " actual home directory. But avoid triggering fontconfig by setting the
+ " cache directory. Only needed for Unix.
+ if $XDG_CACHE_HOME == '' && exists('g:tester_HOME')
+ let $XDG_CACHE_HOME = g:tester_HOME . '/.cache'
+ endif
+ call mkdir('Xhome')
+ let $HOME = fnamemodify('Xhome', ':p')
+endfunc
+
+func GUITearDownCommon()
+ call delete('Xhome', 'rf')
+endfunc
+
+" Ignore the "failed to create input context" error.
+call test_ignore_error('E285')
diff --git a/src/testdir/util/shared.vim b/src/testdir/util/shared.vim
new file mode 100644
index 0000000..ddd3f37
--- /dev/null
+++ b/src/testdir/util/shared.vim
@@ -0,0 +1,450 @@
+" Functions shared by several tests.
+
+" Only load this script once.
+if exists('*PythonProg')
+ finish
+endif
+
+source util/view_util.vim
+
+" When 'term' is changed some status requests may be sent. The responses may
+" interfere with what is being tested. A short sleep is used to process any of
+" those responses first.
+func WaitForResponses()
+ sleep 50m
+endfunc
+
+" Get the name of the Python executable.
+" Also keeps it in s:python.
+func PythonProg()
+ " This test requires the Python command to run the test server.
+ " This most likely only works on Unix and Windows.
+ if has('unix')
+ " We also need the job feature or the pkill command to make sure the server
+ " can be stopped.
+ if !(has('job') || executable('pkill'))
+ return ''
+ endif
+ if executable('python3')
+ let s:python = 'python3'
+ elseif executable('python')
+ let s:python = 'python'
+ else
+ return ''
+ end
+ elseif has('win32')
+ " Use Python Launcher for Windows (py.exe) if available.
+ " NOTE: if you get a "Python was not found" error, disable the Python
+ " shortcuts in "Windows menu / Settings / Manage App Execution Aliases".
+ if executable('py.exe')
+ let s:python = 'py.exe'
+ elseif executable('python.exe')
+ let s:python = 'python.exe'
+ else
+ return ''
+ endif
+ else
+ return ''
+ endif
+ return s:python
+endfunc
+
+" Run "cmd". Returns the job if using a job.
+func RunCommand(cmd)
+ " Running an external command can occasionally be slow or fail.
+ let g:test_is_flaky = 1
+
+ let job = 0
+ if has('job')
+ let job = job_start(a:cmd, {"stoponexit": "hup"})
+ call job_setoptions(job, {"stoponexit": "kill"})
+ elseif has('win32')
+ exe 'silent !start cmd /D /c start "test_channel" ' . a:cmd
+ else
+ exe 'silent !' . a:cmd . '&'
+ endif
+ return job
+endfunc
+
+" Read the port number from the Xportnr file.
+func GetPort()
+ let l = []
+ " with 200 it sometimes failed, with 400 is rarily failed
+ for i in range(600)
+ try
+ let l = readfile("Xportnr")
+ catch
+ endtry
+ if len(l) >= 1
+ break
+ endif
+ sleep 10m
+ endfor
+ call delete("Xportnr")
+
+ if len(l) == 0
+ " Can't make the connection, give up.
+ return 0
+ endif
+ return l[0]
+endfunc
+
+" Run a Python server for "cmd" and call "testfunc".
+" Always kills the server before returning.
+func RunServer(cmd, testfunc, args)
+ " The Python program writes the port number in Xportnr.
+ call delete("Xportnr")
+
+ if len(a:args) == 1
+ let arg = ' ' . a:args[0]
+ else
+ let arg = ''
+ endif
+ let pycmd = s:python . " " . a:cmd . arg
+
+ try
+ let g:currentJob = RunCommand(pycmd)
+
+ " Wait for some time for the port number to be there.
+ let port = GetPort()
+ if port == 0
+ call assert_report(strftime("%H:%M:%S") .. " Can't start " .. a:cmd)
+ return
+ endif
+
+ call call(function(a:testfunc), [port])
+ catch /E901.*Address family for hostname not supported/
+ throw 'Skipped: Invalid network setup ("' .. v:exception .. '" in ' .. v:throwpoint .. ')'
+ catch
+ call assert_report('Caught exception: "' . v:exception . '" in ' . v:throwpoint)
+ finally
+ call s:kill_server(a:cmd)
+ endtry
+endfunc
+
+func s:kill_server(cmd)
+ if has('job')
+ if exists('g:currentJob')
+ call job_stop(g:currentJob)
+ unlet g:currentJob
+ endif
+ elseif has('win32')
+ let cmd = substitute(a:cmd, ".py", '', '')
+ call system('taskkill /IM ' . s:python . ' /T /F /FI "WINDOWTITLE eq ' . cmd . '"')
+ else
+ call system("pkill -f " . a:cmd)
+ endif
+endfunc
+
+" Callback function to be invoked by a child terminal job. The parent could
+" then wait for the notification using WaitForChildNotification()
+let g:child_notification = 0
+func Tapi_notify_parent(bufnum, arglist)
+ let g:child_notification = 1
+endfunc
+
+" Generates a command that we can pass to a terminal job that it uses to
+" notify us. Argument 'escape' will specify whether to escape the double
+" quote.
+func TermNotifyParentCmd(escape)
+ call assert_false(has("win32"), 'Windows does not support terminal API right now. Use another method to synchronize timing.')
+ let cmd = '\033]51;["call", "Tapi_notify_parent", []]\007'
+ if a:escape
+ return escape(cmd, '"')
+ endif
+ return cmd
+endfunc
+
+" Wait for a child process to notify us. This allows us to sequence events in
+" conjunction with the child. Currently the only supported notification method
+" is for a terminal job to call Tapi_notify_parent() using terminal API.
+func WaitForChildNotification(...)
+ let timeout = get(a:000, 0, 5000)
+ call WaitFor({-> g:child_notification == 1}, timeout)
+ let g:child_notification = 0
+endfunc
+
+" Wait for up to five seconds for "expr" to become true. "expr" can be a
+" stringified expression to evaluate, or a funcref without arguments.
+" Using a lambda works best. Example:
+" call WaitFor({-> status == "ok"})
+"
+" A second argument can be used to specify a different timeout in msec.
+"
+" When successful the time slept is returned.
+" When running into the timeout an exception is thrown, thus the function does
+" not return.
+func WaitFor(expr, ...)
+ let timeout = get(a:000, 0, 5000)
+ let slept = s:WaitForCommon(a:expr, v:null, timeout)
+ if slept < 0
+ throw 'WaitFor() timed out after ' . timeout . ' msec'
+ endif
+ return slept
+endfunc
+
+" Wait for up to five seconds for "assert" to return zero. "assert" must be a
+" (lambda) function containing one assert function. Example:
+" call WaitForAssert({-> assert_equal("dead", job_status(job)})
+"
+" A second argument can be used to specify a different timeout in msec.
+"
+" Return zero for success, one for failure (like the assert function).
+func g:WaitForAssert(assert, ...)
+ let timeout = get(a:000, 0, 5000)
+ if s:WaitForCommon(v:null, a:assert, timeout) < 0
+ return 1
+ endif
+ return 0
+endfunc
+
+" Common implementation of WaitFor() and WaitForAssert().
+" Either "expr" or "assert" is not v:null
+" Return the waiting time for success, -1 for failure.
+func s:WaitForCommon(expr, assert, timeout)
+ " using reltime() is more accurate, but not always available
+ let slept = 0
+ if exists('*reltimefloat')
+ let start = reltime()
+ endif
+
+ while 1
+ if type(a:expr) == v:t_func
+ let success = a:expr()
+ elseif type(a:assert) == v:t_func
+ let success = a:assert() == 0
+ else
+ let success = eval(a:expr)
+ endif
+ if success
+ return slept
+ endif
+
+ if slept >= a:timeout
+ break
+ endif
+ if type(a:assert) == v:t_func
+ " Remove the error added by the assert function.
+ call remove(v:errors, -1)
+ endif
+
+ sleep 1m
+ if exists('*reltimefloat')
+ let slept = float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ let slept += 1
+ endif
+ endwhile
+
+ return -1 " timed out
+endfunc
+
+
+" Wait for up to a given milliseconds.
+" With the +timers feature this waits for key-input by getchar(), Resume()
+" feeds key-input and resumes process. Return time waited in milliseconds.
+" Without +timers it uses simply :sleep.
+func Standby(msec)
+ if has('timers') && exists('*reltimefloat')
+ let start = reltime()
+ let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
+ call getchar()
+ return float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ execute 'sleep ' a:msec . 'm'
+ return a:msec
+ endif
+endfunc
+
+func Resume()
+ if exists('g:_standby_timer')
+ call timer_stop(g:_standby_timer)
+ call s:feedkeys(0)
+ unlet g:_standby_timer
+ endif
+endfunc
+
+func s:feedkeys(timer)
+ call feedkeys('x', 'nt')
+endfunc
+
+" Get the name of the Vim executable that we expect has been build in the src
+" directory.
+func s:GetJustBuildVimExe()
+ if has("win32")
+ if !filereadable('..\vim.exe') && filereadable('..\vimd.exe')
+ " looks like the debug executable was intentionally build, so use it
+ return '..\vimd.exe'
+ endif
+ return '..\vim.exe'
+ endif
+ return '../vim'
+endfunc
+
+" Get $VIMPROG to run the Vim executable.
+" The Makefile writes it as the first line in the "vimcmd" file.
+" Falls back to the Vim executable in the src directory.
+func GetVimProg()
+ if filereadable('vimcmd')
+ return readfile('vimcmd')[0]
+ endif
+ echo 'Cannot read the "vimcmd" file, falling back to ../vim.'
+
+ " Probably the script was sourced instead of running "make".
+ " We assume Vim was just build in the src directory then.
+ return s:GetJustBuildVimExe()
+endfunc
+
+let g:valgrind_cnt = 1
+
+" Get the command to run Vim, with -u NONE and --not-a-term arguments.
+" If there is an argument use it instead of "NONE".
+func GetVimCommand(...)
+ if filereadable('vimcmd')
+ let lines = readfile('vimcmd')
+ else
+ echo 'Cannot read the "vimcmd" file, falling back to ../vim.'
+ let lines = [s:GetJustBuildVimExe()]
+ endif
+
+ if a:0 == 0
+ let name = 'NONE'
+ else
+ let name = a:1
+ endif
+ " For Unix Makefile writes the command to use in the second line of the
+ " "vimcmd" file, including environment options.
+ " Other Makefiles just write the executable in the first line, so fall back
+ " to that if there is no second line or it is empty.
+ if len(lines) > 1 && lines[1] != ''
+ let cmd = lines[1]
+ else
+ let cmd = lines[0]
+ endif
+
+ let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '')
+ if cmd !~ '-u '. name
+ let cmd = cmd . ' -u ' . name
+ endif
+ let cmd .= ' --not-a-term'
+ let cmd .= ' --gui-dialog-file guidialogfile'
+ " remove any environment variables
+ let cmd = substitute(cmd, '[A-Z_]\+=\S\+ *', '', 'g')
+
+ " If using valgrind, make sure every run uses a different log file.
+ if cmd =~ 'valgrind.*--log-file='
+ let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '')
+ let g:valgrind_cnt += 1
+ endif
+
+ return cmd
+endfunc
+
+" Return one when it looks like the tests are run with valgrind, which means
+" that everything is much slower.
+func RunningWithValgrind()
+ return GetVimCommand() =~ '\<valgrind\>'
+endfunc
+
+func RunningAsan()
+ return exists("$ASAN_OPTIONS")
+endfunc
+
+func ValgrindOrAsan()
+ return RunningWithValgrind() || RunningAsan()
+endfun
+
+" Get the command to run Vim, with --clean instead of "-u NONE".
+func GetVimCommandClean()
+ let cmd = GetVimCommand()
+ let cmd = substitute(cmd, '-u NONE', '--clean', '')
+ let cmd = substitute(cmd, '--not-a-term', '', '')
+
+ " Force using utf-8, Vim may pick up something else from the environment.
+ let cmd ..= ' --cmd "set enc=utf8" '
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
+ return cmd
+endfunc
+
+" Get the command to run Vim, with --clean, and force to run in terminal so it
+" won't start a new GUI.
+func GetVimCommandCleanTerm()
+ " Add -v to have gvim run in the terminal (if possible)
+ return GetVimCommandClean() .. ' -v '
+endfunc
+
+" Run Vim, using the "vimcmd" file and "-u NORC".
+" "before" is a list of Vim commands to be executed before loading plugins.
+" "after" is a list of Vim commands to be executed after loading plugins.
+" Plugins are not loaded, unless 'loadplugins' is set in "before".
+" Return 1 if Vim could be executed.
+func RunVim(before, after, arguments)
+ return RunVimPiped(a:before, a:after, a:arguments, '')
+endfunc
+
+func RunVimPiped(before, after, arguments, pipecmd)
+ let cmd = GetVimCommand()
+ let args = ''
+ if len(a:before) > 0
+ call writefile(a:before, 'Xbefore.vim')
+ let args .= ' --cmd "so Xbefore.vim"'
+ endif
+ if len(a:after) > 0
+ call writefile(a:after, 'Xafter.vim')
+ let args .= ' -S Xafter.vim'
+ endif
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
+ exe "silent !" .. a:pipecmd .. ' ' .. cmd .. args .. ' ' .. a:arguments
+
+ if len(a:before) > 0
+ call delete('Xbefore.vim')
+ endif
+ if len(a:after) > 0
+ call delete('Xafter.vim')
+ endif
+ return 1
+endfunc
+
+func IsRoot()
+ if !has('unix')
+ return v:false
+ elseif $USER == 'root' || system('id -un') =~ '\<root\>'
+ return v:true
+ endif
+ return v:false
+endfunc
+
+" Get all messages but drop the maintainer entry.
+func GetMessages()
+ redir => result
+ redraw | messages
+ redir END
+ let msg_list = split(result, "\n")
+ if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:'
+ return msg_list[1:]
+ endif
+ return msg_list
+endfunc
+
+" Run the list of commands in 'cmds' and look for 'errstr' in exception.
+" Note that assert_fails() cannot be used in some places and this function
+" can be used.
+func AssertException(cmds, errstr)
+ let save_exception = ''
+ try
+ for cmd in a:cmds
+ exe cmd
+ endfor
+ catch
+ let save_exception = v:exception
+ endtry
+ call assert_match(a:errstr, save_exception)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/util/summarize.vim b/src/testdir/util/summarize.vim
new file mode 100644
index 0000000..d0d4e00
--- /dev/null
+++ b/src/testdir/util/summarize.vim
@@ -0,0 +1,62 @@
+set cpo&vim
+if 1
+ " This is executed only with the eval feature
+ set nocompatible
+ set viminfo=
+ func Count(match, type)
+ if a:type ==# 'executed'
+ let g:executed += (a:match+0)
+ elseif a:type ==# 'failed'
+ let g:failed += a:match+0
+ elseif a:type ==# 'skipped'
+ let g:skipped += 1
+ call extend(g:skipped_output, ["\t" .. a:match])
+ endif
+ endfunc
+
+ let g:executed = 0
+ let g:skipped = 0
+ let g:failed = 0
+ let g:skipped_output = []
+ let g:failed_output = []
+ let output = [""]
+
+ if $TEST_FILTER != ''
+ call extend(g:skipped_output, ["\tAll tests not matching $TEST_FILTER: '" .. $TEST_FILTER .. "'"])
+ endif
+
+ try
+ " This uses the :s command to just fetch and process the output of the
+ " tests, it doesn't actually replace anything.
+ " And it uses "silent" to avoid reporting the number of matches.
+ silent %s/Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn
+ silent %s/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn
+ silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn
+
+ call extend(output, ["Skipped:"])
+ call extend(output, skipped_output)
+
+ call extend(output, [
+ \ "",
+ \ "-------------------------------",
+ \ printf("Executed: %5d Tests", g:executed),
+ \ printf(" Skipped: %5d Tests", g:skipped),
+ \ printf(" %s: %5d Tests", g:failed == 0 ? 'Failed' : 'FAILED', g:failed),
+ \ "",
+ \ ])
+ if filereadable('test.log')
+ " outputs and indents the failed test result
+ call extend(output, ["", "Failures: "])
+ let failed_output = filter(readfile('test.log'), { v,k -> !empty(k)})
+ call extend(output, map(failed_output, { v,k -> "\t".k}))
+ " Add a final newline
+ call extend(output, [""])
+ endif
+
+ catch " Catch-all
+ finally
+ call writefile(output, 'test_result.log') " overwrites an existing file
+ endtry
+endif
+
+q!
diff --git a/src/testdir/util/term_util.vim b/src/testdir/util/term_util.vim
new file mode 100644
index 0000000..61ff9ce
--- /dev/null
+++ b/src/testdir/util/term_util.vim
@@ -0,0 +1,211 @@
+" Functions about terminal shared by several tests
+
+" Only load this script once.
+if exists('*CanRunVimInTerminal')
+ finish
+endif
+
+source util/shared.vim
+
+" For most tests we need to be able to run terminal Vim with 256 colors. On
+" MS-Windows the console only has 16 colors and the GUI can't run in a
+" terminal.
+func CanRunVimInTerminal()
+ return has('terminal') && !has('win32')
+endfunc
+
+" Skip the rest if there is no terminal feature at all.
+if !has('terminal')
+ finish
+endif
+
+" Stops the shell running in terminal "buf".
+func StopShellInTerminal(buf)
+ call term_sendkeys(a:buf, "exit\r")
+ let job = term_getjob(a:buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call TermWait(a:buf)
+endfunc
+
+" Wrapper around term_wait() to allow more time for re-runs of flaky tests
+" The second argument is the minimum time to wait in msec, 10 if omitted.
+func TermWait(buf, ...)
+ let wait_time = a:0 ? a:1 : 10
+ if exists('g:run_nr')
+ if g:run_nr == 2
+ let wait_time *= 4
+ elseif g:run_nr > 2
+ let wait_time *= 10
+ endif
+ endif
+ call term_wait(a:buf, wait_time)
+
+ " In case it wasn't set yet.
+ let g:test_is_flaky = 1
+endfunc
+
+" Run Vim with "arguments" in a new terminal window.
+" By default uses a size of 20 lines and 75 columns.
+" Returns the buffer number of the terminal.
+"
+" Options is a dictionary, these items are recognized:
+" "keep_t_u7" - when 1 do not make t_u7 empty (resetting t_u7 avoids clearing
+" parts of line 2 and 3 on the display)
+" "rows" - height of the terminal window (max. 20)
+" "cols" - width of the terminal window (max. 78)
+" "statusoff" - number of lines the status is offset from default
+" "wait_for_ruler" - if zero then don't wait for ruler to show
+" "no_clean" - if non-zero then remove "--clean" from the command
+" "cmd" - run any other command, e.g. "xxd" (used in xxd test)
+" "env" - additional environment variables, e.g. $TERM variable
+func RunVimInTerminal(arguments, options)
+ " If Vim doesn't exit a swap file remains, causing other tests to fail.
+ " Remove it here.
+ call delete(".swp")
+
+ if exists('$COLORFGBG')
+ " Clear $COLORFGBG to avoid 'background' being set to "dark", which will
+ " only be corrected if the response to t_RB is received, which may be too
+ " late.
+ let $COLORFGBG = ''
+ endif
+
+ " Make a horizontal and vertical split, so that we can get exactly the right
+ " size terminal window. Works only when the current window is full width.
+ call assert_equal(&columns, winwidth(0))
+ split
+ vsplit
+
+ " Always do this with 256 colors and a light background.
+ set t_Co=256 background=light
+ hi Normal ctermfg=NONE ctermbg=NONE
+
+ " Make the window 20 lines high and 75 columns, unless told otherwise or
+ " 'termwinsize' is set.
+ let rows = get(a:options, 'rows', 20)
+ let cols = get(a:options, 'cols', 75)
+ let statusoff = get(a:options, 'statusoff', 1)
+
+ if get(a:options, 'keep_t_u7', 0)
+ let reset_u7 = ''
+ else
+ let reset_u7 = ' --cmd "set t_u7=" '
+ endif
+
+ if empty(get(a:options, 'cmd', ''))
+ let cmd = GetVimCommandCleanTerm() .. reset_u7 .. a:arguments
+ else
+ let cmd = get(a:options, 'cmd')
+ endif
+
+ if get(a:options, 'no_clean', 0)
+ let cmd = substitute(cmd, '--clean', '', '')
+ endif
+
+ let options = #{curwin: 1}
+ if &termwinsize == ''
+ let options.term_rows = rows
+ let options.term_cols = cols
+ endif
+
+ " Accept other options whose name starts with 'term_'.
+ call extend(options, filter(copy(a:options), 'v:key =~# "^term_"'))
+ " Accept the env dict
+ if !empty(get(a:options, 'env', {}))
+ let options.env = get(a:options, 'env')
+ endif
+
+ let buf = term_start(cmd, options)
+
+ if &termwinsize == ''
+ " in the GUI we may end up with a different size, try to set it.
+ if term_getsize(buf) != [rows, cols]
+ call term_setsize(buf, rows, cols)
+ endif
+ call assert_equal([rows, cols], term_getsize(buf))
+ else
+ let rows = term_getsize(buf)[0]
+ let cols = term_getsize(buf)[1]
+ endif
+
+ call TermWait(buf)
+
+ if get(a:options, 'wait_for_ruler', 1) && empty(get(a:options, 'cmd', ''))
+ " Wait for "All" or "Top" of the ruler to be shown in the last line or in
+ " the status line of the last window. This can be quite slow (e.g. when
+ " using valgrind).
+ " If it fails then show the terminal contents for debugging.
+ try
+ call WaitFor({-> len(term_getline(buf, rows)) >= cols - 1 || len(term_getline(buf, rows - statusoff)) >= cols - 1})
+ catch /timed out after/
+ let lines = map(range(1, rows), {key, val -> term_getline(buf, val)})
+ call assert_report('RunVimInTerminal() failed, screen contents: ' . join(lines, "<NL>"))
+ endtry
+ endif
+
+ " Starting a terminal to run Vim is always considered flaky.
+ let g:test_is_flaky = 1
+
+ return buf
+endfunc
+
+" Stop a Vim running in terminal buffer "buf".
+func StopVimInTerminal(buf, kill = 1)
+ " Using a terminal to run Vim is always considered flaky.
+ let g:test_is_flaky = 1
+
+ call assert_equal("running", term_getstatus(a:buf))
+
+ " Wait for all the pending updates to terminal to complete
+ call TermWait(a:buf, 1)
+
+ " CTRL-O : works both in Normal mode and Insert mode to start a command line.
+ " In Command-line it's inserted, the CTRL-U removes it again.
+ call term_sendkeys(a:buf, "\<C-O>:\<C-U>qa!\<cr>")
+
+ " Wait for all the pending updates to terminal to complete
+ call TermWait(a:buf, 1)
+
+ " Wait for the terminal to end.
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))})
+
+ " If the buffer still exists forcefully wipe it.
+ if a:kill && bufexists(a:buf)
+ exe a:buf .. 'bwipe!'
+ endif
+endfunc
+
+" Open a terminal with a shell, assign the job to g:job and return the buffer
+" number.
+func Run_shell_in_terminal(options)
+ if has('win32')
+ let buf = term_start([&shell, '/D', '/k'], a:options)
+ else
+ let buf = term_start(&shell, a:options)
+ endif
+ let g:test_is_flaky = 1
+
+ let termlist = term_list()
+ call assert_equal(1, len(termlist))
+ call assert_equal(buf, termlist[0])
+
+ let g:job = term_getjob(buf)
+ call assert_equal(v:t_job, type(g:job))
+
+ let string = string({'job': buf->term_getjob()})
+ call assert_match("{'job': 'process \\d\\+ run'}", string)
+
+ " On slower systems it may take a bit of time before the shell is ready to
+ " accept keys. This mainly matters when using term_sendkeys() next.
+ call TermWait(buf)
+
+ return buf
+endfunc
+
+" Return concatenated lines in terminal.
+func Term_getlines(buf, lines)
+ return join(map(a:lines, 'term_getline(a:buf, v:val)'), '')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/util/unix.vim b/src/testdir/util/unix.vim
new file mode 100644
index 0000000..8a97129
--- /dev/null
+++ b/src/testdir/util/unix.vim
@@ -0,0 +1,13 @@
+" Settings for test script execution
+" Always use "sh", don't use the value of "$SHELL".
+set shell=sh
+
+" Only when the +eval feature is present.
+if 1
+ " While some tests overwrite $HOME to prevent them from polluting user files,
+ " we need to remember the original value so that we can tell external systems
+ " where to ask about their own user settings.
+ let g:tester_HOME = $HOME
+endif
+
+source util/setup.vim
diff --git a/src/testdir/util/view_util.vim b/src/testdir/util/view_util.vim
new file mode 100644
index 0000000..161c8b2
--- /dev/null
+++ b/src/testdir/util/view_util.vim
@@ -0,0 +1,117 @@
+" Functions about view shared by several tests
+
+" Only load this script once.
+if exists('*Screenline')
+ finish
+endif
+
+" Get line "lnum" as displayed on the screen.
+" Trailing white space is trimmed.
+func Screenline(lnum)
+ let chars = []
+ for c in range(1, winwidth(0))
+ call add(chars, nr2char(screenchar(a:lnum, c)))
+ endfor
+ let line = join(chars, '')
+ return matchstr(line, '^.\{-}\ze\s*$')
+endfunc
+
+" Get text on the screen, including composing characters.
+" ScreenLines(lnum, width) or
+" ScreenLines([start, end], width)
+func ScreenLines(lnum, width) abort
+ redraw!
+ if type(a:lnum) == v:t_list
+ let start = a:lnum[0]
+ let end = a:lnum[1]
+ else
+ let start = a:lnum
+ let end = a:lnum
+ endif
+ let lines = []
+ for l in range(start, end)
+ let lines += [join(map(range(1, a:width), 'screenstring(l, v:val)'), '')]
+ endfor
+ return lines
+endfunc
+
+func ScreenAttrs(lnum, width) abort
+ redraw!
+ if type(a:lnum) == v:t_list
+ let start = a:lnum[0]
+ let end = a:lnum[1]
+ else
+ let start = a:lnum
+ let end = a:lnum
+ endif
+ let attrs = []
+ for l in range(start, end)
+ let attrs += [map(range(1, a:width), 'screenattr(l, v:val)')]
+ endfor
+ return attrs
+endfunc
+
+" Create a new window with the requested size and fix it.
+func NewWindow(height, width) abort
+ exe a:height . 'new'
+ exe a:width . 'vsp'
+ set winfixwidth winfixheight
+ redraw!
+endfunc
+
+func CloseWindow() abort
+ bw!
+ redraw!
+endfunc
+
+
+" When using RunVimInTerminal() we expect modifyOtherKeys level 2 to be enabled
+" automatically. The key + modifier Escape codes must then use the
+" modifyOtherKeys encoding. They are recognized anyway, thus it's safer to use
+" than the raw code.
+
+" Return the modifyOtherKeys level 2 encoding for "key" with "modifier"
+" (number value, e.g. CTRL is 5, Shift is 2, Alt is 3).
+func GetEscCodeCSI27(key, modifier)
+ let key = printf("%d", char2nr(a:key))
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[27;" .. mod .. ';' .. key .. '~'
+endfunc
+
+" Return the modifyOtherKeys level 2 encoding for "key" with "modifier"
+" (character value, e.g. CTRL is "C").
+func GetEscCodeWithModifier(modifier, key)
+ let modifier = get({'C': 5}, a:modifier, '')
+ if modifier == ''
+ echoerr 'Unknown modifier: ' .. a:modifier
+ endif
+ return GetEscCodeCSI27(a:key, modifier)
+endfunc
+
+" Return the kitty keyboard protocol encoding for "key" with "modifier"
+" (number value, e.g. CTRL is 5).
+func GetEscCodeCSIu(key, modifier)
+ let key = printf("%d", char2nr(a:key))
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[" .. key .. ';' .. mod .. 'u'
+endfunc
+
+" Return the kitty keyboard protocol encoding for a function key:
+" CSI {key}
+" CSS 1;{modifier} {key}
+func GetEscCodeFunckey(key, modifier)
+ if a:modifier == 0
+ return "\<Esc>[" .. a:key
+ endif
+
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[1;".. mod .. a:key
+endfunc
+
+" Return the kitty keyboard protocol encoding for "key" without a modifier.
+" Used for the Escape key.
+func GetEscCodeCSIuWithoutModifier(key)
+ let key = printf("%d", char2nr(a:key))
+ return "\<Esc>[" .. key .. 'u'
+endfunc
+
diff --git a/src/testdir/util/vim9.vim b/src/testdir/util/vim9.vim
new file mode 100644
index 0000000..b994855
--- /dev/null
+++ b/src/testdir/util/vim9.vim
@@ -0,0 +1,549 @@
+vim9script
+
+# Utility functions for testing Vim9 script
+
+# Use a different file name for each run.
+var sequence = 1
+
+# Check that "lines" inside a ":def" function has no error when called.
+export func CheckDefSuccess(lines)
+ let cwd = getcwd()
+ let fname = 'XdefSuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()'] + a:lines + ['enddef', 'defcompile'], fname)
+ try
+ exe 'so ' .. fname
+ call Func()
+ finally
+ call chdir(cwd)
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+# Check that "lines" inside a ":def" function has no error when compiled.
+export func CheckDefCompileSuccess(lines)
+ let fname = 'XdefSuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()'] + a:lines + ['enddef', 'defcompile'], fname)
+ try
+ exe 'so ' .. fname
+ finally
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+# Check that "lines" inside ":def" results in an "error" message.
+# If "lnum" is given check that the error is reported for this line.
+# Add a line before and after to make it less likely that the line number is
+# accidentally correct.
+export func CheckDefFailure(lines, error, lnum = -3)
+ let cwd = getcwd()
+ let fname = 'XdefFailure' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()', '# comment'] + a:lines + ['#comment', 'enddef', 'defcompile'], fname)
+ try
+ call assert_fails('so ' .. fname, a:error, a:lines, a:lnum + 1)
+ finally
+ call chdir(cwd)
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+# Check that "lines" inside ":def" results in an "error" message when executed.
+# If "lnum" is given check that the error is reported for this line.
+# Add a line before and after to make it less likely that the line number is
+# accidentally correct.
+export func CheckDefExecFailure(lines, error, lnum = -3)
+ let cwd = getcwd()
+ let fname = 'XdefExecFailure' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()', '# comment'] + a:lines + ['#comment', 'enddef'], fname)
+ try
+ exe 'so ' .. fname
+ call assert_fails('call Func()', a:error, a:lines, a:lnum + 1)
+ finally
+ call chdir(cwd)
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+export def CheckScriptFailure(lines: list<string>, error: string, lnum = -3)
+ var cwd = getcwd()
+ var fname = 'XScriptFailure' .. sequence
+ sequence += 1
+ writefile(lines, fname)
+ try
+ assert_fails('so ' .. fname, error, lines, lnum)
+ finally
+ chdir(cwd)
+ delete(fname)
+ endtry
+enddef
+
+export def CheckScriptFailureList(lines: list<string>, errors: list<string>, lnum = -3)
+ var cwd = getcwd()
+ var fname = 'XScriptFailure' .. sequence
+ sequence += 1
+ writefile(lines, fname)
+ try
+ assert_fails('so ' .. fname, errors, lines, lnum)
+ finally
+ chdir(cwd)
+ delete(fname)
+ endtry
+enddef
+
+export def CheckScriptSuccess(lines: list<string>)
+ var cwd = getcwd()
+ var fname = 'XScriptSuccess' .. sequence
+ sequence += 1
+ writefile(lines, fname)
+ try
+ exe 'so ' .. fname
+ finally
+ chdir(cwd)
+ delete(fname)
+ endtry
+enddef
+
+export def CheckDefAndScriptSuccess(lines: list<string>)
+ CheckDefSuccess(lines)
+ CheckScriptSuccess(['vim9script'] + lines)
+enddef
+
+# Check that a command fails when used in a :def function and when used in
+# Vim9 script.
+# When "error" is a string, both with the same error.
+# When "error" is a list, the :def function fails with "error[0]" , the script
+# fails with "error[1]".
+export def CheckDefAndScriptFailure(lines: list<string>, error: any, lnum = -3)
+ var errorDef: string
+ var errorScript: string
+ if type(error) == v:t_string
+ errorDef = error
+ errorScript = error
+ elseif type(error) == v:t_list && len(error) == 2
+ errorDef = error[0]
+ errorScript = error[1]
+ else
+ echoerr 'error argument must be a string or a list with two items'
+ return
+ endif
+ CheckDefFailure(lines, errorDef, lnum)
+ CheckScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
+enddef
+
+# Check that a command fails when executed in a :def function and when used in
+# Vim9 script.
+# When "error" is a string, both with the same error.
+# When "error" is a list, the :def function fails with "error[0]" , the script
+# fails with "error[1]".
+export def CheckDefExecAndScriptFailure(lines: list<string>, error: any, lnum = -3)
+ var errorDef: string
+ var errorScript: string
+ if type(error) == v:t_string
+ errorDef = error
+ errorScript = error
+ elseif type(error) == v:t_list && len(error) == 2
+ errorDef = error[0]
+ errorScript = error[1]
+ else
+ echoerr 'error argument must be a string or a list with two items'
+ return
+ endif
+ CheckDefExecFailure(lines, errorDef, lnum)
+ CheckScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
+enddef
+
+
+# Check that "lines" inside a legacy function has no error.
+export func CheckLegacySuccess(lines)
+ let cwd = getcwd()
+ let fname = 'XlegacySuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['func Func()'] + a:lines + ['endfunc'], fname)
+ try
+ exe 'so ' .. fname
+ call Func()
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ call delete(fname)
+ endtry
+endfunc
+
+# Check that "lines" inside a legacy function results in the expected error
+export func CheckLegacyFailure(lines, error)
+ let cwd = getcwd()
+ let fname = 'XlegacyFails' .. s:sequence
+ let s:sequence += 1
+ call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname)
+ try
+ call assert_fails('so ' .. fname, a:error)
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ call delete(fname)
+ endtry
+endfunc
+
+# Translate "lines" to legacy Vim script
+def LegacyTrans(lines: list<string>): list<string>
+ return lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'let', 'g')
+ ->substitute('\<LET\>', 'let', 'g')
+ ->substitute('\<LSTART\>', '{', 'g')
+ ->substitute('\<LMIDDLE\>', '->', 'g')
+ ->substitute('\<LEND\>', '}', 'g')
+ ->substitute('\<TRUE\>', '1', 'g')
+ ->substitute('\<FALSE\>', '0', 'g')
+ ->substitute('#"', ' "', 'g'))
+enddef
+
+# Execute "lines" in a legacy function, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckTransLegacySuccess(lines: list<string>)
+ CheckLegacySuccess(LegacyTrans(lines))
+enddef
+
+export def Vim9Trans(lines: list<string>): list<string>
+ return lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'var', 'g')
+ ->substitute('\<LET ', '', 'g')
+ ->substitute('\<LSTART\>', '(', 'g')
+ ->substitute('\<LMIDDLE\>', ') =>', 'g')
+ ->substitute(' *\<LEND\> *', '', 'g')
+ ->substitute('\<TRUE\>', 'true', 'g')
+ ->substitute('\<FALSE\>', 'false', 'g'))
+enddef
+
+# Execute "lines" in a :def function, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckTransDefSuccess(lines: list<string>)
+ CheckDefSuccess(Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a Vim9 script, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckTransVim9Success(lines: list<string>)
+ CheckScriptSuccess(['vim9script'] + Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+# Use LSTART arg LMIDDLE expr LEND for lambda
+# Use 'TRUE' for 1 in legacy, true in Vim9
+# Use 'FALSE' for 0 in legacy, false in Vim9
+export def CheckLegacyAndVim9Success(lines: list<string>)
+ CheckTransLegacySuccess(lines)
+ CheckTransDefSuccess(lines)
+ CheckTransVim9Success(lines)
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+export def CheckLegacyAndVim9Failure(lines: list<string>, error: any)
+ var legacyError: string
+ var defError: string
+ var scriptError: string
+
+ if type(error) == type('string')
+ legacyError = error
+ defError = error
+ scriptError = error
+ else
+ legacyError = error[0]
+ defError = error[1]
+ scriptError = error[2]
+ endif
+
+ var legacylines = lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'let', 'g')
+ ->substitute('\<LET\>', 'let', 'g')
+ ->substitute('\<LSTART\>', '{', 'g')
+ ->substitute('\<LMIDDLE\>', '->', 'g')
+ ->substitute('\<LEND\>', '}', 'g')
+ ->substitute('\<TRUE\>', '1', 'g')
+ ->substitute('\<FALSE\>', '0', 'g')
+ ->substitute('#"', ' "', 'g'))
+ CheckLegacyFailure(legacylines, legacyError)
+
+ var vim9lines = lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'var', 'g')
+ ->substitute('\<LET ', '', 'g')
+ ->substitute('\<LSTART\>', '(', 'g')
+ ->substitute('\<LMIDDLE\>', ') =>', 'g')
+ ->substitute(' *\<LEND\> *', '', 'g')
+ ->substitute('\<TRUE\>', 'true', 'g')
+ ->substitute('\<FALSE\>', 'false', 'g'))
+ CheckDefExecFailure(vim9lines, defError)
+ CheckScriptFailure(['vim9script'] + vim9lines, scriptError)
+enddef
+
+# Check that "lines" inside a legacy function has no error.
+export func CheckSourceLegacySuccess(lines)
+ let cwd = getcwd()
+ new
+ call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()'])
+ let bnr = bufnr()
+ try
+ :source
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Check that "lines" inside a legacy function results in the expected error
+export func CheckSourceLegacyFailure(lines, error)
+ let cwd = getcwd()
+ new
+ call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()'])
+ let bnr = bufnr()
+ try
+ call assert_fails('source', a:error)
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Execute "lines" in a legacy function, translated as in
+# CheckSourceLegacyAndVim9Success()
+export def CheckSourceTransLegacySuccess(lines: list<string>)
+ CheckSourceLegacySuccess(LegacyTrans(lines))
+enddef
+
+# Execute "lines" in a :def function, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckSourceTransDefSuccess(lines: list<string>)
+ CheckSourceDefSuccess(Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a Vim9 script, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckSourceTransVim9Success(lines: list<string>)
+ CheckSourceScriptSuccess(['vim9script'] + Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+# Use LSTART arg LMIDDLE expr LEND for lambda
+# Use 'TRUE' for 1 in legacy, true in Vim9
+# Use 'FALSE' for 0 in legacy, false in Vim9
+export def CheckSourceLegacyAndVim9Success(lines: list<string>)
+ CheckSourceTransLegacySuccess(lines)
+ CheckSourceTransDefSuccess(lines)
+ CheckSourceTransVim9Success(lines)
+enddef
+
+# :source a list of "lines" and check whether it fails with "error"
+export def CheckSourceScriptFailure(lines: list<string>, error: string, lnum = -3)
+ var cwd = getcwd()
+ new
+ setline(1, lines)
+ var bnr = bufnr()
+ try
+ assert_fails('source', error, lines, lnum)
+ finally
+ chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+enddef
+
+# :source a list of "lines" and check whether it fails with the list of
+# "errors"
+export def CheckSourceScriptFailureList(lines: list<string>, errors: list<string>, lnum = -3)
+ var cwd = getcwd()
+ new
+ var bnr = bufnr()
+ setline(1, lines)
+ try
+ assert_fails('source', errors, lines, lnum)
+ finally
+ chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+enddef
+
+# :source a list of "lines" and check whether it succeeds
+export def CheckSourceScriptSuccess(lines: list<string>)
+ var cwd = getcwd()
+ new
+ var bnr = bufnr()
+ setline(1, lines)
+ try
+ :source
+ finally
+ chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+enddef
+
+# :source a List of "lines" inside a ":def" function and check that no error
+# occurs when called.
+export func CheckSourceDefSuccess(lines)
+ let cwd = getcwd()
+ new
+ let bnr = bufnr()
+ call setline(1, ['def Func()'] + a:lines + ['enddef', 'defcompile'])
+ try
+ source
+ call Func()
+ finally
+ call chdir(cwd)
+ delfunc! Func
+ exe $'bw! {bnr}'
+ endtry
+endfunc
+
+# Check that "lines" inside a ":def" function has no error when compiled.
+export func CheckSourceDefCompileSuccess(lines)
+ let cwd = getcwd()
+ new
+ let bnr = bufnr()
+ call setline(1, ['def Func()', '# comment'] + a:lines + ['#comment', 'enddef', 'defcompile'])
+ try
+ source
+ finally
+ call chdir(cwd)
+ delfunc! Func
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Check that "lines" inside ":def" results in an "error" message.
+# If "lnum" is given check that the error is reported for this line.
+# Add a line before and after to make it less likely that the line number is
+# accidentally correct.
+export func CheckSourceDefFailure(lines, error, lnum = -3)
+ let cwd = getcwd()
+ new
+ let bnr = bufnr()
+ call setline(1, ['def Func()', '# comment'] + a:lines + ['#comment', 'enddef', 'defcompile'])
+ try
+ call assert_fails('source', a:error, a:lines, a:lnum + 1)
+ finally
+ call chdir(cwd)
+ delfunc! Func
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Check that "lines" inside ":def" results in an "error" message when executed.
+# If "lnum" is given check that the error is reported for this line.
+# Add a line before and after to make it less likely that the line number is
+# accidentally correct.
+export func CheckSourceDefExecFailure(lines, error, lnum = -3)
+ let cwd = getcwd()
+ new
+ let bnr = bufnr()
+ call setline(1, ['def Func()', '# comment'] + a:lines + ['#comment', 'enddef'])
+ try
+ source
+ call assert_fails('call Func()', a:error, a:lines, a:lnum + 1)
+ finally
+ call chdir(cwd)
+ delfunc! Func
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+# Check that a command fails when used in a :def function and when used in
+# Vim9 script.
+# When "error" is a string, both with the same error.
+# When "error" is a list, the :def function fails with "error[0]" , the script
+# fails with "error[1]".
+export def CheckSourceDefAndScriptFailure(lines: list<string>, error: any, lnum = -3)
+ var errorDef: string
+ var errorScript: string
+ if type(error) == v:t_string
+ errorDef = error
+ errorScript = error
+ elseif type(error) == v:t_list && len(error) == 2
+ errorDef = error[0]
+ errorScript = error[1]
+ else
+ echoerr 'error argument must be a string or a list with two items'
+ return
+ endif
+ CheckSourceDefFailure(lines, errorDef, lnum)
+ CheckSourceScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
+enddef
+
+# Check that a command fails when executed in a :def function and when used in
+# Vim9 script.
+# When "error" is a string, both with the same error.
+# When "error" is a list, the :def function fails with "error[0]" , the script
+# fails with "error[1]".
+export def CheckSourceDefExecAndScriptFailure(lines: list<string>, error: any, lnum = -3)
+ var errorDef: string
+ var errorScript: string
+ if type(error) == v:t_string
+ errorDef = error
+ errorScript = error
+ elseif type(error) == v:t_list && len(error) == 2
+ errorDef = error[0]
+ errorScript = error[1]
+ else
+ echoerr 'error argument must be a string or a list with two items'
+ return
+ endif
+ CheckSourceDefExecFailure(lines, errorDef, lnum)
+ CheckSourceScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
+enddef
+
+export def CheckSourceSuccess(lines: list<string>)
+ CheckSourceScriptSuccess(lines)
+enddef
+
+export def CheckSourceFailure(lines: list<string>, error: string, lnum = -3)
+ CheckSourceScriptFailure(lines, error, lnum)
+enddef
+
+export def CheckSourceFailureList(lines: list<string>, errors: list<string>, lnum = -3)
+ CheckSourceScriptFailureList(lines, errors, lnum)
+enddef
+
+export def CheckSourceDefAndScriptSuccess(lines: list<string>)
+ CheckSourceDefSuccess(lines)
+ CheckSourceScriptSuccess(['vim9script'] + lines)
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+export def CheckSourceLegacyAndVim9Failure(lines: list<string>, error: any)
+ var legacyError: string
+ var defError: string
+ var scriptError: string
+
+ if type(error) == type('string')
+ legacyError = error
+ defError = error
+ scriptError = error
+ else
+ legacyError = error[0]
+ defError = error[1]
+ scriptError = error[2]
+ endif
+
+ CheckSourceLegacyFailure(LegacyTrans(lines), legacyError)
+ var vim9lines = Vim9Trans(lines)
+ CheckSourceDefExecFailure(vim9lines, defError)
+ CheckSourceScriptFailure(['vim9script'] + vim9lines, scriptError)
+enddef
+
diff --git a/src/testdir/util/vms.vim b/src/testdir/util/vms.vim
new file mode 100644
index 0000000..0a264e9
--- /dev/null
+++ b/src/testdir/util/vms.vim
@@ -0,0 +1,6 @@
+" Settings for test script execution under OpenVMS
+
+" Do not use any swap files
+set noswapfile
+
+source util/setup.vim
diff --git a/src/testdir/util/window_manager.vim b/src/testdir/util/window_manager.vim
new file mode 100644
index 0000000..7a3c0c0
--- /dev/null
+++ b/src/testdir/util/window_manager.vim
@@ -0,0 +1,107 @@
+CheckFeature job
+CheckUnix
+
+let g:xdisplay_num = 100
+
+" Each key is the display name and its value is the compositor/wm job
+let s:wayland_displays = {}
+let s:x11_displays = {}
+
+command -nargs=0 CheckWaylandCompositor call CheckWaylandCompositor()
+command -nargs=0 CheckXServer call CheckXServer()
+
+func CheckWaylandCompositor()
+ CheckFeature wayland
+
+ if executable("labwc") != 1
+ throw "Skipped: labwc is not available"
+ endif
+endfunc
+
+func CheckXServer()
+ CheckFeature x11
+
+ if executable("Xvfb") != 1
+ throw "Skipped: Xvfb is not available"
+ endif
+ if executable("xdpyinfo") != 1
+ throw "Skipped: xdpyinfo is not available"
+ endif
+endfunc
+
+func s:StartCompositorOutput(channel, msg)
+ let l:display = matchstr(a:msg, 'WAYLAND_DISPLAY=\zs.\+')
+
+ if !empty(l:display)
+ let s:wayland_display_name = l:display
+ endif
+endfunc
+
+func s:StartCompositorExit(job, status)
+ if s:wayland_display_name == ""
+ throw "Skipped: Error: Wayland compositor exited when starting up"
+ endif
+endfunc
+
+func StartWaylandCompositor()
+ let s:wayland_display_name = ""
+
+ let l:wayland_compositor_job = job_start(
+ \ ['labwc', '-c', 'NONE', '-d'], {
+ \ 'err_io': 'pipe',
+ \ 'err_cb': function('s:StartCompositorOutput'),
+ \ 'err_mode': 'nl',
+ \ 'exit_cb': function('s:StartCompositorExit'),
+ \ 'env': { 'WLR_BACKENDS': 'headless' }
+ \ })
+
+ call WaitForAssert({-> assert_equal("run",
+ \ job_status(l:wayland_compositor_job))})
+ call WaitForAssert({-> assert_match('.\+', s:wayland_display_name)})
+
+ let s:wayland_displays[s:wayland_display_name] = l:wayland_compositor_job
+
+ return s:wayland_display_name
+endfunc
+
+func EndWaylandCompositor(display)
+ let l:job = s:wayland_displays[a:display]
+
+ call job_stop(l:job, 'term')
+
+ " Block until compositor is actually gone
+ call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+
+ unlet s:wayland_displays[a:display]
+endfunc
+
+" Start a separate X11 server instance
+func StartXServer()
+ let l:xdisplay = ':' .. g:xdisplay_num
+
+ let l:x11_server_job = job_start(['Xvfb', l:xdisplay], {})
+
+ call WaitForAssert({-> assert_equal("run", job_status(l:x11_server_job))})
+ " Check if server is ready. Not sure if this is the best way though...
+ call WaitFor({-> system("DISPLAY=" .. l:xdisplay .. " xdpyinfo 2> /dev/null")
+ \ =~? '.\+'})
+
+ g:xdisplay_num += 1
+
+ let s:x11_displays[l:xdisplay] = l:x11_server_job
+
+ return l:xdisplay
+endfunc
+
+func EndXServer(display)
+ let l:job = s:x11_displays[a:display]
+
+ call job_stop(l:job)
+
+ " Block until X server is actually gone
+ call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+
+ unlet s:x11_displays[a:display]
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab