aboutsummaryrefslogtreecommitdiff
path: root/test/functional/eval
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/eval')
-rw-r--r--test/functional/eval/api_functions_spec.lua148
-rw-r--r--test/functional/eval/backtick_expansion_spec.lua50
-rw-r--r--test/functional/eval/buf_functions_spec.lua304
-rw-r--r--test/functional/eval/capture_spec.lua86
-rw-r--r--test/functional/eval/changedtick_spec.lua142
-rw-r--r--test/functional/eval/container_functions_spec.lua24
-rw-r--r--test/functional/eval/executable_spec.lua156
-rw-r--r--test/functional/eval/execute_spec.lua206
-rw-r--r--test/functional/eval/fnamemodify_spec.lua39
-rw-r--r--test/functional/eval/function_spec.lua29
-rw-r--r--test/functional/eval/getline_spec.lua39
-rw-r--r--test/functional/eval/glob_spec.lua4
-rw-r--r--test/functional/eval/has_spec.lua66
-rw-r--r--test/functional/eval/hostname_spec.lua20
-rw-r--r--test/functional/eval/input_spec.lua483
-rw-r--r--test/functional/eval/interrupt_spec.lua61
-rw-r--r--test/functional/eval/json_functions_spec.lua140
-rw-r--r--test/functional/eval/let_spec.lua45
-rw-r--r--test/functional/eval/map_functions_spec.lua159
-rw-r--r--test/functional/eval/match_functions_spec.lua158
-rw-r--r--test/functional/eval/minmax_functions_spec.lua51
-rw-r--r--test/functional/eval/modeline_spec.lua19
-rw-r--r--test/functional/eval/msgpack_functions_spec.lua199
-rw-r--r--test/functional/eval/null_spec.lua147
-rw-r--r--test/functional/eval/printf_spec.lua32
-rw-r--r--test/functional/eval/reltime_spec.lua4
-rw-r--r--test/functional/eval/server_spec.lua128
-rw-r--r--test/functional/eval/setpos_spec.lua64
-rw-r--r--test/functional/eval/sort_spec.lua56
-rw-r--r--test/functional/eval/special_vars_spec.lua25
-rw-r--r--test/functional/eval/string_spec.lua102
-rw-r--r--test/functional/eval/system_spec.lua574
-rw-r--r--test/functional/eval/timer_spec.lua133
-rw-r--r--test/functional/eval/uniq_spec.lua31
-rw-r--r--test/functional/eval/writefile_spec.lua146
35 files changed, 3751 insertions, 319 deletions
diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua
new file mode 100644
index 0000000000..6f440c7d82
--- /dev/null
+++ b/test/functional/eval/api_functions_spec.lua
@@ -0,0 +1,148 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local lfs = require('lfs')
+local neq, eq, command = helpers.neq, helpers.eq, helpers.command
+local clear, curbufmeths = helpers.clear, helpers.curbufmeths
+local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval
+local insert = helpers.insert
+
+describe('api functions', function()
+ before_each(clear)
+
+ it("work", function()
+ command("call nvim_command('let g:test = 1')")
+ eq(1, eval("nvim_get_var('test')"))
+
+ local buf = eval("nvim_get_current_buf()")
+ command("call nvim_buf_set_lines("..buf..", 0, -1, v:true, ['aa', 'bb'])")
+ expect([[
+ aa
+ bb]])
+
+ command("call nvim_win_set_cursor(0, [1, 1])")
+ command("call nvim_input('ax<esc>')")
+ expect([[
+ aax
+ bb]])
+ end)
+
+ it("throw errors for invalid arguments", function()
+ local err = exc_exec('call nvim_get_current_buf("foo")')
+ eq('Vim(call):E118: Too many arguments for function: nvim_get_current_buf', err)
+
+ err = exc_exec('call nvim_set_option("hlsearch")')
+ eq('Vim(call):E119: Not enough arguments for function: nvim_set_option', err)
+
+ err = exc_exec('call nvim_buf_set_lines(1, 0, -1, [], ["list"])')
+ eq('Vim(call):Wrong type for argument 4, expecting Boolean', err)
+
+ err = exc_exec('call nvim_buf_set_lines(0, 0, -1, v:true, "string")')
+ eq('Vim(call):Wrong type for argument 5, expecting ArrayOf(String)', err)
+
+ err = exc_exec('call nvim_buf_get_number("0")')
+ eq('Vim(call):Wrong type for argument 1, expecting Buffer', err)
+
+ err = exc_exec('call nvim_buf_line_count(17)')
+ eq('Vim(call):Invalid buffer id', err)
+ end)
+
+
+ it("use buffer numbers and windows ids as handles", function()
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ local bnr = eval("bufnr('')")
+ local bhnd = eval("nvim_get_current_buf()")
+ local wid = eval("win_getid()")
+ local whnd = eval("nvim_get_current_win()")
+ eq(bnr, bhnd)
+ eq(wid, whnd)
+
+ command("new") -- creates new buffer and new window
+ local bnr2 = eval("bufnr('')")
+ local bhnd2 = eval("nvim_get_current_buf()")
+ local wid2 = eval("win_getid()")
+ local whnd2 = eval("nvim_get_current_win()")
+ eq(bnr2, bhnd2)
+ eq(wid2, whnd2)
+ neq(bnr, bnr2)
+ neq(wid, wid2)
+ -- 0 is synonymous to the current buffer
+ eq(bnr2, eval("nvim_buf_get_number(0)"))
+
+ command("bn") -- show old buffer in new window
+ eq(bnr, eval("nvim_get_current_buf()"))
+ eq(bnr, eval("bufnr('')"))
+ eq(bnr, eval("nvim_buf_get_number(0)"))
+ eq(wid2, eval("win_getid()"))
+ eq(whnd2, eval("nvim_get_current_win()"))
+ end)
+
+ it("get_lines and set_lines use NL to represent NUL", function()
+ curbufmeths.set_lines(0, -1, true, {"aa\0", "b\0b"})
+ eq({'aa\n', 'b\nb'}, eval("nvim_buf_get_lines(0, 0, -1, 1)"))
+
+ command('call nvim_buf_set_lines(0, 1, 2, v:true, ["xx", "\\nyy"])')
+ eq({'aa\0', 'xx', '\0yy'}, curbufmeths.get_lines(0, -1, 1))
+ end)
+
+ it("that are FUNC_ATTR_NOEVAL cannot be called", function()
+ -- Deprecated vim_ prefix is not exported.
+ local err = exc_exec('call vim_get_current_buffer("foo")')
+ eq('Vim(call):E117: Unknown function: vim_get_current_buffer', err)
+
+ -- Deprecated buffer_ prefix is not exported.
+ err = exc_exec('call buffer_line_count(0)')
+ eq('Vim(call):E117: Unknown function: buffer_line_count', err)
+
+ -- Functions deprecated before the api functions became available
+ -- in vimscript are not exported.
+ err = exc_exec('call buffer_get_line(0, 1)')
+ eq('Vim(call):E117: Unknown function: buffer_get_line', err)
+
+ -- some api functions are only useful from a msgpack-rpc channel
+ err = exc_exec('call nvim_subscribe("fancyevent")')
+ eq('Vim(call):E117: Unknown function: nvim_subscribe', err)
+ end)
+
+ it('have metadata accessible with api_info()', function()
+ local api_keys = eval("sort(keys(api_info()))")
+ eq({'error_types', 'functions', 'types',
+ 'ui_events', 'ui_options', 'version'}, api_keys)
+ end)
+
+ it('are highlighted by vim.vim syntax file', function()
+ if lfs.attributes("build/runtime/syntax/vim/generated.vim",'uid') == nil then
+ pending("runtime was not built, skipping test")
+ return
+ end
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Brown},
+ [2] = {foreground = Screen.colors.DarkCyan},
+ [3] = {foreground = Screen.colors.SlateBlue},
+ [4] = {foreground = Screen.colors.Fuchsia},
+ [5] = {bold = true, foreground = Screen.colors.Blue},
+ })
+
+ command("set ft=vim")
+ command("let &rtp='build/runtime/,'.&rtp")
+ command("syntax on")
+ insert([[
+ call bufnr('%')
+ call nvim_input('typing...')
+ call not_a_function(42)]])
+
+ screen:expect([[
+ {1:call} {2:bufnr}{3:(}{4:'%'}{3:)} |
+ {1:call} {2:nvim_input}{3:(}{4:'typing...'}{3:)} |
+ {1:call} not_a_function{3:(}{4:42}{3:^)} |
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ |
+ ]])
+ screen:detach()
+ end)
+end)
diff --git a/test/functional/eval/backtick_expansion_spec.lua b/test/functional/eval/backtick_expansion_spec.lua
new file mode 100644
index 0000000000..b1b44cfa8b
--- /dev/null
+++ b/test/functional/eval/backtick_expansion_spec.lua
@@ -0,0 +1,50 @@
+local lfs = require('lfs')
+local helpers = require('test.functional.helpers')(after_each)
+local clear, command, eval, eq = helpers.clear, helpers.command, helpers.eval, helpers.eq
+local write_file = helpers.write_file
+
+describe("backtick expansion", function()
+ setup(function()
+ clear()
+ lfs.mkdir("test-backticks")
+ write_file("test-backticks/file1", "test file 1")
+ write_file("test-backticks/file2", "test file 2")
+ write_file("test-backticks/file3", "test file 3")
+ lfs.mkdir("test-backticks/subdir")
+ write_file("test-backticks/subdir/file4", "test file 4")
+ -- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
+ command('silent cd test-backticks')
+ end)
+
+ teardown(function()
+ helpers.rmdir('test-backticks')
+ end)
+
+ it("with default 'shell'", function()
+ if helpers.iswin() then
+ command(":silent args `dir /b *2`")
+ else
+ command(":silent args `echo ***2`")
+ end
+ eq({ "file2", }, eval("argv()"))
+ if helpers.iswin() then
+ command(":silent args `dir /s/b *4`")
+ eq({ "subdir\\file4", }, eval("map(argv(), 'fnamemodify(v:val, \":.\")')"))
+ else
+ command(":silent args `echo */*4`")
+ eq({ "subdir/file4", }, eval("argv()"))
+ end
+ end)
+
+ it("with shell=fish", function()
+ if eval("executable('fish')") == 0 then
+ pending('missing "fish" command')
+ return
+ end
+ command("set shell=fish")
+ command(":silent args `echo ***2`")
+ eq({ "file2", }, eval("argv()"))
+ command(":silent args `echo */*4`")
+ eq({ "subdir/file4", }, eval("argv()"))
+ end)
+end)
diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua
new file mode 100644
index 0000000000..c0e264d4c9
--- /dev/null
+++ b/test/functional/eval/buf_functions_spec.lua
@@ -0,0 +1,304 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local lfs = require('lfs')
+
+local eq = helpers.eq
+local clear = helpers.clear
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local bufmeths = helpers.bufmeths
+local winmeths = helpers.winmeths
+local curbufmeths = helpers.curbufmeths
+local curwinmeths = helpers.curwinmeths
+local curtabmeths = helpers.curtabmeths
+local get_pathsep = helpers.get_pathsep
+local rmdir = helpers.rmdir
+local expect_err = helpers.expect_err
+
+local fname = 'Xtest-functional-eval-buf_functions'
+local fname2 = fname .. '.2'
+local dirname = fname .. '.d'
+
+before_each(clear)
+
+for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
+ 'getbufline(%s, 1)', 'getbufvar(%s, "changedtick")',
+ 'setbufvar(%s, "f", 0)'}) do
+ local funcname = func:match('%w+')
+ describe(funcname .. '() function', function()
+ it('errors out when receives v:true/v:false/v:null', function()
+ -- Not compatible with Vim: in Vim it always results in buffer not found
+ -- without any error messages.
+ for _, var in ipairs({'v:true', 'v:false', 'v:null'}) do
+ eq('Vim(call):E5300: Expected a Number or a String',
+ exc_exec('call ' .. func:format(var)))
+ end
+ end)
+ it('errors out when receives invalid argument', function()
+ eq('Vim(call):E745: Expected a Number or a String, List found',
+ exc_exec('call ' .. func:format('[]')))
+ eq('Vim(call):E728: Expected a Number or a String, Dictionary found',
+ exc_exec('call ' .. func:format('{}')))
+ eq('Vim(call):E805: Expected a Number or a String, Float found',
+ exc_exec('call ' .. func:format('0.0')))
+ eq('Vim(call):E703: Expected a Number or a String, Funcref found',
+ exc_exec('call ' .. func:format('function("tr")')))
+ end)
+ end)
+end
+
+describe('bufname() function', function()
+ it('returns empty string when buffer was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.bufname(2))
+ eq('', funcs.bufname('non-existent-buffer'))
+ eq('', funcs.bufname('#'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('', funcs.bufname('X'))
+ end)
+ before_each(function()
+ lfs.mkdir(dirname)
+ end)
+ after_each(function()
+ rmdir(dirname)
+ end)
+ it('returns expected buffer name', function()
+ eq('', funcs.bufname('%')) -- Buffer has no name yet
+ command('file ' .. fname)
+ local wd = lfs.currentdir()
+ local sep = get_pathsep()
+ local curdirname = funcs.fnamemodify(wd, ':t')
+ for _, arg in ipairs({'%', 1, 'X', wd}) do
+ eq(fname, funcs.bufname(arg))
+ meths.set_current_dir('..')
+ eq(curdirname .. sep .. fname, funcs.bufname(arg))
+ meths.set_current_dir(curdirname)
+ meths.set_current_dir(dirname)
+ eq(wd .. sep .. fname, funcs.bufname(arg))
+ meths.set_current_dir('..')
+ eq(fname, funcs.bufname(arg))
+ command('enew')
+ end
+ eq('', funcs.bufname('%'))
+ eq('', funcs.bufname('$'))
+ eq(2, funcs.bufnr('%'))
+ end)
+end)
+
+describe('bufnr() function', function()
+ it('returns -1 when buffer was not found', function()
+ command('file ' .. fname)
+ eq(-1, funcs.bufnr(2))
+ eq(-1, funcs.bufnr('non-existent-buffer'))
+ eq(-1, funcs.bufnr('#'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq(-1, funcs.bufnr('X'))
+ end)
+ it('returns expected buffer number', function()
+ eq(1, funcs.bufnr('%'))
+ command('file ' .. fname)
+ local wd = lfs.currentdir()
+ local curdirname = funcs.fnamemodify(wd, ':t')
+ eq(1, funcs.bufnr(fname))
+ eq(1, funcs.bufnr(wd))
+ eq(1, funcs.bufnr(curdirname))
+ eq(1, funcs.bufnr('X'))
+ end)
+ it('returns number of last buffer with "$"', function()
+ eq(1, funcs.bufnr('$'))
+ command('new')
+ eq(2, funcs.bufnr('$'))
+ command('new')
+ eq(3, funcs.bufnr('$'))
+ command('only')
+ eq(3, funcs.bufnr('$'))
+ eq(3, funcs.bufnr('%'))
+ command('buffer 1')
+ eq(3, funcs.bufnr('$'))
+ eq(1, funcs.bufnr('%'))
+ command('bwipeout 2')
+ eq(3, funcs.bufnr('$'))
+ eq(1, funcs.bufnr('%'))
+ command('bwipeout 3')
+ eq(1, funcs.bufnr('$'))
+ eq(1, funcs.bufnr('%'))
+ command('new')
+ eq(4, funcs.bufnr('$'))
+ end)
+end)
+
+describe('bufwinnr() function', function()
+ it('returns -1 when buffer was not found', function()
+ command('file ' .. fname)
+ eq(-1, funcs.bufwinnr(2))
+ eq(-1, funcs.bufwinnr('non-existent-buffer'))
+ eq(-1, funcs.bufwinnr('#'))
+ command('split ' .. fname2) -- It would be OK if there was one window
+ eq(2, funcs.bufnr('%'))
+ eq(-1, funcs.bufwinnr('X'))
+ end)
+ before_each(function()
+ lfs.mkdir(dirname)
+ end)
+ after_each(function()
+ rmdir(dirname)
+ end)
+ it('returns expected window number', function()
+ eq(1, funcs.bufwinnr('%'))
+ command('file ' .. fname)
+ command('vsplit')
+ command('split ' .. fname2)
+ eq(2, funcs.bufwinnr(fname))
+ eq(1, funcs.bufwinnr(fname2))
+ eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
+ meths.set_current_dir(dirname)
+ eq(2, funcs.bufwinnr(fname))
+ eq(1, funcs.bufwinnr(fname2))
+ eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
+ eq(1, funcs.bufwinnr('%'))
+ eq(2, funcs.bufwinnr(1))
+ eq(1, funcs.bufwinnr(2))
+ eq(-1, funcs.bufwinnr(3))
+ eq(1, funcs.bufwinnr('$'))
+ end)
+end)
+
+describe('getbufline() function', function()
+ it('returns empty list when buffer was not found', function()
+ command('file ' .. fname)
+ eq({}, funcs.getbufline(2, 1))
+ eq({}, funcs.getbufline('non-existent-buffer', 1))
+ eq({}, funcs.getbufline('#', 1))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq({}, funcs.getbufline('X', 1))
+ end)
+ it('returns empty list when range is invalid', function()
+ eq({}, funcs.getbufline(1, 0))
+ curbufmeths.set_lines(0, 1, false, {'foo', 'bar', 'baz'})
+ eq({}, funcs.getbufline(1, 2, 1))
+ eq({}, funcs.getbufline(1, -10, -20))
+ eq({}, funcs.getbufline(1, -2, -1))
+ eq({}, funcs.getbufline(1, -1, 9999))
+ end)
+ it('returns expected lines', function()
+ meths.set_option('hidden', true)
+ command('file ' .. fname)
+ curbufmeths.set_lines(0, 1, false, {'foo\0', '\0bar', 'baz'})
+ command('edit ' .. fname2)
+ curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
+ eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, 9999))
+ eq({'abc\n', '\ndef', 'ghi'}, funcs.getbufline(2, 1, 9999))
+ eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, '$'))
+ eq({'baz'}, funcs.getbufline(1, '$', '$'))
+ eq({'baz'}, funcs.getbufline(1, '$', 9999))
+ end)
+end)
+
+describe('getbufvar() function', function()
+ it('returns empty list when buffer was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.getbufvar(2, '&autoindent'))
+ eq('', funcs.getbufvar('non-existent-buffer', '&autoindent'))
+ eq('', funcs.getbufvar('#', '&autoindent'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('', funcs.getbufvar('X', '&autoindent'))
+ end)
+ it('returns empty list when variable/option/etc was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.getbufvar(1, '&autondent'))
+ eq('', funcs.getbufvar(1, 'changedtic'))
+ end)
+ it('returns expected option value', function()
+ eq(0, funcs.getbufvar(1, '&autoindent'))
+ eq(0, funcs.getbufvar(1, '&l:autoindent'))
+ eq(0, funcs.getbufvar(1, '&g:autoindent'))
+ -- Also works with global-only options
+ eq(0, funcs.getbufvar(1, '&hidden'))
+ eq(0, funcs.getbufvar(1, '&l:hidden'))
+ eq(0, funcs.getbufvar(1, '&g:hidden'))
+ -- Also works with window-local options
+ eq(0, funcs.getbufvar(1, '&number'))
+ eq(0, funcs.getbufvar(1, '&l:number'))
+ eq(0, funcs.getbufvar(1, '&g:number'))
+ command('new')
+ -- But with window-local options it probably does not what you expect
+ curwinmeths.set_option('number', true)
+ -- (note that current window’s buffer is 2, but getbufvar() receives 1)
+ eq(2, bufmeths.get_number(curwinmeths.get_buf()))
+ eq(1, funcs.getbufvar(1, '&number'))
+ eq(1, funcs.getbufvar(1, '&l:number'))
+ -- You can get global value though, if you find this useful.
+ eq(0, funcs.getbufvar(1, '&g:number'))
+ end)
+ it('returns expected variable value', function()
+ eq(2, funcs.getbufvar(1, 'changedtick'))
+ curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
+ eq(3, funcs.getbufvar(1, 'changedtick'))
+ curbufmeths.set_var('test', true)
+ eq(true, funcs.getbufvar(1, 'test'))
+ eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
+ command('new')
+ eq(3, funcs.getbufvar(1, 'changedtick'))
+ eq(true, funcs.getbufvar(1, 'test'))
+ eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
+ end)
+end)
+
+describe('setbufvar() function', function()
+ it('throws the error or ignores the input when buffer was not found', function()
+ command('file ' .. fname)
+ eq(0,
+ exc_exec('call setbufvar(2, "&autoindent", 0)'))
+ eq('Vim(call):E94: No matching buffer for non-existent-buffer',
+ exc_exec('call setbufvar("non-existent-buffer", "&autoindent", 0)'))
+ eq(0,
+ exc_exec('call setbufvar("#", "&autoindent", 0)'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('Vim(call):E93: More than one match for X',
+ exc_exec('call setbufvar("X", "&autoindent", 0)'))
+ end)
+ it('may set options, including window-local and global values', function()
+ local buf1 = meths.get_current_buf()
+ eq(false, curwinmeths.get_option('number'))
+ command('split')
+ command('new')
+ eq(2, bufmeths.get_number(curwinmeths.get_buf()))
+ funcs.setbufvar(1, '&number', true)
+ local windows = curtabmeths.list_wins()
+ eq(false, winmeths.get_option(windows[1], 'number'))
+ eq(true, winmeths.get_option(windows[2], 'number'))
+ eq(false, winmeths.get_option(windows[3], 'number'))
+ eq(false, winmeths.get_option(meths.get_current_win(), 'number'))
+
+ eq(false, meths.get_option('hidden'))
+ funcs.setbufvar(1, '&hidden', true)
+ eq(true, meths.get_option('hidden'))
+
+ eq(false, bufmeths.get_option(buf1, 'autoindent'))
+ funcs.setbufvar(1, '&autoindent', true)
+ eq(true, bufmeths.get_option(buf1, 'autoindent'))
+ eq('Vim(call):E355: Unknown option: xxx',
+ exc_exec('call setbufvar(1, "&xxx", 0)'))
+ end)
+ it('may set variables', function()
+ local buf1 = meths.get_current_buf()
+ command('split')
+ command('new')
+ eq(2, curbufmeths.get_number())
+ funcs.setbufvar(1, 'number', true)
+ eq(true, bufmeths.get_var(buf1, 'number'))
+ eq('Vim(call):E461: Illegal variable name: b:',
+ exc_exec('call setbufvar(1, "", 0)'))
+ eq(true, bufmeths.get_var(buf1, 'number'))
+ expect_err('Vim:E46: Cannot change read%-only variable "b:changedtick"',
+ funcs.setbufvar, 1, 'changedtick', true)
+ eq(2, funcs.getbufvar(1, 'changedtick'))
+ end)
+end)
diff --git a/test/functional/eval/capture_spec.lua b/test/functional/eval/capture_spec.lua
deleted file mode 100644
index d9265f1b5b..0000000000
--- a/test/functional/eval/capture_spec.lua
+++ /dev/null
@@ -1,86 +0,0 @@
-local helpers = require('test.functional.helpers')(after_each)
-local eq = helpers.eq
-local eval = helpers.eval
-local clear = helpers.clear
-local source = helpers.source
-local redir_exec = helpers.redir_exec
-local exc_exec = helpers.exc_exec
-local funcs = helpers.funcs
-local Screen = require('test.functional.ui.screen')
-local feed = helpers.feed
-
-describe('capture()', function()
- before_each(clear)
-
- it('returns the same result with :redir', function()
- eq(redir_exec('messages'), funcs.capture('messages'))
- end)
-
- it('returns the output of the commands if the argument is List', function()
- eq("foobar", funcs.capture({'echon "foo"', 'echon "bar"'}))
- eq("\nfoo\nbar", funcs.capture({'echo "foo"', 'echo "bar"'}))
- end)
-
- it('supports the nested redirection', function()
- source([[
- function! g:Foo()
- let a = ''
- redir => a
- silent echon "foo"
- redir END
- return a
- endfunction
- function! g:Bar()
- let a = ''
- redir => a
- call g:Foo()
- redir END
- return a
- endfunction
- ]])
- eq('foo', funcs.capture('call g:Bar()'))
-
- eq('42', funcs.capture([[echon capture("echon capture('echon 42')")]]))
- end)
-
- it('returns the transformed string', function()
- eq('^A', funcs.capture('echon "\\<C-a>"'))
- end)
-
- it('returns the empty string if the argument list is empty', function()
- eq('', funcs.capture({}))
- eq(0, exc_exec('let g:ret = capture(v:_null_list)'))
- eq('', eval('g:ret'))
- end)
-
- it('returns the errors', function()
- local ret
- ret = exc_exec('call capture(0.0)')
- eq('Vim(call):E806: using Float as a String', ret)
- ret = exc_exec('call capture(v:_null_dict)')
- eq('Vim(call):E731: using Dictionary as a String', ret)
- ret = exc_exec('call capture(function("tr"))')
- eq('Vim(call):E729: using Funcref as a String', ret)
- ret = exc_exec('call capture(["echo 42", 0.0, "echo 44"])')
- eq('Vim(call):E806: using Float as a String', ret)
- ret = exc_exec('call capture(["echo 42", v:_null_dict, "echo 44"])')
- eq('Vim(call):E731: using Dictionary as a String', ret)
- ret = exc_exec('call capture(["echo 42", function("tr"), "echo 44"])')
- eq('Vim(call):E729: using Funcref as a String', ret)
- end)
-
- it('silences command run inside', function()
- local screen = Screen.new(20, 5)
- screen:attach()
- screen:set_default_attr_ignore({{bold=true, foreground=255}})
- feed(':let g:mes = capture("echon 42")<CR>')
- screen:expect([[
- ^ |
- ~ |
- ~ |
- ~ |
- |
- ]])
- eq('42', eval('g:mes'))
- end)
-end)
diff --git a/test/functional/eval/changedtick_spec.lua b/test/functional/eval/changedtick_spec.lua
new file mode 100644
index 0000000000..60ea9fa12b
--- /dev/null
+++ b/test/functional/eval/changedtick_spec.lua
@@ -0,0 +1,142 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
+local clear = helpers.clear
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local redir_exec = helpers.redir_exec
+local meth_pcall = helpers.meth_pcall
+local curbufmeths = helpers.curbufmeths
+
+before_each(clear)
+
+local function changedtick()
+ local ct = curbufmeths.get_changedtick()
+ eq(ct, curbufmeths.get_var('changedtick'))
+ eq(ct, curbufmeths.get_var('changedtick'))
+ eq(ct, eval('b:changedtick'))
+ eq(ct, eval('b:["changedtick"]'))
+ eq(ct, eval('b:.changedtick'))
+ eq(ct, funcs.getbufvar('%', 'changedtick'))
+ eq(ct, funcs.getbufvar('%', '').changedtick)
+ eq(ct, eval('b:').changedtick)
+ return ct
+end
+
+describe('b:changedtick', function()
+ -- Ported tests from Vim-8.0.333
+ it('increments', function() -- Test_changedtick_increments
+ -- New buffer has an empty line, tick starts at 2
+ eq(2, changedtick())
+ funcs.setline(1, 'hello')
+ eq(3, changedtick())
+ eq(0, exc_exec('undo'))
+ -- Somehow undo counts as two changes
+ eq(5, changedtick())
+ end)
+ it('is present in b: dictionary', function()
+ eq(2, changedtick())
+ command('let d = b:')
+ eq(2, meths.get_var('d').changedtick)
+ end)
+ it('increments at bdel', function()
+ command('new')
+ eq(2, changedtick())
+ local bnr = curbufmeths.get_number()
+ eq(2, bnr)
+ command('bdel')
+ eq(3, funcs.getbufvar(bnr, 'changedtick'))
+ eq(1, curbufmeths.get_number())
+ end)
+ it('fails to be changed by user', function()
+ local ct = changedtick()
+ local ctn = ct + 100500
+ eq(0, exc_exec('let d = b:'))
+ eq('\nE46: Cannot change read-only variable "b:changedtick"',
+ redir_exec('let b:changedtick = ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] = ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:.changedtick"',
+ redir_exec('let b:.changedtick = ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('let d.changedtick = ' .. ctn))
+ eq({false, 'Key is read-only: changedtick'},
+ meth_pcall(curbufmeths.set_var, 'changedtick', ctn))
+
+ eq('\nE795: Cannot delete variable b:changedtick',
+ redir_exec('unlet b:changedtick'))
+ eq('\nE46: Cannot change read-only variable "b:.changedtick"',
+ redir_exec('unlet b:.changedtick'))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('unlet b:["changedtick"]'))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('unlet d.changedtick'))
+ eq({false, 'Key is read-only: changedtick'},
+ meth_pcall(curbufmeths.del_var, 'changedtick'))
+ eq(ct, changedtick())
+
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] += ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] -= ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] .= ' .. ctn))
+
+ eq(ct, changedtick())
+
+ funcs.setline(1, 'hello')
+
+ eq(ct + 1, changedtick())
+ end)
+ it('is listed in :let output', function()
+ eq('\nb:changedtick #2',
+ redir_exec(':let b:'))
+ end)
+ it('fails to unlock b:changedtick', function()
+ eq(0, exc_exec('let d = b:'))
+ eq(0, funcs.islocked('b:changedtick'))
+ eq(0, funcs.islocked('d.changedtick'))
+ eq('\nE940: Cannot lock or unlock variable b:changedtick',
+ redir_exec('unlockvar b:changedtick'))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('unlockvar d.changedtick'))
+ eq(0, funcs.islocked('b:changedtick'))
+ eq(0, funcs.islocked('d.changedtick'))
+ eq('\nE940: Cannot lock or unlock variable b:changedtick',
+ redir_exec('lockvar b:changedtick'))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('lockvar d.changedtick'))
+ eq(0, funcs.islocked('b:changedtick'))
+ eq(0, funcs.islocked('d.changedtick'))
+ end)
+ it('is being completed', function()
+ feed(':echo b:<Tab><Home>let cmdline="<End>"<CR>')
+ eq('echo b:changedtick', meths.get_var('cmdline'))
+ end)
+ it('cannot be changed by filter() or map()', function()
+ eq(2, changedtick())
+ eq('\nE795: Cannot delete variable filter() argument',
+ redir_exec('call filter(b:, 0)'))
+ eq('\nE742: Cannot change value of map() argument',
+ redir_exec('call map(b:, 0)'))
+ eq('\nE742: Cannot change value of map() argument',
+ redir_exec('call map(b:, "v:val")'))
+ eq(2, changedtick())
+ end)
+ it('cannot be remove()d', function()
+ eq(2, changedtick())
+ eq('\nE795: Cannot delete variable remove() argument',
+ redir_exec('call remove(b:, "changedtick")'))
+ eq(2, changedtick())
+ end)
+ it('does not inherit VAR_FIXED when copying dictionary over', function()
+ eq(2, changedtick())
+ eq('', redir_exec('let d1 = copy(b:)|let d1.changedtick = 42'))
+ eq('', redir_exec('let d2 = copy(b:)|unlet d2.changedtick'))
+ eq(2, changedtick())
+ end)
+end)
diff --git a/test/functional/eval/container_functions_spec.lua b/test/functional/eval/container_functions_spec.lua
new file mode 100644
index 0000000000..04a3248c49
--- /dev/null
+++ b/test/functional/eval/container_functions_spec.lua
@@ -0,0 +1,24 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local meths = helpers.meths
+local clear = helpers.clear
+
+before_each(clear)
+
+describe('extend()', function()
+ it('suceeds to extend list with itself', function()
+ meths.set_var('l', {1, {}})
+ eq({1, {}, 1, {}}, eval('extend(l, l)'))
+ eq({1, {}, 1, {}}, meths.get_var('l'))
+
+ meths.set_var('l', {1, {}})
+ eq({1, {}, 1, {}}, eval('extend(l, l, 0)'))
+ eq({1, {}, 1, {}}, meths.get_var('l'))
+
+ meths.set_var('l', {1, {}})
+ eq({1, 1, {}, {}}, eval('extend(l, l, 1)'))
+ eq({1, 1, {}, {}}, meths.get_var('l'))
+ end)
+end)
diff --git a/test/functional/eval/executable_spec.lua b/test/functional/eval/executable_spec.lua
new file mode 100644
index 0000000000..c931b47221
--- /dev/null
+++ b/test/functional/eval/executable_spec.lua
@@ -0,0 +1,156 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq, clear, call, iswin, write_file =
+ helpers.eq, helpers.clear, helpers.call, helpers.iswin, helpers.write_file
+
+describe('executable()', function()
+ before_each(clear)
+
+ it('returns 1 for commands in $PATH', function()
+ local exe = iswin() and 'ping' or 'ls'
+ eq(1, call('executable', exe))
+ end)
+
+ it('returns 0 for non-existent files', function()
+ eq(0, call('executable', 'no_such_file_exists_209ufq23f'))
+ end)
+
+ it('sibling to nvim binary', function()
+ -- Some executable in build/bin/, *not* in $PATH nor CWD.
+ local sibling_exe = 'printargs-test'
+ -- Windows: siblings are in Nvim's "pseudo-$PATH".
+ local expected = iswin() and 1 or 0
+ if iswin() then
+ -- $PATH on AppVeyor CI might be oversized, redefine it to a minimal one.
+ clear({env={PATH=[[C:\Windows\system32;C:\Windows]]}})
+ eq('arg1=lemon;arg2=sky;arg3=tree;',
+ call('system', sibling_exe..' lemon sky tree'))
+ end
+ local is_executable = call('executable', sibling_exe)
+ if iswin() and is_executable ~= expected then
+ pending('XXX: sometimes fails on AppVeyor')
+ end
+ end)
+
+ describe('exec-bit', function()
+ setup(function()
+ clear()
+ write_file('Xtest_not_executable', 'non-executable file')
+ write_file('Xtest_executable', 'executable file (exec-bit set)')
+ if not iswin() then -- N/A for Windows.
+ call('system', {'chmod', '-x', 'Xtest_not_executable'})
+ call('system', {'chmod', '+x', 'Xtest_executable'})
+ end
+ end)
+
+ teardown(function()
+ os.remove('Xtest_not_executable')
+ os.remove('Xtest_executable')
+ end)
+
+ it('not set', function()
+ local expected = iswin() and 1 or 0
+ eq(expected, call('executable', 'Xtest_not_executable'))
+ eq(expected, call('executable', './Xtest_not_executable'))
+ end)
+
+ it('set, unqualified and not in $PATH', function()
+ local expected = iswin() and 1 or 0
+ eq(expected, call('executable', 'Xtest_executable'))
+ end)
+
+ it('set, qualified as a path', function()
+ eq(1, call('executable', './Xtest_executable'))
+ end)
+ end)
+end)
+
+describe('executable() (Windows)', function()
+ if not iswin() then return end -- N/A for Unix.
+
+ local exts = {'bat', 'exe', 'com', 'cmd'}
+ setup(function()
+ for _, ext in ipairs(exts) do
+ write_file('test_executable_'..ext..'.'..ext, '')
+ end
+ write_file('test_executable_zzz.zzz', '')
+ end)
+
+ teardown(function()
+ for _, ext in ipairs(exts) do
+ os.remove('test_executable_'..ext..'.'..ext)
+ end
+ os.remove('test_executable_zzz.zzz')
+ end)
+
+ it('tries default extensions on a filename if $PATHEXT is empty', function()
+ -- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
+ clear({env={PATHEXT=''}})
+ for _,ext in ipairs(exts) do
+ eq(1, call('executable', 'test_executable_'..ext))
+ end
+ eq(0, call('executable', 'test_executable_zzz'))
+ end)
+
+ it('tries default extensions on a filepath if $PATHEXT is empty', function()
+ -- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
+ clear({env={PATHEXT=''}})
+ for _,ext in ipairs(exts) do
+ eq(1, call('executable', '.\\test_executable_'..ext))
+ end
+ eq(0, call('executable', '.\\test_executable_zzz'))
+ end)
+
+ it('full path with extension', function()
+ -- Some executable we can expect in the test env.
+ local exe = 'printargs-test'
+ local exedir = helpers.eval("fnamemodify(v:progpath, ':h')")
+ local exepath = exedir..'/'..exe..'.exe'
+ eq(1, call('executable', exepath))
+ eq('arg1=lemon;arg2=sky;arg3=tree;',
+ call('system', exepath..' lemon sky tree'))
+ end)
+
+ it('full path without extension', function()
+ -- Some executable we can expect in the test env.
+ local exe = 'printargs-test'
+ local exedir = helpers.eval("fnamemodify(v:progpath, ':h')")
+ local exepath = exedir..'/'..exe
+ eq('arg1=lemon;arg2=sky;arg3=tree;',
+ call('system', exepath..' lemon sky tree'))
+ eq(1, call('executable', exepath))
+ end)
+
+ it('respects $PATHEXT when trying extensions on a filename', function()
+ clear({env={PATHEXT='.zzz'}})
+ for _,ext in ipairs(exts) do
+ eq(0, call('executable', 'test_executable_'..ext))
+ end
+ eq(1, call('executable', 'test_executable_zzz'))
+ end)
+
+ it('respects $PATHEXT when trying extensions on a filepath', function()
+ clear({env={PATHEXT='.zzz'}})
+ for _,ext in ipairs(exts) do
+ eq(0, call('executable', '.\\test_executable_'..ext))
+ end
+ eq(1, call('executable', '.\\test_executable_zzz'))
+ end)
+
+ it('returns 1 for any existing filename', function()
+ clear({env={PATHEXT=''}})
+ for _,ext in ipairs(exts) do
+ eq(1, call('executable', 'test_executable_'..ext..'.'..ext))
+ end
+ eq(1, call('executable', 'test_executable_zzz.zzz'))
+ end)
+
+ it('returns 1 for any existing path (backslashes)', function()
+ clear({env={PATHEXT=''}})
+ for _,ext in ipairs(exts) do
+ eq(1, call('executable', '.\\test_executable_'..ext..'.'..ext))
+ eq(1, call('executable', './test_executable_'..ext..'.'..ext))
+ end
+ eq(1, call('executable', '.\\test_executable_zzz.zzz'))
+ eq(1, call('executable', './test_executable_zzz.zzz'))
+ end)
+end)
diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua
new file mode 100644
index 0000000000..af37ab8d55
--- /dev/null
+++ b/test/functional/eval/execute_spec.lua
@@ -0,0 +1,206 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local eval = helpers.eval
+local clear = helpers.clear
+local source = helpers.source
+local redir_exec = helpers.redir_exec
+local exc_exec = helpers.exc_exec
+local funcs = helpers.funcs
+local Screen = require('test.functional.ui.screen')
+local command = helpers.command
+local feed = helpers.feed
+local iswin = helpers.iswin
+
+describe('execute()', function()
+ before_each(clear)
+
+ it('captures the same result as :redir', function()
+ eq(redir_exec('messages'), funcs.execute('messages'))
+ end)
+
+ it('captures the concatenated outputs of a List of commands', function()
+ eq("foobar", funcs.execute({'echon "foo"', 'echon "bar"'}))
+ eq("\nfoo\nbar", funcs.execute({'echo "foo"', 'echo "bar"'}))
+ end)
+
+ it('supports nested execute("execute(...)")', function()
+ eq('42', funcs.execute([[echon execute("echon execute('echon 42')")]]))
+ end)
+
+ it('supports nested :redir to a variable', function()
+ source([[
+ function! g:Foo()
+ let a = ''
+ redir => a
+ silent echon "foo"
+ redir END
+ return a
+ endfunction
+ function! g:Bar()
+ let a = ''
+ redir => a
+ silent echon "bar1"
+ call g:Foo()
+ silent echon "bar2"
+ redir END
+ silent echon "bar3"
+ return a
+ endfunction
+ ]])
+ eq('top1bar1foobar2bar3', funcs.execute('echon "top1"|call g:Bar()'))
+ end)
+
+ it('supports nested :redir to a register', function()
+ source([[
+ let @a = ''
+ function! g:Foo()
+ redir @a>>
+ silent echon "foo"
+ redir END
+ return @a
+ endfunction
+ function! g:Bar()
+ redir @a>>
+ silent echon "bar1"
+ call g:Foo()
+ silent echon "bar2"
+ redir END
+ silent echon "bar3"
+ return @a
+ endfunction
+ ]])
+ eq('top1bar1foobar2bar3', funcs.execute('echon "top1"|call g:Bar()'))
+ -- :redir itself doesn't nest, so the redirection ends in g:Foo
+ eq('bar1foo', eval('@a'))
+ end)
+
+ it('captures a transformed string', function()
+ eq('^A', funcs.execute('echon "\\<C-a>"'))
+ end)
+
+ it('returns empty string if the argument list is empty', function()
+ eq('', funcs.execute({}))
+ eq(0, exc_exec('let g:ret = execute(v:_null_list)'))
+ eq('', eval('g:ret'))
+ end)
+
+ it('captures errors', function()
+ local ret
+ ret = exc_exec('call execute(0.0)')
+ eq('Vim(call):E806: using Float as a String', ret)
+ ret = exc_exec('call execute(v:_null_dict)')
+ eq('Vim(call):E731: using Dictionary as a String', ret)
+ ret = exc_exec('call execute(function("tr"))')
+ eq('Vim(call):E729: using Funcref as a String', ret)
+ ret = exc_exec('call execute(["echo 42", 0.0, "echo 44"])')
+ eq('Vim:E806: using Float as a String', ret)
+ ret = exc_exec('call execute(["echo 42", v:_null_dict, "echo 44"])')
+ eq('Vim:E731: using Dictionary as a String', ret)
+ ret = exc_exec('call execute(["echo 42", function("tr"), "echo 44"])')
+ eq('Vim:E729: using Funcref as a String', ret)
+ end)
+
+ it('captures output with highlights', function()
+ eq('\nErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red',
+ eval('execute("hi ErrorMsg")'))
+ end)
+
+ it('does not corrupt the command display #5422', function()
+ local screen = Screen.new(70, 7)
+ screen:attach()
+ feed(':echo execute("hi ErrorMsg")<CR>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {2: }|
+ :echo execute("hi ErrorMsg") |
+ ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red |
+ {3:Press ENTER or type command to continue}^ |
+ ]], {
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ feed('<CR>')
+ end)
+
+ -- This deviates from vim behavior, but is consistent
+ -- with how nvim currently displays the output.
+ it('does capture shell-command output', function()
+ local win_lf = iswin() and '\13' or ''
+ eq('\n:!echo foo\r\n\nfoo'..win_lf..'\n', funcs.execute('!echo foo'))
+ end)
+
+ describe('{silent} argument', function()
+ it('captures & displays output for ""', function()
+ local screen = Screen.new(40, 5)
+ screen:attach()
+ command('let g:mes = execute("echon 42", "")')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ 42 |
+ ]])
+ eq('42', eval('g:mes'))
+ end)
+
+ it('captures but does not display output for "silent"', function()
+ local screen = Screen.new(40, 5)
+ screen:attach()
+ command('let g:mes = execute("echon 42")')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ eq('42', eval('g:mes'))
+
+ command('let g:mes = execute("echon 13", "silent")')
+ screen:expect{grid=[[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]], unchanged=true}
+ eq('13', eval('g:mes'))
+ end)
+
+ it('suppresses errors for "silent!"', function()
+ eq(0, exc_exec('let g:mes = execute(0.0, "silent!")'))
+ eq('', eval('g:mes'))
+
+ eq(0, exc_exec('let g:mes = execute("echon add(1, 1)", "silent!")'))
+ eq('1', eval('g:mes'))
+
+ eq(0, exc_exec('let g:mes = execute(["echon 42", "echon add(1, 1)"], "silent!")'))
+ eq('421', eval('g:mes'))
+ end)
+
+ it('propagates errors for "" and "silent"', function()
+ local ret
+ ret = exc_exec('call execute(0.0, "")')
+ eq('Vim(call):E806: using Float as a String', ret)
+
+ ret = exc_exec('call execute(v:_null_dict, "silent")')
+ eq('Vim(call):E731: using Dictionary as a String', ret)
+
+ ret = exc_exec('call execute("echo add(1, 1)", "")')
+ eq('Vim(echo):E714: List required', ret)
+
+ ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "")')
+ eq('Vim(echo):E714: List required', ret)
+
+ ret = exc_exec('call execute("echo add(1, 1)", "silent")')
+ eq('Vim(echo):E714: List required', ret)
+
+ ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "silent")')
+ eq('Vim(echo):E714: List required', ret)
+ end)
+ end)
+end)
diff --git a/test/functional/eval/fnamemodify_spec.lua b/test/functional/eval/fnamemodify_spec.lua
new file mode 100644
index 0000000000..fe6b50a544
--- /dev/null
+++ b/test/functional/eval/fnamemodify_spec.lua
@@ -0,0 +1,39 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local iswin = helpers.iswin
+local fnamemodify = helpers.funcs.fnamemodify
+local command = helpers.command
+local write_file = helpers.write_file
+
+describe('fnamemodify()', function()
+ setup(function()
+ write_file('Xtest-fnamemodify.txt', [[foobar]])
+ end)
+
+ before_each(clear)
+
+ teardown(function()
+ os.remove('Xtest-fnamemodify.txt')
+ end)
+
+ it('works', function()
+ local root = helpers.pathroot()
+ eq(root, fnamemodify([[/]], ':p:h'))
+ eq(root, fnamemodify([[/]], ':p'))
+ if iswin() then
+ eq(root, fnamemodify([[\]], ':p:h'))
+ eq(root, fnamemodify([[\]], ':p'))
+ command('set shellslash')
+ root = string.sub(root, 1, -2)..'/'
+ eq(root, fnamemodify([[\]], ':p:h'))
+ eq(root, fnamemodify([[\]], ':p'))
+ eq(root, fnamemodify([[/]], ':p:h'))
+ eq(root, fnamemodify([[/]], ':p'))
+ end
+ end)
+
+ it(':8 works', function()
+ eq('Xtest-fnamemodify.txt', fnamemodify([[Xtest-fnamemodify.txt]], ':8'))
+ end)
+end)
diff --git a/test/functional/eval/function_spec.lua b/test/functional/eval/function_spec.lua
new file mode 100644
index 0000000000..776e760aaf
--- /dev/null
+++ b/test/functional/eval/function_spec.lua
@@ -0,0 +1,29 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local eq = helpers.eq
+local exc_exec = helpers.exc_exec
+
+describe('Up to MAX_FUNC_ARGS arguments are handled by', function()
+ local max_func_args = 20 -- from eval.h
+ local range = helpers.funcs.range
+
+ before_each(clear)
+
+ it('printf()', function()
+ local printf = helpers.funcs.printf
+ local rep = helpers.funcs['repeat']
+ local expected = '2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,'
+ eq(expected, printf(rep('%d,', max_func_args-1), unpack(range(2, max_func_args))))
+ local ret = exc_exec('call printf("", 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)')
+ eq('Vim(call):E740: Too many arguments for function printf', ret)
+ end)
+
+ it('rpcnotify()', function()
+ local rpcnotify = helpers.funcs.rpcnotify
+ local ret = rpcnotify(0, 'foo', unpack(range(3, max_func_args)))
+ eq(1, ret)
+ ret = exc_exec('call rpcnotify(0, "foo", 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)')
+ eq('Vim(call):E740: Too many arguments for function rpcnotify', ret)
+ end)
+end)
diff --git a/test/functional/eval/getline_spec.lua b/test/functional/eval/getline_spec.lua
new file mode 100644
index 0000000000..3c56bde094
--- /dev/null
+++ b/test/functional/eval/getline_spec.lua
@@ -0,0 +1,39 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local call = helpers.call
+local clear = helpers.clear
+local eq = helpers.eq
+local expect = helpers.expect
+
+describe('getline()', function()
+ before_each(function()
+ clear()
+ call('setline', 1, {'a', 'b', 'c'})
+ expect([[
+ a
+ b
+ c]])
+ end)
+
+ it('returns empty string for invalid line', function()
+ eq('', call('getline', -1))
+ eq('', call('getline', 0))
+ eq('', call('getline', 4))
+ end)
+
+ it('returns empty list for invalid range', function()
+ eq({}, call('getline', 2, 1))
+ eq({}, call('getline', -1, 1))
+ eq({}, call('getline', 4, 4))
+ end)
+
+ it('returns value of valid line', function()
+ eq('b', call('getline', 2))
+ eq('a', call('getline', '.'))
+ end)
+
+ it('returns value of valid range', function()
+ eq({'a', 'b'}, call('getline', 1, 2))
+ eq({'a', 'b', 'c'}, call('getline', 1, 4))
+ end)
+end)
diff --git a/test/functional/eval/glob_spec.lua b/test/functional/eval/glob_spec.lua
index 599b3dcdc3..b8807ecfcc 100644
--- a/test/functional/eval/glob_spec.lua
+++ b/test/functional/eval/glob_spec.lua
@@ -1,13 +1,13 @@
local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
-local clear, execute, eval, eq = helpers.clear, helpers.execute, helpers.eval, helpers.eq
+local clear, command, eval, eq = helpers.clear, helpers.command, helpers.eval, helpers.eq
before_each(function()
clear()
lfs.mkdir('test-glob')
-- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
- execute('silent cd test-glob')
+ command('silent cd test-glob')
end)
after_each(function()
diff --git a/test/functional/eval/has_spec.lua b/test/functional/eval/has_spec.lua
new file mode 100644
index 0000000000..a3af2d1a20
--- /dev/null
+++ b/test/functional/eval/has_spec.lua
@@ -0,0 +1,66 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local clear = helpers.clear
+local funcs = helpers.funcs
+local iswin = helpers.iswin
+
+describe('has()', function()
+ before_each(clear)
+
+ it('"nvim-x.y.z"', function()
+ eq(0, funcs.has("nvim-"))
+ eq(0, funcs.has("nvim- "))
+ eq(0, funcs.has("nvim- \t "))
+ eq(0, funcs.has("nvim-0. 1. 1"))
+ eq(0, funcs.has("nvim-0. 1.1"))
+ eq(0, funcs.has("nvim-0.1. 1"))
+ eq(0, funcs.has("nvim-a"))
+ eq(0, funcs.has("nvim-a.b.c"))
+ eq(0, funcs.has("nvim-0.b.c"))
+ eq(0, funcs.has("nvim-0.0.c"))
+ eq(0, funcs.has("nvim-0.b.0"))
+ eq(0, funcs.has("nvim-a.b.0"))
+ eq(0, funcs.has("nvim-.0.0.0"))
+ eq(0, funcs.has("nvim-.0"))
+ eq(0, funcs.has("nvim-0."))
+ eq(0, funcs.has("nvim-0.."))
+ eq(0, funcs.has("nvim-."))
+ eq(0, funcs.has("nvim-.."))
+ eq(0, funcs.has("nvim-..."))
+ eq(0, funcs.has("nvim-42"))
+ eq(0, funcs.has("nvim-9999"))
+ eq(0, funcs.has("nvim-99.001.05"))
+
+ eq(1, funcs.has("nvim"))
+ eq(1, funcs.has("nvim-0"))
+ eq(1, funcs.has("nvim-0.1"))
+ eq(1, funcs.has("nvim-0.0.0"))
+ eq(1, funcs.has("nvim-0.1.1."))
+ eq(1, funcs.has("nvim-0.1.1.abc"))
+ eq(1, funcs.has("nvim-0.1.1.."))
+ eq(1, funcs.has("nvim-0.1.1.. .."))
+ eq(1, funcs.has("nvim-0.1.1.... "))
+ eq(1, funcs.has("nvim-0.0.0"))
+ eq(1, funcs.has("nvim-0.0.1"))
+ eq(1, funcs.has("nvim-0.1.0"))
+ eq(1, funcs.has("nvim-0.1.1"))
+ eq(1, funcs.has("nvim-0.1.5"))
+ eq(1, funcs.has("nvim-0000.001.05"))
+ eq(1, funcs.has("nvim-0.01.005"))
+ eq(1, funcs.has("nvim-00.001.05"))
+ end)
+
+ it('"unnamedplus"', function()
+ if (not iswin()) and funcs.has("clipboard") == 1 then
+ eq(1, funcs.has("unnamedplus"))
+ else
+ eq(0, funcs.has("unnamedplus"))
+ end
+ end)
+
+ it('"wsl"', function()
+ if 1 == funcs.has('win32') or 1 == funcs.has('mac') then
+ eq(0, funcs.has('wsl'))
+ end
+ end)
+end)
diff --git a/test/functional/eval/hostname_spec.lua b/test/functional/eval/hostname_spec.lua
new file mode 100644
index 0000000000..6112cf64e3
--- /dev/null
+++ b/test/functional/eval/hostname_spec.lua
@@ -0,0 +1,20 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local ok = helpers.ok
+local call = helpers.call
+local clear = helpers.clear
+local iswin = helpers.iswin
+
+describe('hostname()', function()
+ before_each(clear)
+
+ it('returns hostname string', function()
+ local actual = call('hostname')
+ ok(string.len(actual) > 0)
+ if call('executable', 'hostname') == 1 then
+ local expected = string.gsub(call('system', 'hostname'), '[\n\r]', '')
+ eq((iswin() and expected:upper() or expected),
+ (iswin() and actual:upper() or actual))
+ end
+ end)
+end)
diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua
new file mode 100644
index 0000000000..e774b939f7
--- /dev/null
+++ b/test/functional/eval/input_spec.lua
@@ -0,0 +1,483 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local eq = helpers.eq
+local feed = helpers.feed
+local meths = helpers.meths
+local clear = helpers.clear
+local source = helpers.source
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local nvim_async = helpers.nvim_async
+
+local screen
+
+before_each(function()
+ clear()
+ screen = Screen.new(25, 5)
+ screen:attach()
+ source([[
+ hi Test ctermfg=Red guifg=Red term=bold
+ function CustomCompl(...)
+ return 'TEST'
+ endfunction
+ function CustomListCompl(...)
+ return ['FOO']
+ endfunction
+
+ highlight RBP1 guibg=Red
+ highlight RBP2 guibg=Yellow
+ highlight RBP3 guibg=Green
+ highlight RBP4 guibg=Blue
+ let g:NUM_LVLS = 4
+ function Redraw()
+ redraw!
+ return ''
+ endfunction
+ cnoremap <expr> {REDRAW} Redraw()
+ function RainBowParens(cmdline)
+ let ret = []
+ let i = 0
+ let lvl = 0
+ while i < len(a:cmdline)
+ if a:cmdline[i] is# '('
+ call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
+ let lvl += 1
+ elseif a:cmdline[i] is# ')'
+ let lvl -= 1
+ call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
+ endif
+ let i += 1
+ endwhile
+ return ret
+ endfunction
+ ]])
+ screen:set_default_attr_ids({
+ EOB={bold = true, foreground = Screen.colors.Blue1},
+ T={foreground=Screen.colors.Red},
+ RBP1={background=Screen.colors.Red},
+ RBP2={background=Screen.colors.Yellow},
+ RBP3={background=Screen.colors.Green},
+ RBP4={background=Screen.colors.Blue},
+ SEP={bold = true, reverse = true},
+ CONFIRM={bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+end)
+
+describe('input()', function()
+ it('works with multiline prompts', function()
+ feed([[:call input("Test\nFoo")<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {SEP: }|
+ Test |
+ Foo^ |
+ ]])
+ end)
+ it('works with multiline prompts and :echohl', function()
+ feed([[:echohl Test | call input("Test\nFoo")<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {SEP: }|
+ {T:Test} |
+ {T:Foo}^ |
+ ]])
+ command('redraw!')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo}^ |
+ ]])
+ end)
+ it('allows unequal numeric arguments when using multiple args', function()
+ command('echohl Test')
+ feed([[:call input(1, 2)<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}2^ |
+ ]])
+ feed('<BS>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}^ |
+ ]])
+ end)
+ it('allows unequal numeric values when using {opts} dictionary', function()
+ command('echohl Test')
+ meths.set_var('opts', {prompt=1, default=2, cancelreturn=3})
+ feed([[:echo input(opts)<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}2^ |
+ ]])
+ feed('<BS>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:3} |
+ ]])
+ end)
+ it('works with redraw', function()
+ command('echohl Test')
+ meths.set_var('opts', {prompt='Foo>', default='Bar'})
+ feed([[:echo inputdialog(opts)<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Bar^ |
+ ]])
+ command('mode')
+ screen:expect{grid=[[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Bar^ |
+ ]], reset=true}
+ feed('<BS>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Ba^ |
+ ]])
+ command('mode')
+ screen:expect{grid=[[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Ba^ |
+ ]], reset=true}
+ end)
+ it('allows omitting everything with dictionary argument', function()
+ command('echohl Test')
+ feed([[:call input({})<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ ^ |
+ ]])
+ end)
+ it('supports completion', function()
+ feed(':let var = input("", "", "custom,CustomCompl")<CR>')
+ feed('<Tab><CR>')
+ eq('TEST', meths.get_var('var'))
+
+ feed(':let var = input({"completion": "customlist,CustomListCompl"})<CR>')
+ feed('<Tab><CR>')
+ eq('FOO', meths.get_var('var'))
+ end)
+ it('supports cancelreturn', function()
+ feed(':let var = input({"cancelreturn": "BAR"})<CR>')
+ feed('<Esc>')
+ eq('BAR', meths.get_var('var'))
+ end)
+ it('supports default string', function()
+ feed(':let var = input("", "DEF1")<CR>')
+ feed('<CR>')
+ eq('DEF1', meths.get_var('var'))
+
+ feed(':let var = input({"default": "DEF2"})<CR>')
+ feed('<CR>')
+ eq('DEF2', meths.get_var('var'))
+ end)
+ it('errors out on invalid inputs', function()
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input([])'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input("", [])'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input("", "", [])'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input({"prompt": []})'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input({"cancelreturn": []})'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input({"default": []})'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call input({"completion": []})'))
+ eq('Vim(call):E5050: {opts} must be the only argument',
+ exc_exec('call input({}, "default")'))
+ eq('Vim(call):E118: Too many arguments for function: input',
+ exc_exec('call input("prompt> ", "default", "file", "extra")'))
+ end)
+ it('supports highlighting', function()
+ command('nnoremap <expr> X input({"highlight": "RainBowParens"})[-1]')
+ feed([[X]])
+ feed('(())')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {RBP1:(}{RBP2:()}{RBP1:)}^ |
+ ]])
+ end)
+ it('is not hidden by :silent', function()
+ feed([[:silent call input('Foo: ')<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {SEP: }|
+ Foo: ^ |
+ |
+ ]])
+ feed('Bar')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {SEP: }|
+ Foo: Bar^ |
+ |
+ ]])
+ feed('<CR>')
+ end)
+end)
+describe('inputdialog()', function()
+ it('works with multiline prompts', function()
+ feed([[:call inputdialog("Test\nFoo")<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {SEP: }|
+ Test |
+ Foo^ |
+ ]])
+ end)
+ it('works with multiline prompts and :echohl', function()
+ feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {SEP: }|
+ {T:Test} |
+ {T:Foo}^ |
+ ]])
+ command('redraw!')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo}^ |
+ ]])
+ end)
+ it('allows unequal numeric arguments when using multiple args', function()
+ command('echohl Test')
+ feed([[:call inputdialog(1, 2)<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}2^ |
+ ]])
+ feed('<BS>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}^ |
+ ]])
+ end)
+ it('allows unequal numeric values when using {opts} dictionary', function()
+ command('echohl Test')
+ meths.set_var('opts', {prompt=1, default=2, cancelreturn=3})
+ feed([[:echo input(opts)<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}2^ |
+ ]])
+ feed('<BS>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:1}^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:3} |
+ ]])
+ end)
+ it('works with redraw', function()
+ command('echohl Test')
+ meths.set_var('opts', {prompt='Foo>', default='Bar'})
+ feed([[:echo input(opts)<CR>]])
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Bar^ |
+ ]])
+ command('mode')
+ screen:expect{grid=[[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Bar^ |
+ ]], reset=true}
+ feed('<BS>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Ba^ |
+ ]])
+ command('mode')
+ screen:expect{grid=[[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {T:Foo>}Ba^ |
+ ]], reset=true}
+ end)
+ it('allows omitting everything with dictionary argument', function()
+ command('echohl Test')
+ feed(':echo inputdialog({})<CR>')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ ^ |
+ ]])
+ end)
+ it('supports completion', function()
+ feed(':let var = inputdialog({"completion": "customlist,CustomListCompl"})<CR>')
+ feed('<Tab><CR>')
+ eq('FOO', meths.get_var('var'))
+ end)
+ it('supports cancelreturn', function()
+ feed(':let var = inputdialog("", "", "CR1")<CR>')
+ feed('<Esc>')
+ eq('CR1', meths.get_var('var'))
+
+ feed(':let var = inputdialog({"cancelreturn": "BAR"})<CR>')
+ feed('<Esc>')
+ eq('BAR', meths.get_var('var'))
+ end)
+ it('supports default string', function()
+ feed(':let var = inputdialog("", "DEF1")<CR>')
+ feed('<CR>')
+ eq('DEF1', meths.get_var('var'))
+
+ feed(':let var = inputdialog({"default": "DEF2"})<CR>')
+ feed('<CR>')
+ eq('DEF2', meths.get_var('var'))
+ end)
+ it('errors out on invalid inputs', function()
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog([])'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog("", [])'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog("", "", [])'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog({"prompt": []})'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog({"cancelreturn": []})'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog({"default": []})'))
+ eq('Vim(call):E730: using List as a String',
+ exc_exec('call inputdialog({"completion": []})'))
+ eq('Vim(call):E5050: {opts} must be the only argument',
+ exc_exec('call inputdialog({}, "default")'))
+ eq('Vim(call):E118: Too many arguments for function: inputdialog',
+ exc_exec('call inputdialog("prompt> ", "default", "file", "extra")'))
+ end)
+ it('supports highlighting', function()
+ command('nnoremap <expr> X inputdialog({"highlight": "RainBowParens"})[-1]')
+ feed([[X]])
+ feed('(())')
+ screen:expect([[
+ |
+ {EOB:~ }|
+ {EOB:~ }|
+ {EOB:~ }|
+ {RBP1:(}{RBP2:()}{RBP1:)}^ |
+ ]])
+ end)
+end)
+
+describe('confirm()', function()
+ it("shows dialog even if :silent #8788", function()
+ command("autocmd BufNewFile * call confirm('test')")
+
+ local function check_and_clear(edit_line)
+ screen:expect([[
+ |
+ {SEP: }|
+ ]]..edit_line..[[
+ {CONFIRM:test} |
+ {CONFIRM:[O]k: }^ |
+ ]])
+ feed('<cr>')
+ command('redraw')
+ command('bdelete!')
+ end
+
+ -- With shortmess-=F
+ command('set shortmess-=F')
+ feed(':edit foo<cr>')
+ check_and_clear('"foo" [New File] |\n')
+
+ -- With shortmess+=F
+ command('set shortmess+=F')
+ feed(':edit foo<cr>')
+ check_and_clear(':edit foo |\n')
+
+ -- With :silent
+ feed(':silent edit foo<cr>')
+ check_and_clear(':silent edit foo |\n')
+
+ -- With API (via eval/VimL) call and shortmess+=F
+ feed(':call nvim_command("edit x")<cr>')
+ check_and_clear(':call nvim_command("edit |\n')
+
+ nvim_async('command', 'edit x')
+ check_and_clear(' |\n')
+ end)
+end)
diff --git a/test/functional/eval/interrupt_spec.lua b/test/functional/eval/interrupt_spec.lua
new file mode 100644
index 0000000000..7f4ca95317
--- /dev/null
+++ b/test/functional/eval/interrupt_spec.lua
@@ -0,0 +1,61 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local command = helpers.command
+local meths = helpers.meths
+local clear = helpers.clear
+local sleep = helpers.sleep
+local wait = helpers.wait
+local feed = helpers.feed
+local eq = helpers.eq
+
+local dur
+local min_dur = 8
+local len = 131072
+
+describe('List support code', function()
+ if not pending('does not actually allows interrupting with just got_int', function() end) then return end
+ -- The following tests are confirmed to work with os_breakcheck() just before
+ -- `if (got_int) {break;}` in tv_list_copy and list_join_inner() and not to
+ -- work without.
+ setup(function()
+ clear()
+ dur = 0
+ while true do
+ command(([[
+ let rt = reltime()
+ let bl = range(%u)
+ let dur = reltimestr(reltime(rt))
+ ]]):format(len))
+ dur = tonumber(meths.get_var('dur'))
+ if dur >= min_dur then
+ -- print(('Using len %u, dur %g'):format(len, dur))
+ break
+ else
+ len = len * 2
+ end
+ end
+ end)
+ it('allows interrupting copy', function()
+ feed(':let t_rt = reltime()<CR>:let t_bl = copy(bl)<CR>')
+ sleep(min_dur / 16 * 1000)
+ feed('<C-c>')
+ wait()
+ command('let t_dur = reltimestr(reltime(t_rt))')
+ local t_dur = tonumber(meths.get_var('t_dur'))
+ if t_dur >= dur / 8 then
+ eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8))
+ end
+ end)
+ it('allows interrupting join', function()
+ feed(':let t_rt = reltime()<CR>:let t_j = join(bl)<CR>')
+ sleep(min_dur / 16 * 1000)
+ feed('<C-c>')
+ wait()
+ command('let t_dur = reltimestr(reltime(t_rt))')
+ local t_dur = tonumber(meths.get_var('t_dur'))
+ print(('t_dur: %g'):format(t_dur))
+ if t_dur >= dur / 8 then
+ eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8))
+ end
+ end)
+end)
diff --git a/test/functional/eval/json_functions_spec.lua b/test/functional/eval/json_functions_spec.lua
index 159d775ff1..8dcaea806e 100644
--- a/test/functional/eval/json_functions_spec.lua
+++ b/test/functional/eval/json_functions_spec.lua
@@ -4,16 +4,17 @@ local funcs = helpers.funcs
local meths = helpers.meths
local eq = helpers.eq
local eval = helpers.eval
-local execute = helpers.execute
+local command = helpers.command
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local NIL = helpers.NIL
+local source = helpers.source
describe('json_decode() function', function()
local restart = function(...)
clear(...)
- execute('language C')
- execute([[
+ source([[
+ language C
function Eq(exp, act)
let act = a:act
let exp = a:exp
@@ -45,8 +46,6 @@ describe('json_decode() function', function()
endif
return 1
endfunction
- ]])
- execute([[
function EvalEq(exp, act_expr)
let act = eval(a:act_expr)
if Eq(a:exp, act)
@@ -441,7 +440,7 @@ describe('json_decode() function', function()
local sp_decode_eq = function(expected, json)
meths.set_var('__json', json)
speq(expected, 'json_decode(g:__json)')
- execute('unlet! g:__json')
+ command('unlet! g:__json')
end
it('parses strings with NUL properly', function()
@@ -489,18 +488,6 @@ describe('json_decode() function', function()
'{"b": 3, "a": 1, "c": 4, "d": 2, "\\u0000": 4}')
end)
- it('converts strings to latin1 when &encoding is latin1', function()
- restart('--cmd', 'set encoding=latin1')
- eq('\171', funcs.json_decode('"\\u00AB"'))
- sp_decode_eq({_TYPE='string', _VAL={'\n\171\n'}}, '"\\u0000\\u00AB\\u0000"')
- end)
-
- it('fails to convert string to latin1 if it is impossible', function()
- restart('--cmd', 'set encoding=latin1')
- eq('Vim(call):E474: Failed to convert string "ꯍ" from UTF-8',
- exc_exec('call json_decode(\'"\\uABCD"\')'))
- end)
-
it('parses U+00C3 correctly', function()
eq('\195\131', funcs.json_decode('"\195\131"'))
end)
@@ -528,14 +515,6 @@ describe('json_decode() function', function()
eq({key={'val', 'val2'}, key2=1}, funcs.json_decode(str))
end)
- it('always treats input as UTF-8', function()
- -- When &encoding is latin1 string "«" is U+00C2 U+00AB U+00C2: «Â. So if
- -- '"«"' was parsed as latin1 json_decode would return three characters, and
- -- only one U+00AB when this string is parsed as latin1.
- restart('--cmd', 'set encoding=latin1')
- eq(('%c'):format(0xAB), funcs.json_decode('"«"'))
- end)
-
it('does not overflow when writing error message about decoding ["", ""]',
function()
eq('\nE474: Attempt to decode a blank string'
@@ -547,7 +526,7 @@ end)
describe('json_encode() function', function()
before_each(function()
clear()
- execute('language C')
+ command('language C')
end)
it('dumps strings', function()
@@ -596,94 +575,94 @@ describe('json_encode() function', function()
it('cannot dump generic mapping with generic mapping keys and values',
function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('call add(todump._VAL, [todumpv1, todumpv2])')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('call add(todump._VAL, [todumpv1, todumpv2])')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with ext key', function()
- execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with array key', function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with UINT64_MAX key', function()
- execute('let todump = {"_TYPE": v:msgpack_types.integer}')
- execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.integer}')
+ command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with floating-point key', function()
- execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('can dump generic mapping with STR special key and NUL', function()
- execute('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n"]}')
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n"]}')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('{"\\u0000": 1}', eval('json_encode(todump)'))
end)
it('can dump generic mapping with BIN special key and NUL', function()
- execute('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n"]}')
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n"]}')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('{"\\u0000": 1}', eval('json_encode(todump)'))
end)
it('can dump STR special mapping with NUL and NL', function()
- execute('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n", ""]}')
+ command('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n", ""]}')
eq('"\\u0000\\n"', eval('json_encode(todump)'))
end)
it('can dump BIN special mapping with NUL and NL', function()
- execute('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n", ""]}')
+ command('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n", ""]}')
eq('"\\u0000\\n"', eval('json_encode(todump)'))
end)
it('cannot dump special ext mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
eq('Vim(call):E474: Unable to convert EXT string to JSON', exc_exec('call json_encode(todump)'))
end)
it('can dump special array mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
eq('[5, [""]]', eval('json_encode(todump)'))
end)
it('can dump special UINT64_MAX mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.integer}')
- execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
+ command('let todump = {"_TYPE": v:msgpack_types.integer}')
+ command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
eq('18446744073709551615', eval('json_encode(todump)'))
end)
it('can dump special INT64_MIN mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.integer}')
- execute('let todump._VAL = [-1, 2, 0, 0]')
+ command('let todump = {"_TYPE": v:msgpack_types.integer}')
+ command('let todump._VAL = [-1, 2, 0, 0]')
eq('-9223372036854775808', eval('json_encode(todump)'))
end)
it('can dump special BOOLEAN true mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
+ command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
eq('true', eval('json_encode(todump)'))
end)
it('can dump special BOOLEAN false mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
+ command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
eq('false', eval('json_encode(todump)'))
end)
it('can dump special NIL mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
+ command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
eq('null', eval('json_encode(todump)'))
end)
@@ -692,62 +671,68 @@ describe('json_encode() function', function()
exc_exec('call json_encode(function("tr"))'))
end)
+ it('fails to dump a partial', function()
+ command('function T() dict\nendfunction')
+ eq('Vim(call):E474: Error while dumping encode_tv2json() argument, itself: attempt to dump function reference',
+ exc_exec('call json_encode(function("T", [1, 2], {}))'))
+ end)
+
it('fails to dump a function reference in a list', function()
eq('Vim(call):E474: Error while dumping encode_tv2json() argument, index 0: attempt to dump function reference',
exc_exec('call json_encode([function("tr")])'))
end)
it('fails to dump a recursive list', function()
- execute('let todump = [[[]]]')
- execute('call add(todump[0][0], todump)')
+ command('let todump = [[[]]]')
+ command('call add(todump[0][0], todump)')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
it('fails to dump a recursive dict', function()
- execute('let todump = {"d": {"d": {}}}')
- execute('call extend(todump.d.d, {"d": todump})')
+ command('let todump = {"d": {"d": {}}}')
+ command('call extend(todump.d.d, {"d": todump})')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode([todump])'))
end)
it('can dump dict with two same dicts inside', function()
- execute('let inter = {}')
- execute('let todump = {"a": inter, "b": inter}')
+ command('let inter = {}')
+ command('let todump = {"a": inter, "b": inter}')
eq('{"a": {}, "b": {}}', eval('json_encode(todump)'))
end)
it('can dump list with two same lists inside', function()
- execute('let inter = []')
- execute('let todump = [inter, inter]')
+ command('let inter = []')
+ command('let todump = [inter, inter]')
eq('[[], []]', eval('json_encode(todump)'))
end)
it('fails to dump a recursive list in a special dict', function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
- execute('call add(todump._VAL, todump)')
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
+ command('call add(todump._VAL, todump)')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
it('fails to dump a recursive (val) map in a special dict', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('call add(todump._VAL, ["", todump])')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('call add(todump._VAL, ["", todump])')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode([todump])'))
end)
it('fails to dump a recursive (val) map in a special dict, _VAL reference', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [["", []]]}')
- execute('call add(todump._VAL[0][1], todump._VAL)')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [["", []]]}')
+ command('call add(todump._VAL[0][1], todump._VAL)')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
it('fails to dump a recursive (val) special list in a special dict',
function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
- execute('call add(todump._VAL, ["", todump._VAL])')
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
+ command('call add(todump._VAL, ["", todump._VAL])')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
@@ -762,12 +747,6 @@ describe('json_encode() function', function()
exc_exec('call json_encode(["", ""], 1)'))
end)
- it('converts strings from latin1 when &encoding is latin1', function()
- clear('--cmd', 'set encoding=latin1')
- eq('"\\u00AB"', funcs.json_encode('\171'))
- eq('"\\u0000\\u00AB\\u0000"', eval('json_encode({"_TYPE": v:msgpack_types.string, "_VAL": ["\\n\171\\n"]})'))
- end)
-
it('ignores improper values in &isprint', function()
meths.set_option('isprint', '1')
eq(1, eval('"\1" =~# "\\\\p"'))
@@ -797,4 +776,11 @@ describe('json_encode() function', function()
it('can dump NULL dictionary', function()
eq('{}', eval('json_encode(v:_null_dict)'))
end)
+
+ it('fails to parse NULL strings and lists', function()
+ eq('Vim(call):E474: Attempt to decode a blank string',
+ exc_exec('call json_decode($XXX_UNEXISTENT_VAR_XXX)'))
+ eq('Vim(call):E474: Attempt to decode a blank string',
+ exc_exec('call json_decode(v:_null_list)'))
+ end)
end)
diff --git a/test/functional/eval/let_spec.lua b/test/functional/eval/let_spec.lua
new file mode 100644
index 0000000000..1bd3405698
--- /dev/null
+++ b/test/functional/eval/let_spec.lua
@@ -0,0 +1,45 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local clear = helpers.clear
+local meths = helpers.meths
+local redir_exec = helpers.redir_exec
+local source = helpers.source
+
+before_each(clear)
+
+describe(':let command', function()
+ it('correctly lists variables with curly-braces', function()
+ meths.set_var('v', {0})
+ eq('\nv [0]', redir_exec('let {"v"}'))
+ end)
+
+ it('correctly lists variables with subscript', function()
+ meths.set_var('v', {0})
+ eq('\nv[0] #0', redir_exec('let v[0]'))
+ eq('\ng:["v"][0] #0', redir_exec('let g:["v"][0]'))
+ eq('\n{"g:"}["v"][0] #0', redir_exec('let {"g:"}["v"][0]'))
+ end)
+
+ it(":unlet self-referencing node in a List graph #6070", function()
+ -- :unlet-ing a self-referencing List must not allow GC on indirectly
+ -- referenced in-scope Lists. Before #6070 this caused use-after-free.
+ source([=[
+ let [l1, l2] = [[], []]
+ echo 'l1:' . id(l1)
+ echo 'l2:' . id(l2)
+ echo ''
+ let [l3, l4] = [[], []]
+ call add(l4, l4)
+ call add(l4, l3)
+ call add(l3, 1)
+ call add(l2, l2)
+ call add(l2, l1)
+ call add(l1, 1)
+ unlet l2
+ unlet l4
+ call garbagecollect(1)
+ call feedkeys(":\e:echo l1 l3\n:echo 42\n:cq\n", "t")
+ ]=])
+ end)
+end)
diff --git a/test/functional/eval/map_functions_spec.lua b/test/functional/eval/map_functions_spec.lua
new file mode 100644
index 0000000000..e914f674aa
--- /dev/null
+++ b/test/functional/eval/map_functions_spec.lua
@@ -0,0 +1,159 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local eq = helpers.eq
+local eval = helpers.eval
+local funcs = helpers.funcs
+local nvim = helpers.nvim
+local source = helpers.source
+local command = helpers.command
+
+describe('maparg()', function()
+ before_each(clear)
+
+ local foo_bar_map_table = {
+ lhs='foo',
+ silent=0,
+ rhs='bar',
+ expr=0,
+ sid=0,
+ buffer=0,
+ nowait=0,
+ mode='n',
+ noremap=1,
+ }
+
+ it('returns a dictionary', function()
+ nvim('command', 'nnoremap foo bar')
+ eq('bar', funcs.maparg('foo'))
+ eq(foo_bar_map_table, funcs.maparg('foo', 'n', false, true))
+ end)
+
+ it('returns 1 for silent when <silent> is used', function()
+ nvim('command', 'nnoremap <silent> foo bar')
+ eq(1, funcs.maparg('foo', 'n', false, true)['silent'])
+
+ nvim('command', 'nnoremap baz bat')
+ eq(0, funcs.maparg('baz', 'n', false, true)['silent'])
+ end)
+
+ it('returns an empty string when no map is present', function()
+ eq('', funcs.maparg('not a mapping'))
+ end)
+
+ it('returns an empty dictionary when no map is present and dict is requested', function()
+ eq({}, funcs.maparg('not a mapping', 'n', false, true))
+ end)
+
+ it('returns the same value for noremap and <script>', function()
+ nvim('command', 'inoremap <script> hello world')
+ nvim('command', 'inoremap this that')
+ eq(
+ funcs.maparg('hello', 'i', false, true)['noremap'],
+ funcs.maparg('this', 'i', false, true)['noremap']
+ )
+ end)
+
+ it('returns a boolean for buffer', function()
+ -- Open enough windows to know we aren't on buffer number 1
+ nvim('command', 'new')
+ nvim('command', 'new')
+ nvim('command', 'new')
+ nvim('command', 'cnoremap <buffer> this that')
+ eq(1, funcs.maparg('this', 'c', false, true)['buffer'])
+
+ -- Global will return 0 always
+ nvim('command', 'nnoremap other another')
+ eq(0, funcs.maparg('other', 'n', false, true)['buffer'])
+ end)
+
+ it('returns script numbers', function()
+ source([[
+ function! s:maparg_test_function() abort
+ return 'testing'
+ endfunction
+
+ nnoremap fizz :call <SID>maparg_test_function()<CR>
+ ]])
+ eq(1, funcs.maparg('fizz', 'n', false, true)['sid'])
+ eq('testing', nvim('call_function', '<SNR>1_maparg_test_function', {}))
+ end)
+
+ it('works with <F12> and others', function()
+ source([[
+ let g:maparg_test_var = 0
+
+ nnoremap <F12> :let g:maparg_test_var = 1<CR>
+ ]])
+ eq(0, eval('g:maparg_test_var'))
+ source([[
+ call feedkeys("\<F12>")
+ ]])
+ eq(1, eval('g:maparg_test_var'))
+
+ eq(':let g:maparg_test_var = 1<CR>', funcs.maparg('<F12>', 'n', false, true)['rhs'])
+ end)
+
+ it('works with <expr>', function()
+ source([[
+ let counter = 0
+ inoremap <expr> <C-L> ListItem()
+ inoremap <expr> <C-R> ListReset()
+
+ func ListItem()
+ let g:counter += 1
+ return g:counter . '. '
+ endfunc
+
+ func ListReset()
+ let g:counter = 0
+ return ''
+ endfunc
+
+ call feedkeys("i\<C-L>")
+ ]])
+ eq(1, eval('g:counter'))
+
+ local map_dict = funcs.maparg('<C-L>', 'i', false, true)
+ eq(1, map_dict['expr'])
+ eq('i', map_dict['mode'])
+ end)
+
+ it('works with combining characters', function()
+ -- Using addacutes to make combining character better visible
+ local function ac(s)
+ local acute = '\204\129' -- U+0301 COMBINING ACUTE ACCENT
+ local ret = s:gsub('`', acute)
+ return ret
+ end
+ command(ac([[
+ nnoremap a b`
+ nnoremap c` d
+ nnoremap e` f`
+ ]]))
+ eq(ac('b`'), funcs.maparg(ac('a')))
+ eq(ac(''), funcs.maparg(ac('c')))
+ eq(ac('d'), funcs.maparg(ac('c`')))
+ eq(ac('f`'), funcs.maparg(ac('e`')))
+
+ local function acmap(lhs, rhs)
+ return {
+ lhs = ac(lhs),
+ rhs = ac(rhs),
+
+ buffer = 0,
+ expr = 0,
+ mode = 'n',
+ noremap = 1,
+ nowait = 0,
+ sid = 0,
+ silent = 0,
+ }
+ end
+
+ eq({}, funcs.maparg(ac('c'), 'n', 0, 1))
+ eq(acmap('a', 'b`'), funcs.maparg(ac('a'), 'n', 0, 1))
+ eq(acmap('c`', 'd'), funcs.maparg(ac('c`'), 'n', 0, 1))
+ eq(acmap('e`', 'f`'), funcs.maparg(ac('e`'), 'n', 0, 1))
+ end)
+end)
diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua
new file mode 100644
index 0000000000..0ec465a34c
--- /dev/null
+++ b/test/functional/eval/match_functions_spec.lua
@@ -0,0 +1,158 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local eq = helpers.eq
+local clear = helpers.clear
+local funcs = helpers.funcs
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local expect_err = helpers.expect_err
+
+before_each(clear)
+
+describe('setmatches()', function()
+ it('correctly handles case when both group and pattern entries are numbers',
+ function()
+ command('hi def link 1 PreProc')
+ eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}}))
+ eq({{
+ group='1',
+ pattern='2',
+ id=3,
+ priority=4,
+ }}, funcs.getmatches())
+ eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4, conceal=5}}))
+ eq({{
+ group='1',
+ pattern='2',
+ id=3,
+ priority=4,
+ conceal='5',
+ }}, funcs.getmatches())
+ eq(0, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}))
+ eq({{
+ group='1',
+ pos1={2},
+ pos2={6},
+ id=3,
+ priority=4,
+ conceal='5',
+ }}, funcs.getmatches())
+ end)
+
+ it('fails with -1 if highlight group is not defined', function()
+ expect_err('E28: No such highlight group name: 1', funcs.setmatches,
+ {{group=1, pattern=2, id=3, priority=4}})
+ eq({}, funcs.getmatches())
+ expect_err('E28: No such highlight group name: 1', funcs.setmatches,
+ {{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}})
+ eq({}, funcs.getmatches())
+ end)
+end)
+
+describe('matchadd()', function()
+ it('correctly works when first two arguments and conceal are numbers at once',
+ function()
+ command('hi def link 1 PreProc')
+ eq(4, funcs.matchadd(1, 2, 3, 4, {conceal=5}))
+ eq({{
+ group='1',
+ pattern='2',
+ priority=3,
+ id=4,
+ conceal='5',
+ }}, funcs.getmatches())
+ end)
+end)
+
+describe('matchaddpos()', function()
+ it('errors out on invalid input', function()
+ command('hi clear PreProc')
+ eq('Vim(let):E5030: Empty list at position 0',
+ exc_exec('let val = matchaddpos("PreProc", [[]])'))
+ eq('Vim(let):E5030: Empty list at position 1',
+ exc_exec('let val = matchaddpos("PreProc", [1, v:_null_list])'))
+ eq('Vim(let):E5031: List or number required at position 1',
+ exc_exec('let val = matchaddpos("PreProc", [1, v:_null_dict])'))
+ end)
+ it('works with 0 lnum', function()
+ command('hi clear PreProc')
+ eq(4, funcs.matchaddpos('PreProc', {1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ funcs.matchdelete(4)
+ eq(4, funcs.matchaddpos('PreProc', {{0}, 1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ funcs.matchdelete(4)
+ eq(4, funcs.matchaddpos('PreProc', {0, 1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ end)
+ it('works with negative numbers', function()
+ command('hi clear PreProc')
+ eq(4, funcs.matchaddpos('PreProc', {-10, 1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ funcs.matchdelete(4)
+ eq(4, funcs.matchaddpos('PreProc', {{-10}, 1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ funcs.matchdelete(4)
+ eq(4, funcs.matchaddpos('PreProc', {{2, -1}, 1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ funcs.matchdelete(4)
+ eq(4, funcs.matchaddpos('PreProc', {{2, 0, -1}, 1}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ end)
+ it('works with zero length', function()
+ local screen = Screen.new(40, 5)
+ screen:attach()
+ funcs.setline(1, 'abcdef')
+ command('hi PreProc guifg=Red')
+ eq(4, funcs.matchaddpos('PreProc', {{1, 2, 0}}, 3, 4))
+ eq({{
+ group='PreProc',
+ pos1 = {1, 2, 0},
+ priority=3,
+ id=4,
+ }}, funcs.getmatches())
+ screen:expect([[
+ ^a{1:b}cdef |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]], {[1] = {foreground = Screen.colors.Red}, [2] = {bold = true, foreground = Screen.colors.Blue1}})
+ end)
+end)
diff --git a/test/functional/eval/minmax_functions_spec.lua b/test/functional/eval/minmax_functions_spec.lua
new file mode 100644
index 0000000000..c6eb754f91
--- /dev/null
+++ b/test/functional/eval/minmax_functions_spec.lua
@@ -0,0 +1,51 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local clear = helpers.clear
+local funcs = helpers.funcs
+local redir_exec = helpers.redir_exec
+
+before_each(clear)
+for _, func in ipairs({'min', 'max'}) do
+ describe(func .. '()', function()
+ it('gives a single error message when multiple values failed conversions',
+ function()
+ eq('\nE745: Using a List as a Number\n0',
+ redir_exec('echo ' .. func .. '([-5, [], [], [], 5])'))
+ eq('\nE745: Using a List as a Number\n0',
+ redir_exec('echo ' .. func .. '({1:-5, 2:[], 3:[], 4:[], 5:5})'))
+ for errmsg, errinput in pairs({
+ ['E745: Using a List as a Number'] = '[]',
+ ['E805: Using a Float as a Number'] = '0.0',
+ ['E703: Using a Funcref as a Number'] = 'function("tr")',
+ ['E728: Using a Dictionary as a Number'] = '{}',
+ }) do
+ eq('\n' .. errmsg .. '\n0',
+ redir_exec('echo ' .. func .. '([' .. errinput .. '])'))
+ eq('\n' .. errmsg .. '\n0',
+ redir_exec('echo ' .. func .. '({1:' .. errinput .. '})'))
+ end
+ end)
+ it('works with arrays/dictionaries with zero items', function()
+ eq(0, funcs[func]({}))
+ eq(0, eval(func .. '({})'))
+ end)
+ it('works with arrays/dictionaries with one item', function()
+ eq(5, funcs[func]({5}))
+ eq(5, funcs[func]({test=5}))
+ end)
+ it('works with NULL arrays/dictionaries', function()
+ eq(0, eval(func .. '(v:_null_list)'))
+ eq(0, eval(func .. '(v:_null_dict)'))
+ end)
+ it('errors out for invalid types', function()
+ for _, errinput in ipairs({'1', 'v:true', 'v:false', 'v:null',
+ 'function("tr")', '""'}) do
+ eq(('\nE712: Argument of %s() must be a List or Dictionary\n0'):format(
+ func),
+ redir_exec('echo ' .. func .. '(' .. errinput .. ')'))
+ end
+ end)
+ end)
+end
diff --git a/test/functional/eval/modeline_spec.lua b/test/functional/eval/modeline_spec.lua
new file mode 100644
index 0000000000..c5bb798f4a
--- /dev/null
+++ b/test/functional/eval/modeline_spec.lua
@@ -0,0 +1,19 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear, command, write_file = helpers.clear, helpers.command, helpers.write_file
+local eq, eval = helpers.eq, helpers.eval
+
+describe("modeline", function()
+ local tempfile = helpers.tmpname()
+ before_each(clear)
+
+ after_each(function()
+ os.remove(tempfile)
+ end)
+
+ it('does not crash with a large version number', function()
+ write_file(tempfile, 'vim100000000000000000000000')
+ command('e! ' .. tempfile)
+
+ eq(2, eval('1+1')) -- Still alive?
+ end)
+end)
diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua
index 5b87b05652..a8a413f68b 100644
--- a/test/functional/eval/msgpack_functions_spec.lua
+++ b/test/functional/eval/msgpack_functions_spec.lua
@@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local funcs = helpers.funcs
local eval, eq = helpers.eval, helpers.eq
-local execute = helpers.execute
+local command = helpers.command
local nvim = helpers.nvim
local exc_exec = helpers.exc_exec
@@ -16,18 +16,18 @@ describe('msgpack*() functions', function()
end)
end
- -- Regression test: msgpack_list_write was failing to write buffer with zero
+ -- Regression test: msgpack_list_write was failing to write buffer with zero
-- length.
obj_test('are able to dump and restore {"file": ""}', {{file=''}})
- -- Regression test: msgpack_list_write was failing to write buffer with NL at
+ -- Regression test: msgpack_list_write was failing to write buffer with NL at
-- the end.
obj_test('are able to dump and restore {0, "echo mpack"}', {{0, 'echo mpack'}})
obj_test('are able to dump and restore "Test\\n"', {'Test\n'})
- -- Regression test: msgpack_list_write was failing to write buffer with NL
+ -- Regression test: msgpack_list_write was failing to write buffer with NL
-- inside.
obj_test('are able to dump and restore "Test\\nTest 2"', {'Test\nTest 2'})
- -- Test that big objects (requirement: dump to something that is bigger then
- -- IOSIZE) are also fine. This particular object is obtained by concatenating
+ -- Test that big objects (requirement: dump to something that is bigger then
+ -- IOSIZE) are also fine. This particular object is obtained by concatenating
-- 5 identical shada files.
local big_obj = {
1, 1436711454, 78, {
@@ -331,13 +331,14 @@ describe('msgpack*() functions', function()
obj_test('are able to dump and restore floating-point value', {0.125})
it('can restore and dump UINT64_MAX', function()
- execute('let dumped = ["\\xCF" . repeat("\\xFF", 8)]')
- execute('let parsed = msgpackparse(dumped)')
- execute('let dumped2 = msgpackdump(parsed)')
+ command('let dumped = ["\\xCF" . repeat("\\xFF", 8)]')
+ command('let parsed = msgpackparse(dumped)')
+ command('let dumped2 = msgpackdump(parsed)')
eq(1, eval('type(parsed[0]) == type(0) ' ..
'|| parsed[0]._TYPE is v:msgpack_types.integer'))
if eval('type(parsed[0]) == type(0)') == 1 then
- eq(1, eval('0xFFFFFFFFFFFFFFFF == parsed[0]'))
+ command('call assert_equal(0xFFFFFFFFFFFFFFFF, parsed[0])')
+ eq({}, eval('v:errors'))
else
eq({_TYPE={}, _VAL={1, 3, 0x7FFFFFFF, 0x7FFFFFFF}}, eval('parsed[0]'))
end
@@ -345,13 +346,14 @@ describe('msgpack*() functions', function()
end)
it('can restore and dump INT64_MIN', function()
- execute('let dumped = ["\\xD3\\x80" . repeat("\\n", 7)]')
- execute('let parsed = msgpackparse(dumped)')
- execute('let dumped2 = msgpackdump(parsed)')
+ command('let dumped = ["\\xD3\\x80" . repeat("\\n", 7)]')
+ command('let parsed = msgpackparse(dumped)')
+ command('let dumped2 = msgpackdump(parsed)')
eq(1, eval('type(parsed[0]) == type(0) ' ..
'|| parsed[0]._TYPE is v:msgpack_types.integer'))
if eval('type(parsed[0]) == type(0)') == 1 then
- eq(1, eval('-0x8000000000000000 == parsed[0]'))
+ command('call assert_equal(-0x7fffffffffffffff - 1, parsed[0])')
+ eq({}, eval('v:errors'))
else
eq({_TYPE={}, _VAL={-1, 2, 0, 0}}, eval('parsed[0]'))
end
@@ -359,33 +361,33 @@ describe('msgpack*() functions', function()
end)
it('can restore and dump BIN string with zero byte', function()
- execute('let dumped = ["\\xC4\\x01\\n"]')
- execute('let parsed = msgpackparse(dumped)')
- execute('let dumped2 = msgpackdump(parsed)')
+ command('let dumped = ["\\xC4\\x01\\n"]')
+ command('let parsed = msgpackparse(dumped)')
+ command('let dumped2 = msgpackdump(parsed)')
eq({{_TYPE={}, _VAL={'\n'}}}, eval('parsed'))
eq(1, eval('parsed[0]._TYPE is v:msgpack_types.binary'))
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump STR string with zero byte', function()
- execute('let dumped = ["\\xA1\\n"]')
- execute('let parsed = msgpackparse(dumped)')
- execute('let dumped2 = msgpackdump(parsed)')
+ command('let dumped = ["\\xA1\\n"]')
+ command('let parsed = msgpackparse(dumped)')
+ command('let dumped2 = msgpackdump(parsed)')
eq({{_TYPE={}, _VAL={'\n'}}}, eval('parsed'))
eq(1, eval('parsed[0]._TYPE is v:msgpack_types.string'))
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump BIN string with NL', function()
- execute('let dumped = ["\\xC4\\x01", ""]')
- execute('let parsed = msgpackparse(dumped)')
- execute('let dumped2 = msgpackdump(parsed)')
+ command('let dumped = ["\\xC4\\x01", ""]')
+ command('let parsed = msgpackparse(dumped)')
+ command('let dumped2 = msgpackdump(parsed)')
eq({"\n"}, eval('parsed'))
eq(1, eval('dumped ==# dumped2'))
end)
it('dump and restore special mapping with floating-point value', function()
- execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
+ command('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
eq({0.125}, eval('msgpackparse(msgpackdump([todump]))'))
end)
end)
@@ -394,52 +396,53 @@ describe('msgpackparse() function', function()
before_each(clear)
it('restores nil as v:null', function()
- execute('let dumped = ["\\xC0"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\xC0"]')
+ command('let parsed = msgpackparse(dumped)')
eq('[v:null]', eval('string(parsed)'))
end)
it('restores boolean false as v:false', function()
- execute('let dumped = ["\\xC2"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\xC2"]')
+ command('let parsed = msgpackparse(dumped)')
eq({false}, eval('parsed'))
end)
it('restores boolean true as v:true', function()
- execute('let dumped = ["\\xC3"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\xC3"]')
+ command('let parsed = msgpackparse(dumped)')
eq({true}, eval('parsed'))
end)
it('restores FIXSTR as special dict', function()
- execute('let dumped = ["\\xa2ab"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\xa2ab"]')
+ command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={'ab'}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.string'))
end)
it('restores BIN 8 as string', function()
- execute('let dumped = ["\\xC4\\x02ab"]')
+ command('let dumped = ["\\xC4\\x02ab"]')
eq({'ab'}, eval('msgpackparse(dumped)'))
end)
it('restores FIXEXT1 as special dictionary', function()
- execute('let dumped = ["\\xD4\\x10", ""]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\xD4\\x10", ""]')
+ command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={0x10, {"", ""}}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.ext'))
end)
it('restores MAP with BIN key as special dictionary', function()
- execute('let dumped = ["\\x81\\xC4\\x01a\\xC4\\n"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\x81\\xC4\\x01a\\xC4\\n"]')
+ command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={{'a', ''}}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
it('restores MAP with duplicate STR keys as special dictionary', function()
- execute('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]')
+ -- FIXME Internal error bug
+ command('silent! let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={ {{_TYPE={}, _VAL={'a'}}, ''},
{{_TYPE={}, _VAL={'a'}}, ''}}} }, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
@@ -448,8 +451,8 @@ describe('msgpackparse() function', function()
end)
it('restores MAP with MAP key as special dictionary', function()
- execute('let dumped = ["\\x81\\x80\\xC4\\n"]')
- execute('let parsed = msgpackparse(dumped)')
+ command('let dumped = ["\\x81\\x80\\xC4\\n"]')
+ command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={{{}, ''}}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
@@ -460,7 +463,8 @@ describe('msgpackparse() function', function()
eval(cmd)
eval(cmd) -- do it again (try to force segfault)
local api_info = eval(cmd) -- do it again
- eq({'error_types', 'functions', 'types'}, api_info)
+ eq({'error_types', 'functions', 'types',
+ 'ui_events', 'ui_options', 'version'}, api_info)
end)
it('fails when called with no arguments', function()
@@ -493,6 +497,12 @@ describe('msgpackparse() function', function()
exc_exec('call msgpackparse(function("tr"))'))
end)
+ it('fails to parse a partial', function()
+ command('function T() dict\nendfunction')
+ eq('Vim(call):E686: Argument of msgpackparse() must be a List',
+ exc_exec('call msgpackparse(function("T", [1, 2], {}))'))
+ end)
+
it('fails to parse a float', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
exc_exec('call msgpackparse(0.0)'))
@@ -508,10 +518,10 @@ describe('msgpackdump() function', function()
end)
it('can dump generic mapping with generic mapping keys and values', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('call add(todump._VAL, [todumpv1, todumpv2])')
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('call add(todump._VAL, [todumpv1, todumpv2])')
eq({'\129\128\128'}, eval('msgpackdump([todump])'))
end)
@@ -524,124 +534,131 @@ describe('msgpackdump() function', function()
end)
it('can v:null', function()
- execute('let todump = v:null')
+ command('let todump = v:null')
end)
it('can dump special bool mapping (true)', function()
- execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
+ command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
eq({'\195'}, eval('msgpackdump([todump])'))
end)
it('can dump special bool mapping (false)', function()
- execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
+ command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
eq({'\194'}, eval('msgpackdump([todump])'))
end)
it('can dump special nil mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
+ command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
eq({'\192'}, eval('msgpackdump([todump])'))
end)
it('can dump special ext mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
eq({'\212\005', ''}, eval('msgpackdump([todump])'))
end)
it('can dump special array mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
eq({'\146\005\145\196\n'}, eval('msgpackdump([todump])'))
end)
it('can dump special UINT64_MAX mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.integer}')
- execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
+ command('let todump = {"_TYPE": v:msgpack_types.integer}')
+ command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
eq({'\207\255\255\255\255\255\255\255\255'}, eval('msgpackdump([todump])'))
end)
it('can dump special INT64_MIN mapping', function()
- execute('let todump = {"_TYPE": v:msgpack_types.integer}')
- execute('let todump._VAL = [-1, 2, 0, 0]')
+ command('let todump = {"_TYPE": v:msgpack_types.integer}')
+ command('let todump._VAL = [-1, 2, 0, 0]')
eq({'\211\128\n\n\n\n\n\n\n'}, eval('msgpackdump([todump])'))
end)
it('fails to dump a function reference', function()
- execute('let Todump = function("tr")')
- eq('Vim(call):E951: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference',
+ command('let Todump = function("tr")')
+ eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference',
+ exc_exec('call msgpackdump([Todump])'))
+ end)
+
+ it('fails to dump a partial', function()
+ command('function T() dict\nendfunction')
+ command('let Todump = function("T", [1, 2], {})')
+ eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference',
exc_exec('call msgpackdump([Todump])'))
end)
it('fails to dump a function reference in a list', function()
- execute('let todump = [function("tr")]')
- eq('Vim(call):E951: Error while dumping msgpackdump() argument, index 0, index 0: attempt to dump function reference',
+ command('let todump = [function("tr")]')
+ eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, index 0: attempt to dump function reference',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive list', function()
- execute('let todump = [[[]]]')
- execute('call add(todump[0][0], todump)')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in index 0, index 0, index 0',
+ command('let todump = [[[]]]')
+ command('call add(todump[0][0], todump)')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0, index 0, index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive dict', function()
- execute('let todump = {"d": {"d": {}}}')
- execute('call extend(todump.d.d, {"d": todump})')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in key \'d\', key \'d\', key \'d\'',
+ command('let todump = {"d": {"d": {}}}')
+ command('call extend(todump.d.d, {"d": todump})')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key \'d\', key \'d\', key \'d\'',
exc_exec('call msgpackdump([todump])'))
end)
it('can dump dict with two same dicts inside', function()
- execute('let inter = {}')
- execute('let todump = {"a": inter, "b": inter}')
+ command('let inter = {}')
+ command('let todump = {"a": inter, "b": inter}')
eq({"\130\161a\128\161b\128"}, eval('msgpackdump([todump])'))
end)
it('can dump list with two same lists inside', function()
- execute('let inter = []')
- execute('let todump = [inter, inter]')
+ command('let inter = []')
+ command('let todump = [inter, inter]')
eq({"\146\144\144"}, eval('msgpackdump([todump])'))
end)
it('fails to dump a recursive list in a special dict', function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
- execute('call add(todump._VAL, todump)')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in index 0',
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
+ command('call add(todump._VAL, todump)')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (key) map in a special dict', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('call add(todump._VAL, [todump, 0])')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in index 1',
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('call add(todump._VAL, [todump, 0])')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (val) map in a special dict', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
- execute('call add(todump._VAL, [0, todump])')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in key 0 at index 0 from special map',
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
+ command('call add(todump._VAL, [0, todump])')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key 0 at index 0 from special map',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (key) map in a special dict, _VAL reference', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
- execute('call add(todump._VAL[0][0], todump._VAL)')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in key [[[[...@0], []]]] at index 0 from special map, index 0',
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
+ command('call add(todump._VAL[0][0], todump._VAL)')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key [[[[...@0], []]]] at index 0 from special map, index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (val) map in a special dict, _VAL reference', function()
- execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
- execute('call add(todump._VAL[0][1], todump._VAL)')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in key [] at index 0 from special map, index 0',
+ command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
+ command('call add(todump._VAL[0][1], todump._VAL)')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key [] at index 0 from special map, index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (val) special list in a special dict',
function()
- execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
- execute('call add(todump._VAL, [0, todump._VAL])')
- eq('Vim(call):E952: Unable to dump msgpackdump() argument, index 0: container references itself in index 0, index 1',
+ command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
+ command('call add(todump._VAL, [0, todump._VAL])')
+ eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0, index 1',
exc_exec('call msgpackdump([todump])'))
end)
@@ -675,6 +692,12 @@ describe('msgpackdump() function', function()
exc_exec('call msgpackdump(function("tr"))'))
end)
+ it('fails to dump a partial', function()
+ command('function T() dict\nendfunction')
+ eq('Vim(call):E686: Argument of msgpackdump() must be a List',
+ exc_exec('call msgpackdump(function("T", [1, 2], {}))'))
+ end)
+
it('fails to dump a float', function()
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump(0.0)'))
diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua
new file mode 100644
index 0000000000..afe999e1fa
--- /dev/null
+++ b/test/functional/eval/null_spec.lua
@@ -0,0 +1,147 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local curbufmeths = helpers.curbufmeths
+local redir_exec = helpers.redir_exec
+local exc_exec = helpers.exc_exec
+local command = helpers.command
+local clear = helpers.clear
+local meths = helpers.meths
+local funcs = helpers.funcs
+local eq = helpers.eq
+
+describe('NULL', function()
+ before_each(function()
+ clear()
+ command('let L = v:_null_list')
+ command('let D = v:_null_dict')
+ command('let S = $XXX_NONEXISTENT_VAR_XXX')
+ end)
+ local tmpfname = 'Xtest-functional-viml-null'
+ after_each(function()
+ os.remove(tmpfname)
+ end)
+ local null_test = function(name, cmd, err)
+ it(name, function()
+ eq(err, exc_exec(cmd))
+ end)
+ end
+ local null_expr_test = function(name, expr, err, val, after)
+ it(name, function()
+ eq((err == 0) and ('') or ('\n' .. err),
+ redir_exec('let g:_var = ' .. expr))
+ if val == nil then
+ eq(0, funcs.exists('g:_var'))
+ else
+ eq(val, meths.get_var('_var'))
+ end
+ if after ~= nil then
+ after()
+ end
+ end)
+ end
+ describe('list', function()
+ -- Incorrect behaviour
+ -- FIXME Should error out with different message
+ null_test('makes :unlet act as if it is not a list', ':unlet L[0]',
+ 'Vim(unlet):E689: Can only index a List or Dictionary')
+
+ -- Subjectable behaviour
+
+ -- FIXME Should return 1
+ null_expr_test('is equal to empty list', 'L == []', 0, 0)
+ -- FIXME Should return 1
+ null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0)
+
+ -- Correct behaviour
+ null_expr_test('can be indexed with error message for empty list', 'L[0]',
+ 'E684: list index out of range: 0\nE15: Invalid expression: L[0]', nil)
+ null_expr_test('can be splice-indexed', 'L[:]', 0, {})
+ null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0)
+ null_test('is accepted by :for', 'for x in L|throw x|endfor', 0)
+ null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function()
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+ end)
+ null_expr_test('does not crash setline()', 'setline(1, L)', 0, 0, function()
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+ end)
+ null_expr_test('is identical to itself', 'L is L', 0, 1)
+ null_expr_test('can be sliced', 'L[:]', 0, {})
+ null_expr_test('can be copied', 'copy(L)', 0, {})
+ null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {})
+ null_expr_test('does not crash when indexed', 'L[1]',
+ 'E684: list index out of range: 1\nE15: Invalid expression: L[1]', nil)
+ null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0)
+ null_expr_test('does not crash col()', 'col(L)', 0, 0)
+ null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0)
+ null_expr_test('does not crash line()', 'line(L)', 0, 0)
+ null_expr_test('does not crash count()', 'count(L, 1)', 0, 0)
+ null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1)
+ null_expr_test('does not crash map()', 'map(L, "v:val")', 0, {})
+ null_expr_test('does not crash filter()', 'filter(L, "1")', 0, {})
+ null_expr_test('is empty', 'empty(L)', 0, 1)
+ null_expr_test('does not crash get()', 'get(L, 1, 10)', 0, 10)
+ null_expr_test('has zero length', 'len(L)', 0, 0)
+ null_expr_test('is accepted as an empty list by max()', 'max(L)', 0, 0)
+ null_expr_test('is accepted as an empty list by min()', 'min(L)', 0, 0)
+ null_expr_test('is stringified correctly', 'string(L)', 0, '[]')
+ null_expr_test('is JSON encoded correctly', 'json_encode(L)', 0, '[]')
+ null_test('does not crash lockvar', 'lockvar! L', 0)
+ null_expr_test('can be added to itself', '(L + L)', 0, {})
+ null_expr_test('can be added to itself', '(L + L) is L', 0, 1)
+ null_expr_test('can be added to non-empty list', '([1] + L)', 0, {1})
+ null_expr_test('can be added to non-empty list (reversed)', '(L + [1])', 0, {1})
+ null_expr_test('is equal to itself', 'L == L', 0, 1)
+ null_expr_test('is not not equal to itself', 'L != L', 0, 0)
+ null_expr_test('counts correctly', 'count([L], L)', 0, 1)
+ null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 1)
+ null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1)
+ null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items')
+ null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]',
+ 'Type number and <Enter> or click with mouse (empty cancels): ', {0, 0})
+ null_expr_test('is accepted as an empty list by writefile()',
+ ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname),
+ 0, {0, {}})
+ null_expr_test('makes add() error out', 'add(L, 0)',
+ 'E742: Cannot change value of add() argument', 1)
+ null_expr_test('makes insert() error out', 'insert(L, 1)',
+ 'E742: Cannot change value of insert() argument', 0)
+ null_expr_test('does not crash remove()', 'remove(L, 0)',
+ 'E742: Cannot change value of remove() argument', 0)
+ null_expr_test('makes reverse() error out', 'reverse(L)',
+ 'E742: Cannot change value of reverse() argument', 0)
+ null_expr_test('makes sort() error out', 'sort(L)',
+ 'E742: Cannot change value of sort() argument', 0)
+ null_expr_test('makes uniq() error out', 'uniq(L)',
+ 'E742: Cannot change value of uniq() argument', 0)
+ null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0)
+ null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1})
+ null_expr_test('makes join() return empty string', 'join(L, "")', 0, '')
+ null_expr_test('makes msgpackdump() return empty list', 'msgpackdump(L)', 0, {})
+ null_expr_test('does not crash system()', 'system("cat", L)', 0, '')
+ null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0)
+ null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {})
+ null_test('does not make Neovim crash when v:oldfiles gets assigned to that', ':let v:oldfiles = L|oldfiles', 0)
+ null_expr_test('does not make complete() crash or error out',
+ 'execute(":normal i\\<C-r>=complete(1, L)[-1]\\n")',
+ '', '\n', function()
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+ end)
+ null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, 0)
+ null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, 0)
+ null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, 0)
+ null_test('is accepted by :cexpr', 'cexpr L', 0)
+ null_test('is accepted by :lexpr', 'lexpr L', 0)
+ end)
+ describe('dict', function()
+ it('does not crash when indexing NULL dict', function()
+ eq('\nE716: Key not present in Dictionary: test\nE15: Invalid expression: v:_null_dict.test',
+ redir_exec('echo v:_null_dict.test'))
+ end)
+ null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0)
+ null_expr_test('makes extend do nothing', 'extend({1: 2}, D)', 0, {['1']=2})
+ null_expr_test('does not crash map()', 'map(D, "v:val")', 0, {})
+ null_expr_test('does not crash filter()', 'filter(D, "1")', 0, {})
+ null_expr_test('makes map() return v:_null_dict', 'map(D, "v:val") is# D', 0, 1)
+ null_expr_test('makes filter() return v:_null_dict', 'filter(D, "1") is# D', 0, 1)
+ end)
+end)
diff --git a/test/functional/eval/printf_spec.lua b/test/functional/eval/printf_spec.lua
index c84290ceef..27e24c4118 100644
--- a/test/functional/eval/printf_spec.lua
+++ b/test/functional/eval/printf_spec.lua
@@ -1,7 +1,10 @@
local helpers = require('test.functional.helpers')(after_each)
+
local clear = helpers.clear
local eq = helpers.eq
+local eval = helpers.eval
local funcs = helpers.funcs
+local meths = helpers.meths
local exc_exec = helpers.exc_exec
describe('printf()', function()
@@ -57,4 +60,33 @@ describe('printf()', function()
it('errors out when %b modifier is used for a float', function()
eq('Vim(call):E805: Using a Float as a Number', exc_exec('call printf("%b", 3.1415926535)'))
end)
+ it('works with %p correctly', function()
+ local null_ret = nil
+ local seen_rets = {}
+ -- Collect all args in an array to avoid possible allocation of the same
+ -- address after freeing unreferenced values.
+ meths.set_var('__args', {})
+ local function check_printf(expr, is_null)
+ eq(0, exc_exec('call add(__args, ' .. expr .. ')'))
+ eq(0, exc_exec('let __result = printf("%p", __args[-1])'))
+ local id_ret = eval('id(__args[-1])')
+ eq(id_ret, meths.get_var('__result'))
+ if is_null then
+ if null_ret then
+ eq(null_ret, id_ret)
+ else
+ null_ret = id_ret
+ end
+ else
+ eq(nil, seen_rets[id_ret])
+ seen_rets[id_ret] = expr
+ end
+ meths.del_var('__result')
+ end
+ check_printf('v:_null_list', true)
+ check_printf('v:_null_dict', true)
+ check_printf('[]')
+ check_printf('{}')
+ check_printf('function("tr", ["a"])')
+ end)
end)
diff --git a/test/functional/eval/reltime_spec.lua b/test/functional/eval/reltime_spec.lua
index 0b19d372ec..0181f09024 100644
--- a/test/functional/eval/reltime_spec.lua
+++ b/test/functional/eval/reltime_spec.lua
@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, ok = helpers.clear, helpers.eq, helpers.ok
-local neq, execute, funcs = helpers.neq, helpers.execute, helpers.funcs
+local neq, command, funcs = helpers.neq, helpers.command, helpers.funcs
local reltime, reltimestr, reltimefloat = funcs.reltime, funcs.reltimestr, funcs.reltimefloat
describe('reltimestr(), reltimefloat()', function()
@@ -8,7 +8,7 @@ describe('reltimestr(), reltimefloat()', function()
it('Checks', function()
local now = reltime()
- execute('sleep 10m')
+ command('sleep 10m')
local later = reltime()
local elapsed = reltime(now)
diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua
index 0fd55ce2f9..563e619b39 100644
--- a/test/functional/eval/server_spec.lua
+++ b/test/functional/eval/server_spec.lua
@@ -1,24 +1,41 @@
-
local helpers = require('test.functional.helpers')(after_each)
-local nvim, eq, neq, eval = helpers.nvim, helpers.eq, helpers.neq, helpers.eval
+local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval
+local command = helpers.command
local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths
-local os_name = helpers.os_name
-
-describe('serverstart(), serverstop()', function()
+local iswin = helpers.iswin
+local ok = helpers.ok
+local matches = helpers.matches
+local expect_err = helpers.expect_err
+
+local function clear_serverlist()
+ for _, server in pairs(funcs.serverlist()) do
+ funcs.serverstop(server)
+ end
+end
+
+describe('server', function()
before_each(clear)
- it('sets $NVIM_LISTEN_ADDRESS on first invocation', function()
+ it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function()
-- Unset $NVIM_LISTEN_ADDRESS
- nvim('command', 'let $NVIM_LISTEN_ADDRESS = ""')
+ command('let $NVIM_LISTEN_ADDRESS = ""')
local s = eval('serverstart()')
assert(s ~= nil and s:len() > 0, "serverstart() returned empty")
eq(s, eval('$NVIM_LISTEN_ADDRESS'))
- nvim('command', "call serverstop('"..s.."')")
+ eq(1, eval("serverstop('"..s.."')"))
eq('', eval('$NVIM_LISTEN_ADDRESS'))
end)
- it('sets v:servername _only_ on nvim startup unless all servers are stopped',
+ it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function()
+ clear({env={NVIM_LISTEN_ADDRESS='.'}})
+ eq('.', eval('$NVIM_LISTEN_ADDRESS'))
+ local servers = funcs.serverlist()
+ eq(1, #servers)
+ ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\…
+ end)
+
+ it('sets v:servername at startup or if all servers were stopped',
function()
local initial_server = meths.get_vvar('servername')
assert(initial_server ~= nil and initial_server:len() > 0,
@@ -31,39 +48,75 @@ describe('serverstart(), serverstop()', function()
neq(initial_server, s)
-- serverstop() does _not_ modify v:servername...
- funcs.serverstop(s)
+ eq(1, funcs.serverstop(s))
eq(initial_server, meths.get_vvar('servername'))
-- ...unless we stop _all_ servers.
- funcs.serverstop(funcs.serverlist()[1])
+ eq(1, funcs.serverstop(funcs.serverlist()[1]))
eq('', meths.get_vvar('servername'))
-- v:servername will take the next available server.
- local servername = (os_name() == 'windows'
- and [[\\.\pipe\Xtest-functional-server-server-pipe]]
- or 'Xtest-functional-server-server-socket')
+ local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]]
+ or 'Xtest-functional-server-socket')
funcs.serverstart(servername)
eq(servername, meths.get_vvar('servername'))
end)
- it('serverstop() ignores invalid input', function()
- nvim('command', "call serverstop('')")
- nvim('command', "call serverstop('bogus-socket-name')")
+ it('serverstop() returns false for invalid input', function()
+ eq(0, eval("serverstop('')"))
+ eq(0, eval("serverstop('bogus-socket-name')"))
end)
-end)
+ it('parses endpoints correctly', function()
+ clear_serverlist()
+ eq({}, funcs.serverlist())
-describe('serverlist()', function()
- before_each(clear)
+ local s = funcs.serverstart('127.0.0.1:0') -- assign random port
+ if #s > 0 then
+ assert(string.match(s, '127.0.0.1:%d+'))
+ eq(s, funcs.serverlist()[1])
+ clear_serverlist()
+ end
+
+ s = funcs.serverstart('127.0.0.1:') -- assign random port
+ if #s > 0 then
+ assert(string.match(s, '127.0.0.1:%d+'))
+ eq(s, funcs.serverlist()[1])
+ clear_serverlist()
+ end
+
+ local expected = {}
+ local v4 = '127.0.0.1:12345'
+ local status, _ = pcall(funcs.serverstart, v4)
+ if status then
+ table.insert(expected, v4)
+ pcall(funcs.serverstart, v4) -- exists already; ignore
+ end
- it('returns the list of servers', function()
+ local v6 = '::1:12345'
+ status, _ = pcall(funcs.serverstart, v6)
+ if status then
+ table.insert(expected, v6)
+ pcall(funcs.serverstart, v6) -- exists already; ignore
+ end
+ eq(expected, funcs.serverlist())
+ clear_serverlist()
+
+ expect_err('Failed to start server: invalid argument',
+ funcs.serverstart, '127.0.0.1:65536') -- invalid port
+ eq({}, funcs.serverlist())
+ end)
+
+ it('serverlist() returns the list of servers', function()
-- There should already be at least one server.
local n = eval('len(serverlist())')
- -- Add a few
- local servs = {'should-not-exist', 'another-one-that-shouldnt'}
+ -- Add some servers.
+ local servs = (iswin()
+ and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] }
+ or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] })
for _, s in ipairs(servs) do
- eq(s, eval('serverstart("'..s..'")'))
+ eq(s, eval("serverstart('"..s.."')"))
end
local new_servs = eval('serverlist()')
@@ -73,10 +126,31 @@ describe('serverlist()', function()
-- The new servers should be at the end of the list.
for i = 1, #servs do
eq(servs[i], new_servs[i + n])
- nvim('command', 'call serverstop("'..servs[i]..'")')
+ eq(1, eval("serverstop('"..servs[i].."')"))
end
- -- After calling serverstop() on the new servers, they should no longer be
- -- in the list.
+ -- After serverstop() the servers should NOT be in the list.
eq(n, eval('len(serverlist())'))
end)
end)
+
+describe('startup --listen', function()
+ it('validates', function()
+ clear()
+
+ local cmd = { unpack(helpers.nvim_argv) }
+ table.insert(cmd, '--listen')
+ matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd))
+
+ cmd = { unpack(helpers.nvim_argv) }
+ table.insert(cmd, '--listen2')
+ matches('nvim.*: Garbage after option argument: "%-%-listen2"', funcs.system(cmd))
+ end)
+
+ it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function()
+ local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]]
+ or 'Xtest-listen-pipe')
+ clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' },
+ args={ '--listen', addr } })
+ eq(addr, meths.get_vvar('servername'))
+ end)
+end)
diff --git a/test/functional/eval/setpos_spec.lua b/test/functional/eval/setpos_spec.lua
new file mode 100644
index 0000000000..935f387bcc
--- /dev/null
+++ b/test/functional/eval/setpos_spec.lua
@@ -0,0 +1,64 @@
+local helpers = require('test.functional.helpers')(after_each)
+local setpos = helpers.funcs.setpos
+local getpos = helpers.funcs.getpos
+local insert = helpers.insert
+local clear = helpers.clear
+local command = helpers.command
+local eval = helpers.eval
+local eq = helpers.eq
+local exc_exec = helpers.exc_exec
+
+
+describe('setpos() function', function()
+ before_each(function()
+ clear()
+ insert([[
+ First line of text
+ Second line of text
+ Third line of text]])
+ command('new')
+ insert([[
+ Line of text 1
+ Line of text 2
+ Line of text 3]])
+ end)
+ it('can set the current cursor position', function()
+ setpos(".", {0, 2, 1, 0})
+ eq(getpos("."), {0, 2, 1, 0})
+ setpos(".", {2, 1, 1, 0})
+ eq(getpos("."), {0, 1, 1, 0})
+ local ret = exc_exec('call setpos(".", [1, 1, 1, 0])')
+ eq(0, ret)
+ end)
+ it('can set lowercase marks in the current buffer', function()
+ setpos("'d", {0, 2, 1, 0})
+ eq(getpos("'d"), {0, 2, 1, 0})
+ command('undo')
+ command('call setpos("\'d", [2, 3, 1, 0])')
+ eq(getpos("'d"), {0, 3, 1, 0})
+ end)
+ it('can set lowercase marks in other buffers', function()
+ local retval = setpos("'d", {1, 2, 1, 0})
+ eq(0, retval)
+ setpos("'d", {1, 2, 1, 0})
+ eq(getpos("'d"), {0, 0, 0, 0})
+ command('wincmd w')
+ eq(eval('bufnr("%")'), 1)
+ eq(getpos("'d"), {0, 2, 1, 0})
+ end)
+ it("fails when setting a mark in a buffer that doesn't exist", function()
+ local retval = setpos("'d", {3, 2, 1, 0})
+ eq(-1, retval)
+ eq(getpos("'d"), {0, 0, 0, 0})
+ retval = setpos("'D", {3, 2, 1, 0})
+ eq(-1, retval)
+ eq(getpos("'D"), {0, 0, 0, 0})
+ end)
+ it('can set uppercase marks', function()
+ setpos("'D", {2, 2, 3, 0})
+ eq(getpos("'D"), {2, 2, 3, 0})
+ -- Can set a mark in another buffer
+ setpos("'D", {1, 2, 2, 0})
+ eq(getpos("'D"), {1, 2, 2, 0})
+ end)
+end)
diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua
new file mode 100644
index 0000000000..82557575ce
--- /dev/null
+++ b/test/functional/eval/sort_spec.lua
@@ -0,0 +1,56 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local NIL = helpers.NIL
+local eval = helpers.eval
+local clear = helpers.clear
+local meths = helpers.meths
+local funcs = helpers.funcs
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local redir_exec = helpers.redir_exec
+
+before_each(clear)
+
+describe('sort()', function()
+ it('errors out when sorting special values', function()
+ eq('Vim(call):E907: Using a special value as a Float',
+ exc_exec('call sort([v:true, v:false], "f")'))
+ end)
+
+ it('sorts “wrong” values between -0.0001 and 0.0001, preserving order',
+ function()
+ meths.set_var('list', {true, false, NIL, {}, {a=42}, 'check',
+ 0.0001, -0.0001})
+ command('call insert(g:list, function("tr"))')
+ local error_lines = funcs.split(
+ funcs.execute('silent! call sort(g:list, "f")'), '\n')
+ local errors = {}
+ for _, err in ipairs(error_lines) do
+ errors[err] = true
+ end
+ eq({
+ ['E891: Using a Funcref as a Float']=true,
+ ['E892: Using a String as a Float']=true,
+ ['E893: Using a List as a Float']=true,
+ ['E894: Using a Dictionary as a Float']=true,
+ ['E907: Using a special value as a Float']=true,
+ }, errors)
+ eq('[-1.0e-4, function(\'tr\'), v:true, v:false, v:null, [], {\'a\': 42}, \'check\', 1.0e-4]',
+ eval('string(g:list)'))
+ end)
+
+ it('can yield E702 and stop sorting after that', function()
+ command([[
+ function Cmp(a, b)
+ if type(a:a) == type([]) || type(a:b) == type([])
+ return []
+ endif
+ return (a:a > a:b) - (a:a < a:b)
+ endfunction
+ ]])
+ eq('\nE745: Using a List as a Number\nE702: Sort compare function failed',
+ redir_exec('let sl = sort([1, 0, [], 3, 2], "Cmp")'))
+ eq({1, 0, {}, 3, 2}, meths.get_var('sl'))
+ end)
+end)
diff --git a/test/functional/eval/special_vars_spec.lua b/test/functional/eval/special_vars_spec.lua
index 4c5d63ce23..97a12d490d 100644
--- a/test/functional/eval/special_vars_spec.lua
+++ b/test/functional/eval/special_vars_spec.lua
@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local exc_exec = helpers.exc_exec
-local execute = helpers.execute
+local command = helpers.command
local funcs = helpers.funcs
local clear = helpers.clear
local eval = helpers.eval
@@ -12,7 +12,7 @@ describe('Special values', function()
before_each(clear)
it('do not cause error when freed', function()
- execute([[
+ command([[
function Test()
try
return v:true
@@ -109,7 +109,7 @@ describe('Special values', function()
it('does not work with +=/-=/.=', function()
meths.set_var('true', true)
meths.set_var('false', false)
- execute('let null = v:null')
+ command('let null = v:null')
eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let true += 1'))
eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let false += 1'))
@@ -168,4 +168,23 @@ describe('Special values', function()
'Expected True but got v:null',
}, meths.get_vvar('errors'))
end)
+
+ describe('compat', function()
+ it('v:count is distinct from count', function()
+ command('let count = []') -- v:count is readonly
+ eq(1, eval('count is# g:["count"]'))
+ end)
+ it('v:errmsg is distinct from errmsg', function()
+ command('let errmsg = 1')
+ eq(1, eval('errmsg is# g:["errmsg"]'))
+ end)
+ it('v:shell_error is distinct from shell_error', function()
+ command('let shell_error = []') -- v:shell_error is readonly
+ eq(1, eval('shell_error is# g:["shell_error"]'))
+ end)
+ it('v:this_session is distinct from this_session', function()
+ command('let this_session = []')
+ eq(1, eval('this_session is# g:["this_session"]'))
+ end)
+ end)
end)
diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua
index 9e2dc4e111..adc1af9b8e 100644
--- a/test/functional/eval/string_spec.lua
+++ b/test/functional/eval/string_spec.lua
@@ -7,8 +7,9 @@ local eval = helpers.eval
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local funcs = helpers.funcs
-local write_file = helpers.write_file
local NIL = helpers.NIL
+local source = helpers.source
+local dedent = helpers.dedent
describe('string() function', function()
before_each(clear)
@@ -103,26 +104,19 @@ describe('string() function', function()
end)
describe('used to represent funcrefs', function()
- local fname = 'Xtest-functional-eval-string_spec-fref-script.vim'
-
before_each(function()
- write_file(fname, [[
+ source([[
function Test1()
endfunction
- function s:Test2()
+ function s:Test2() dict
endfunction
- function g:Test3()
+ function g:Test3() dict
endfunction
let g:Test2_f = function('s:Test2')
]])
- command('source ' .. fname)
- end)
-
- after_each(function()
- os.remove(fname)
end)
it('dumps references to built-in functions', function()
@@ -137,6 +131,85 @@ describe('string() function', function()
it('dumps references to script functions', function()
eq('function(\'<SNR>1_Test2\')', eval('string(Test2_f)'))
end)
+
+ it('dumps partials with self referencing a partial', function()
+ source([[
+ function TestDict() dict
+ endfunction
+ let d = {}
+ let TestDictRef = function('TestDict', d)
+ let d.tdr = TestDictRef
+ ]])
+ eq("\nE724: unable to correctly dump variable with self-referencing container\nfunction('TestDict', {'tdr': function('TestDict', {E724@1})})",
+ redir_exec('echo string(d.tdr)'))
+ end)
+
+ it('dumps automatically created partials', function()
+ eq('function(\'<SNR>1_Test2\', {\'f\': function(\'<SNR>1_Test2\')})',
+ eval('string({"f": Test2_f}.f)'))
+ eq('function(\'<SNR>1_Test2\', [1], {\'f\': function(\'<SNR>1_Test2\', [1])})',
+ eval('string({"f": function(Test2_f, [1])}.f)'))
+ end)
+
+ it('dumps manually created partials', function()
+ eq('function(\'Test3\', [1, 2], {})',
+ eval('string(function("Test3", [1, 2], {}))'))
+ eq('function(\'Test3\', {})',
+ eval('string(function("Test3", {}))'))
+ eq('function(\'Test3\', [1, 2])',
+ eval('string(function("Test3", [1, 2]))'))
+ end)
+
+ it('does not crash or halt when dumping partials with reference cycles in self',
+ function()
+ meths.set_var('d', {v=true})
+ eq(dedent([[
+
+ E724: unable to correctly dump variable with self-referencing container
+ {'p': function('<SNR>1_Test2', {E724@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]),
+ redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))'))
+ end)
+
+ it('does not show errors when dumping partials referencing the same dictionary',
+ function()
+ command('let d = {}')
+ -- Regression for “eval/typval_encode: Dump empty dictionary before
+ -- checking for refcycle”, results in error.
+ eq('[function(\'tr\', {}), function(\'tr\', {})]', eval('string([function("tr", d), function("tr", d)])'))
+ -- Regression for “eval: Work with reference cycles in partials (self)
+ -- properly”, results in crash.
+ eval('extend(d, {"a": 1})')
+ eq('[function(\'tr\', {\'a\': 1}), function(\'tr\', {\'a\': 1})]', eval('string([function("tr", d), function("tr", d)])'))
+ end)
+
+ it('does not crash or halt when dumping partials with reference cycles in arguments',
+ function()
+ meths.set_var('l', {})
+ eval('add(l, l)')
+ -- Regression: the below line used to crash (add returns original list and
+ -- there was error in dumping partials). Tested explicitly in
+ -- test/unit/api/private_helpers_spec.lua.
+ eval('add(l, function("Test1", l))')
+ eq(dedent([=[
+
+ E724: unable to correctly dump variable with self-referencing container
+ function('Test1', [[{E724@2}, function('Test1', [{E724@2}])], function('Test1', [[{E724@4}, function('Test1', [{E724@4}])]])])]=]),
+ redir_exec('echo string(function("Test1", l))'))
+ end)
+
+ it('does not crash or halt when dumping partials with reference cycles in self and arguments',
+ function()
+ meths.set_var('d', {v=true})
+ meths.set_var('l', {})
+ eval('add(l, l)')
+ eval('add(l, function("Test1", l))')
+ eval('add(l, function("Test1", d))')
+ eq(dedent([=[
+
+ E724: unable to correctly dump variable with self-referencing container
+ {'p': function('<SNR>1_Test2', [[{E724@3}, function('Test1', [{E724@3}]), function('Test1', {E724@0})], function('Test1', [[{E724@5}, function('Test1', [{E724@5}]), function('Test1', {E724@0})]]), function('Test1', {E724@0})], {E724@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]=]),
+ redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))'))
+ end)
end)
describe('used to represent lists', function()
@@ -174,6 +247,13 @@ describe('string() function', function()
eq('{}', eval('string({})'))
end)
+ it('dumps list with two same empty dictionaries, also in partials', function()
+ command('let d = {}')
+ eq('[{}, {}]', eval('string([d, d])'))
+ eq('[function(\'tr\', {}), {}]', eval('string([function("tr", d), d])'))
+ eq('[{}, function(\'tr\', {})]', eval('string([d, function("tr", d)])'))
+ end)
+
it('dumps non-empty dictionary', function()
eq('{\'t\'\'est\': 1}', funcs.string({['t\'est']=1}))
end)
diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua
new file mode 100644
index 0000000000..5cbf34365b
--- /dev/null
+++ b/test/functional/eval/system_spec.lua
@@ -0,0 +1,574 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local nvim_dir = helpers.nvim_dir
+local eq, call, clear, eval, feed_command, feed, nvim =
+ helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.feed_command,
+ helpers.feed, helpers.nvim
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local iswin = helpers.iswin
+
+local Screen = require('test.functional.ui.screen')
+
+local function create_file_with_nuls(name)
+ return function()
+ feed('ipart1<C-V>000part2<C-V>000part3<ESC>:w '..name..'<CR>')
+ eval('1') -- wait for the file to be created
+ end
+end
+
+local function delete_file(name)
+ return function()
+ eval("delete('"..name.."')")
+ end
+end
+
+-- Some tests require the xclip program and a x server.
+local xclip = nil
+do
+ if os.getenv('DISPLAY') then
+ xclip = (os.execute('command -v xclip > /dev/null 2>&1') == 0)
+ end
+end
+
+describe('system()', function()
+ before_each(clear)
+
+ describe('command passed as a List', function()
+ local function printargs_path()
+ return nvim_dir..'/printargs-test' .. (iswin() and '.exe' or '')
+ end
+
+ it('sets v:shell_error if cmd[0] is not executable', function()
+ call('system', { 'this-should-not-exist' })
+ eq(-1, eval('v:shell_error'))
+ end)
+
+ it('parameter validation does NOT modify v:shell_error', function()
+ -- 1. Call system() with invalid parameters.
+ -- 2. Assert that v:shell_error was NOT set.
+ feed_command('call system({})')
+ eq('E475: Invalid argument: expected String or List', eval('v:errmsg'))
+ eq(0, eval('v:shell_error'))
+ feed_command('call system([])')
+ eq('E474: Invalid argument', eval('v:errmsg'))
+ eq(0, eval('v:shell_error'))
+
+ -- Provoke a non-zero v:shell_error.
+ call('system', { 'this-should-not-exist' })
+ local old_val = eval('v:shell_error')
+ eq(-1, old_val)
+
+ -- 1. Call system() with invalid parameters.
+ -- 2. Assert that v:shell_error was NOT modified.
+ feed_command('call system({})')
+ eq(old_val, eval('v:shell_error'))
+ feed_command('call system([])')
+ eq(old_val, eval('v:shell_error'))
+ end)
+
+ it('quotes arguments correctly #5280', function()
+ local out = call('system',
+ { printargs_path(), [[1]], [[2 "3]], [[4 ' 5]], [[6 ' 7']] })
+
+ eq(0, eval('v:shell_error'))
+ eq([[arg1=1;arg2=2 "3;arg3=4 ' 5;arg4=6 ' 7';]], out)
+
+ out = call('system', { printargs_path(), [['1]], [[2 "3]] })
+ eq(0, eval('v:shell_error'))
+ eq([[arg1='1;arg2=2 "3;]], out)
+
+ out = call('system', { printargs_path(), "A\nB" })
+ eq(0, eval('v:shell_error'))
+ eq("arg1=A\nB;", out)
+ end)
+
+ it('calls executable in $PATH', function()
+ if 0 == eval("executable('python')") then pending("missing `python`") end
+ eq("foo\n", eval([[system(['python', '-c', 'print("foo")'])]]))
+ eq(0, eval('v:shell_error'))
+ end)
+
+ it('does NOT run in shell', function()
+ if iswin() then
+ eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'echo', '%PATH%'])"))
+ else
+ eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])"))
+ end
+ end)
+ end)
+
+ it('sets v:shell_error', function()
+ if iswin() then
+ eval([[system("cmd.exe /c exit")]])
+ eq(0, eval('v:shell_error'))
+ eval([[system("cmd.exe /c exit 1")]])
+ eq(1, eval('v:shell_error'))
+ eval([[system("cmd.exe /c exit 5")]])
+ eq(5, eval('v:shell_error'))
+ eval([[system('this-should-not-exist')]])
+ eq(1, eval('v:shell_error'))
+ else
+ eval([[system("sh -c 'exit'")]])
+ eq(0, eval('v:shell_error'))
+ eval([[system("sh -c 'exit 1'")]])
+ eq(1, eval('v:shell_error'))
+ eval([[system("sh -c 'exit 5'")]])
+ eq(5, eval('v:shell_error'))
+ eval([[system('this-should-not-exist')]])
+ eq(127, eval('v:shell_error'))
+ end
+ end)
+
+ describe('executes shell function', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new()
+ screen:attach()
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ if iswin() then
+ local function test_more()
+ eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]]))
+ end
+ local function test_shell_unquoting()
+ eval([[system('"ping" "-n" "1" "127.0.0.1"')]])
+ eq(0, eval('v:shell_error'))
+ eq('"a b"\n', eval([[system('cmd /s/c "cmd /s/c "cmd /s/c "echo "a b""""')]]))
+ eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command echo ''\^"a b\^"''')]]))
+ end
+
+ it('with shell=cmd.exe', function()
+ command('set shell=cmd.exe')
+ eq('""\n', eval([[system('echo ""')]]))
+ eq('"a b"\n', eval([[system('echo "a b"')]]))
+ eq('a \nb\n', eval([[system('echo a & echo b')]]))
+ eq('a \n', eval([[system('echo a 2>&1')]]))
+ test_more()
+ eval([[system('cd "C:\Program Files"')]])
+ eq(0, eval('v:shell_error'))
+ test_shell_unquoting()
+ end)
+
+ it('with shell=cmd', function()
+ command('set shell=cmd')
+ eq('"a b"\n', eval([[system('echo "a b"')]]))
+ test_more()
+ test_shell_unquoting()
+ end)
+
+ it('with shell=$COMSPEC', function()
+ local comspecshell = eval("fnamemodify($COMSPEC, ':t')")
+ if comspecshell == 'cmd.exe' then
+ command('set shell=$COMSPEC')
+ eq('"a b"\n', eval([[system('echo "a b"')]]))
+ test_more()
+ test_shell_unquoting()
+ else
+ pending('$COMSPEC is not cmd.exe: ' .. comspecshell)
+ end
+ end)
+
+ it('works with powershell', function()
+ helpers.set_shell_powershell()
+ eq('a\nb\n', eval([[system('echo a b')]]))
+ eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]]))
+ eq('a b\n', eval([[system('echo "a b"')]]))
+ end)
+ end
+
+ it('`echo` and waits for its return', function()
+ feed(':call system("echo")<cr>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ :call system("echo") |
+ ]])
+ end)
+
+ it('prints verbose information', function()
+ screen:try_resize(72, 14)
+ feed(':4verbose echo system("echo hi")<cr>')
+ if iswin() then
+ screen:expect{any=[[Executing command: "'cmd.exe' '/s' '/c' '"echo hi"'"]]}
+ else
+ screen:expect{any=[[Executing command: "'/[^']*sh' '%-c' 'echo hi'"]]}
+ end
+ feed('<cr>')
+ end)
+
+ it('self and total time recorded separately', function()
+ local tempfile = helpers.tmpname()
+
+ feed(':function! AlmostNoSelfTime()<cr>')
+ feed('echo system("echo hi")<cr>')
+ feed('endfunction<cr>')
+
+ feed(':profile start ' .. tempfile .. '<cr>')
+ feed(':profile func AlmostNoSelfTime<cr>')
+ feed(':call AlmostNoSelfTime()<cr>')
+ feed(':profile dump<cr>')
+
+ feed(':edit ' .. tempfile .. '<cr>')
+
+ local command_total_time = tonumber(helpers.funcs.split(helpers.funcs.getline(7))[2])
+ local command_self_time = tonumber(helpers.funcs.split(helpers.funcs.getline(7))[3])
+
+ helpers.neq(nil, command_total_time)
+ helpers.neq(nil, command_self_time)
+ end)
+
+ it('`yes` interrupted with CTRL-C', function()
+ feed(':call system("' .. (iswin()
+ and 'for /L %I in (1,0,2) do @echo y'
+ or 'yes') .. '")<cr>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+]] .. (iswin()
+ and [[
+ :call system("for /L %I in (1,0,2) do @echo y") |]]
+ or [[
+ :call system("yes") |]]))
+ feed('<c-c>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ Type :qa! and press <E...all changes and exit Nvim |
+ ]])
+ end)
+ end)
+
+ describe('passing no input', function()
+ it('returns the program output', function()
+ if iswin() then
+ eq("echoed\n", eval('system("echo echoed")'))
+ else
+ eq("echoed", eval('system("echo -n echoed")'))
+ end
+ end)
+ it('to backgrounded command does not crash', function()
+ -- This is indeterminate, just exercise the codepath. May get E5677.
+ feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "echo -n echoed &")')
+ local v_errnum = string.match(eval("v:errmsg"), "^E%d*:")
+ if v_errnum then
+ eq("E5677:", v_errnum)
+ end
+ eq(2, eval("1+1")) -- Still alive?
+ end)
+ end)
+
+ describe('passing input', function()
+ it('returns the program output', function()
+ eq("input", eval('system("cat -", "input")'))
+ end)
+ it('to backgrounded command does not crash', function()
+ -- This is indeterminate, just exercise the codepath. May get E5677.
+ feed_command('call system(has("win32") ? "start /b /wait more" : "cat - &", "input")')
+ local v_errnum = string.match(eval("v:errmsg"), "^E%d*:")
+ if v_errnum then
+ eq("E5677:", v_errnum)
+ end
+ eq(2, eval("1+1")) -- Still alive?
+ end)
+ it('works with an empty string', function()
+ eq("test\n", eval('system("echo test", "")'))
+ eq(2, eval("1+1")) -- Still alive?
+ end)
+ end)
+
+ describe('passing a lot of input', function()
+ it('returns the program output', function()
+ local input = {}
+ -- write more than 1mb of data, which should be enough to overcome
+ -- the os buffer limit and force multiple event loop iterations to write
+ -- everything
+ for _ = 1, 0xffff do
+ input[#input + 1] = '01234567890ABCDEFabcdef'
+ end
+ input = table.concat(input, '\n')
+ nvim('set_var', 'input', input)
+ eq(input, eval('system("cat -", g:input)'))
+ end)
+ end)
+
+ describe('Number input', function()
+ it('is treated as a buffer id', function()
+ command("put ='text in buffer 1'")
+ eq('\ntext in buffer 1\n', eval('system("cat", 1)'))
+ eq('Vim(echo):E86: Buffer 42 does not exist',
+ exc_exec('echo system("cat", 42)'))
+ end)
+ end)
+
+ describe('with output containing NULs', function()
+ local fname = 'Xtest'
+
+ before_each(create_file_with_nuls(fname))
+ after_each(delete_file(fname))
+
+ it('replaces NULs by SOH characters', function()
+ eq('part1\001part2\001part3\n', eval([[system('"cat" "]]..fname..[["')]]))
+ end)
+ end)
+
+ describe('input passed as List', function()
+ it('joins List items with linefeed characters', function()
+ eq('line1\nline2\nline3',
+ eval("system('cat -', ['line1', 'line2', 'line3'])"))
+ end)
+
+ -- Notice that NULs are converted to SOH when the data is read back. This
+ -- is inconsistent and is a good reason for the existence of the
+ -- `systemlist()` function, where input and output map to the same
+ -- characters(see the following tests with `systemlist()` below)
+ describe('with linefeed characters inside List items', function()
+ it('converts linefeed characters to NULs', function()
+ eq('l1\001p2\nline2\001a\001b\nl3',
+ eval([[system('cat -', ["l1\np2", "line2\na\nb", 'l3'])]]))
+ end)
+ end)
+
+ describe('with leading/trailing whitespace characters on items', function()
+ it('preserves whitespace, replacing linefeeds by NULs', function()
+ eq('line \nline2\001\n\001line3',
+ eval([[system('cat -', ['line ', "line2\n", "\nline3"])]]))
+ end)
+ end)
+ end)
+
+ describe("with a program that doesn't close stdout", function()
+ if not xclip then
+ pending('missing `xclip`', function() end)
+ else
+ it('will exit properly after passing input', function()
+ eq('', eval([[system('xclip -i -loops 1 -selection clipboard', 'clip-data')]]))
+ eq('clip-data', eval([[system('xclip -o -selection clipboard')]]))
+ end)
+ end
+ end)
+end)
+
+describe('systemlist()', function()
+ -- Similar to `system()`, but returns List instead of String.
+ before_each(clear)
+
+ it('sets v:shell_error', function()
+ if iswin() then
+ eval([[systemlist("cmd.exe /c exit")]])
+ eq(0, eval('v:shell_error'))
+ eval([[systemlist("cmd.exe /c exit 1")]])
+ eq(1, eval('v:shell_error'))
+ eval([[systemlist("cmd.exe /c exit 5")]])
+ eq(5, eval('v:shell_error'))
+ eval([[systemlist('this-should-not-exist')]])
+ eq(1, eval('v:shell_error'))
+ else
+ eval([[systemlist("sh -c 'exit'")]])
+ eq(0, eval('v:shell_error'))
+ eval([[systemlist("sh -c 'exit 1'")]])
+ eq(1, eval('v:shell_error'))
+ eval([[systemlist("sh -c 'exit 5'")]])
+ eq(5, eval('v:shell_error'))
+ eval([[systemlist('this-should-not-exist')]])
+ eq(127, eval('v:shell_error'))
+ end
+ end)
+
+ describe('executes shell function', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new()
+ screen:attach()
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ it('`echo` and waits for its return', function()
+ feed(':call systemlist("echo")<cr>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ :call systemlist("echo") |
+ ]])
+ end)
+
+ it('`yes` interrupted with CTRL-C', function()
+ feed(':call systemlist("yes | xargs")<cr>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ :call systemlist("yes | xargs") |
+ ]])
+ feed('<c-c>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ Type :qa! and press <E...all changes and exit Nvim |
+ ]])
+ end)
+ end)
+
+ describe('passing string with linefeed characters as input', function()
+ it('splits the output on linefeed characters', function()
+ eq({'abc', 'def', 'ghi'}, eval([[systemlist("cat -", "abc\ndef\nghi")]]))
+ end)
+ end)
+
+ describe('passing a lot of input', function()
+ it('returns the program output', function()
+ local input = {}
+ for _ = 1, 0xffff do
+ input[#input + 1] = '01234567890ABCDEFabcdef'
+ end
+ nvim('set_var', 'input', input)
+ eq(input, eval('systemlist("cat -", g:input)'))
+ end)
+ end)
+
+ describe('with output containing NULs', function()
+ local fname = 'Xtest'
+
+ before_each(function()
+ command('set ff=unix')
+ create_file_with_nuls(fname)()
+ end)
+ after_each(delete_file(fname))
+
+ it('replaces NULs by newline characters', function()
+ eq({'part1\npart2\npart3'}, eval([[systemlist('"cat" "]]..fname..[["')]]))
+ end)
+ end)
+
+ describe('input passed as List', function()
+ it('joins list items with linefeed characters', function()
+ eq({'line1', 'line2', 'line3'},
+ eval("systemlist('cat -', ['line1', 'line2', 'line3'])"))
+ end)
+
+ -- Unlike `system()` which uses SOH to represent NULs, with `systemlist()`
+ -- input and ouput are the same.
+ describe('with linefeed characters inside list items', function()
+ it('converts linefeed characters to NULs', function()
+ eq({'l1\np2', 'line2\na\nb', 'l3'},
+ eval([[systemlist('cat -', ["l1\np2", "line2\na\nb", 'l3'])]]))
+ end)
+ end)
+
+ describe('with leading/trailing whitespace characters on items', function()
+ it('preserves whitespace, replacing linefeeds by NULs', function()
+ eq({'line ', 'line2\n', '\nline3'},
+ eval([[systemlist('cat -', ['line ', "line2\n", "\nline3"])]]))
+ end)
+ end)
+ end)
+
+ describe('handles empty lines', function()
+ it('in the middle', function()
+ eq({'line one','','line two'}, eval("systemlist('cat',['line one','','line two'])"))
+ end)
+
+ it('in the beginning', function()
+ eq({'','line one','line two'}, eval("systemlist('cat',['','line one','line two'])"))
+ end)
+ end)
+
+ describe('when keepempty option is', function()
+ it('0, ignores trailing newline', function()
+ eq({'aa','bb'}, eval("systemlist('cat',['aa','bb'],0)"))
+ eq({'aa','bb'}, eval("systemlist('cat',['aa','bb',''],0)"))
+ end)
+
+ it('1, preserves trailing newline', function()
+ eq({'aa','bb'}, eval("systemlist('cat',['aa','bb'],1)"))
+ eq({'aa','bb',''}, eval("systemlist('cat',['aa','bb',''],2)"))
+ end)
+ end)
+
+ describe("with a program that doesn't close stdout", function()
+ if not xclip then
+ pending('missing `xclip`', function() end)
+ else
+ it('will exit properly after passing input', function()
+ eq({}, eval(
+ "systemlist('xclip -i -loops 1 -selection clipboard', ['clip', 'data'])"))
+ eq({'clip', 'data'}, eval(
+ "systemlist('xclip -o -selection clipboard')"))
+ end)
+ end
+ end)
+end)
diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua
index 2f83edb9e4..124b9625c3 100644
--- a/test/functional/eval/timer_spec.lua
+++ b/test/functional/eval/timer_spec.lua
@@ -1,8 +1,9 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local ok, feed, eq, eval = helpers.ok, helpers.feed, helpers.eq, helpers.eval
+local feed, eq, eval = helpers.feed, helpers.eq, helpers.eval
local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run
-local clear, execute, funcs = helpers.clear, helpers.execute, helpers.funcs
+local clear, command, funcs = helpers.clear, helpers.command, helpers.funcs
+local curbufmeths = helpers.curbufmeths
describe('timers', function()
before_each(function()
@@ -16,14 +17,14 @@ describe('timers', function()
end)
it('works one-shot', function()
- execute("call timer_start(50, 'MyHandler')")
+ command("call timer_start(50, 'MyHandler')")
eq(0,eval("g:val"))
run(nil, nil, nil, 200)
eq(1,eval("g:val"))
end)
it('works one-shot when repeat=0', function()
- execute("call timer_start(50, 'MyHandler', {'repeat': 0})")
+ command("call timer_start(50, 'MyHandler', {'repeat': 0})")
eq(0,eval("g:val"))
run(nil, nil, nil, 200)
eq(1,eval("g:val"))
@@ -31,14 +32,14 @@ describe('timers', function()
it('works with repeat two', function()
- execute("call timer_start(50, 'MyHandler', {'repeat': 2})")
+ command("call timer_start(50, 'MyHandler', {'repeat': 2})")
eq(0,eval("g:val"))
run(nil, nil, nil, 300)
eq(2,eval("g:val"))
end)
it('are triggered during sleep', function()
- execute("call timer_start(50, 'MyHandler', {'repeat': 2})")
+ command("call timer_start(50, 'MyHandler', {'repeat': 2})")
nvim_async("command", "sleep 10")
eq(0,eval("g:val"))
run(nil, nil, nil, 300)
@@ -48,7 +49,7 @@ describe('timers', function()
it('works with zero timeout', function()
-- timer_start does still not invoke the callback immediately
eq(0,eval("[timer_start(0, 'MyHandler', {'repeat': 1000}), g:val][1]"))
- run(nil, nil, nil, 300)
+ run(nil, nil, nil, 400)
eq(1000,eval("g:val"))
end)
@@ -62,19 +63,77 @@ describe('timers', function()
end)
it('are paused when event processing is disabled', function()
- -- this is not the intended behavior, but at least there will
- -- not be a burst of queued up callbacks
- execute("call timer_start(50, 'MyHandler', {'repeat': 2})")
+ command("call timer_start(50, 'MyHandler', {'repeat': -1})")
run(nil, nil, nil, 100)
local count = eval("g:val")
+ -- shows two line error message and thus invokes the return prompt.
+ -- if we start to allow event processing here, we need to change this test.
+ feed(':throw "fatal error"<CR>')
+ run(nil, nil, nil, 300)
+ feed("<cr>")
+ local diff = eval("g:val") - count
+ assert(0 <= diff and diff <= 4,
+ 'expected (0 <= diff <= 4), got: '..tostring(diff))
+ end)
+
+ it('are triggered in blocking getchar() call', function()
+ command("call timer_start(50, 'MyHandler', {'repeat': -1})")
nvim_async("command", "let g:c = getchar()")
run(nil, nil, nil, 300)
feed("c")
- local diff = eval("g:val") - count
- ok(0 <= diff and diff <= 2)
+ local count = eval("g:val")
+ assert(count >= 3, 'expected count >= 3, got: '..tostring(count))
eq(99, eval("g:c"))
end)
+ it('can invoke redraw in blocking getchar() call', function()
+ local screen = Screen.new(40, 6)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold=true, foreground=Screen.colors.Blue},
+ })
+
+ curbufmeths.set_lines(0, -1, true, {"ITEM 1", "ITEM 2"})
+ source([[
+ func! AddItem(timer)
+ call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3'])
+ call getchar(1)
+ redraw
+ endfunc
+ call timer_start(200, 'AddItem')
+ ]])
+ nvim_async("command", "let g:c2 = getchar()")
+
+ screen:expect([[
+ ITEM 1 |
+ ITEM 2 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ^ |
+ ]])
+
+ screen:expect([[
+ ITEM 1 |
+ ITEM 2 |
+ ITEM 3 |
+ {1:~ }|
+ {1:~ }|
+ ^ |
+ ]])
+
+ feed("3")
+ eq(51, eval("g:c2"))
+ screen:expect([[
+ ^ITEM 1 |
+ ITEM 2 |
+ ITEM 3 |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end)
+
it('can be stopped', function()
local t = eval("timer_start(50, 'MyHandler', {'repeat': -1})")
eq(0,eval("g:val"))
@@ -83,9 +142,10 @@ describe('timers', function()
local count = eval("g:val")
run(nil, nil, nil, 300)
local count2 = eval("g:val")
- ok(4 <= count and count <= 7)
-- when count is eval:ed after timer_stop this should be non-racy
eq(count, count2)
+ assert(3 <= count and count <= 7,
+ 'expected (3 <= count <= 7), got: '..tostring(count))
end)
it('can be stopped from the handler', function()
@@ -99,7 +159,7 @@ describe('timers', function()
endif
endfunc
]])
- execute("call timer_start(50, 'MyHandler', {'repeat': -1})")
+ command("call timer_start(50, 'MyHandler', {'repeat': -1})")
eq(0,eval("g:val"))
run(nil, nil, nil, 300)
eq(3,eval("g:val"))
@@ -112,33 +172,60 @@ describe('timers', function()
let g:val2 += 1
endfunc
]])
- execute("call timer_start(50, 'MyHandler', {'repeat': 3})")
- execute("call timer_start(100, 'MyHandler2', {'repeat': 2})")
+ command("call timer_start(20, 'MyHandler', {'repeat': 3})")
+ command("call timer_start(40, 'MyHandler2', {'repeat': 2})")
run(nil, nil, nil, 300)
eq(3,eval("g:val"))
eq(2,eval("g:val2"))
end)
+ it('do not crash when processing events in the handler', function()
+ source([[
+ let g:val = 0
+ func! MyHandler(timer)
+ call timer_stop(a:timer)
+ sleep 100m
+ let g:val += 1
+ endfunc
+ ]])
+ command("call timer_start(5, 'MyHandler', {'repeat': 1})")
+ run(nil, nil, nil, 300)
+ eq(1,eval("g:val"))
+ end)
+
+
it("doesn't mess up the cmdline", function()
local screen = Screen.new(40, 6)
screen:attach()
- screen:set_default_attr_ignore({{bold=true, foreground=Screen.colors.Blue}})
+ screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} )
source([[
+ let g:val = 0
func! MyHandler(timer)
echo "evil"
+ let g:val = 1
endfunc
]])
- execute("call timer_start(100, 'MyHandler', {'repeat': 1})")
+ command("call timer_start(100, 'MyHandler', {'repeat': 1})")
feed(":good")
- screen:sleep(200)
screen:expect([[
|
- ~ |
- ~ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
:good^ |
]])
+
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ :good^ |
+ ]], intermediate=true, timeout=200}
+
+ eq(1, eval('g:val'))
end)
end)
diff --git a/test/functional/eval/uniq_spec.lua b/test/functional/eval/uniq_spec.lua
new file mode 100644
index 0000000000..0e7a013e32
--- /dev/null
+++ b/test/functional/eval/uniq_spec.lua
@@ -0,0 +1,31 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local clear = helpers.clear
+local meths = helpers.meths
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local redir_exec = helpers.redir_exec
+
+before_each(clear)
+
+describe('uniq()', function()
+ it('errors out when processing special values', function()
+ eq('Vim(call):E907: Using a special value as a Float',
+ exc_exec('call uniq([v:true, v:false], "f")'))
+ end)
+
+ it('can yield E882 and stop filtering after that', function()
+ command([[
+ function Cmp(a, b)
+ if type(a:a) == type([]) || type(a:b) == type([])
+ return []
+ endif
+ return (a:a > a:b) - (a:a < a:b)
+ endfunction
+ ]])
+ eq('\nE745: Using a List as a Number\nE882: Uniq compare function failed',
+ redir_exec('let fl = uniq([0, 0, [], 1, 1], "Cmp")'))
+ eq({0, {}, 1, 1}, meths.get_var('fl'))
+ end)
+end)
diff --git a/test/functional/eval/writefile_spec.lua b/test/functional/eval/writefile_spec.lua
new file mode 100644
index 0000000000..0bb7523d7e
--- /dev/null
+++ b/test/functional/eval/writefile_spec.lua
@@ -0,0 +1,146 @@
+local helpers = require('test.functional.helpers')(after_each)
+local lfs = require('lfs')
+
+local clear = helpers.clear
+local eq = helpers.eq
+local funcs = helpers.funcs
+local meths = helpers.meths
+local exc_exec = helpers.exc_exec
+local read_file = helpers.read_file
+local write_file = helpers.write_file
+local redir_exec = helpers.redir_exec
+
+local fname = 'Xtest-functional-eval-writefile'
+local dname = fname .. '.d'
+local dfname_tail = '1'
+local dfname = dname .. '/' .. dfname_tail
+local ddname_tail = '2'
+local ddname = dname .. '/' .. ddname_tail
+
+before_each(function()
+ lfs.mkdir(dname)
+ lfs.mkdir(ddname)
+ clear()
+end)
+
+after_each(function()
+ os.remove(fname)
+ os.remove(dfname)
+ lfs.rmdir(ddname)
+ lfs.rmdir(dname)
+end)
+
+describe('writefile()', function()
+ it('writes empty list to a file', function()
+ eq(nil, read_file(fname))
+ eq(0, funcs.writefile({}, fname))
+ eq('', read_file(fname))
+ os.remove(fname)
+ eq(nil, read_file(fname))
+ eq(0, funcs.writefile({}, fname, 'b'))
+ eq('', read_file(fname))
+ os.remove(fname)
+ eq(nil, read_file(fname))
+ eq(0, funcs.writefile({}, fname, 'ab'))
+ eq('', read_file(fname))
+ os.remove(fname)
+ eq(nil, read_file(fname))
+ eq(0, funcs.writefile({}, fname, 'a'))
+ eq('', read_file(fname))
+ end)
+
+ it('writes list with an empty string to a file', function()
+ eq(0, exc_exec(
+ ('call writefile([$XXX_NONEXISTENT_VAR_XXX], "%s", "b")'):format(
+ fname)))
+ eq('', read_file(fname))
+ eq(0, exc_exec(('call writefile([$XXX_NONEXISTENT_VAR_XXX], "%s")'):format(
+ fname)))
+ eq('\n', read_file(fname))
+ end)
+
+ it('appends to a file', function()
+ eq(nil, read_file(fname))
+ eq(0, funcs.writefile({'abc', 'def', 'ghi'}, fname))
+ eq('abc\ndef\nghi\n', read_file(fname))
+ eq(0, funcs.writefile({'jkl'}, fname, 'a'))
+ eq('abc\ndef\nghi\njkl\n', read_file(fname))
+ os.remove(fname)
+ eq(nil, read_file(fname))
+ eq(0, funcs.writefile({'abc', 'def', 'ghi'}, fname, 'b'))
+ eq('abc\ndef\nghi', read_file(fname))
+ eq(0, funcs.writefile({'jkl'}, fname, 'ab'))
+ eq('abc\ndef\nghijkl', read_file(fname))
+ end)
+
+ it('correctly treats NLs', function()
+ eq(0, funcs.writefile({'\na\nb\n'}, fname, 'b'))
+ eq('\0a\0b\0', read_file(fname))
+ eq(0, funcs.writefile({'a\n\n\nb'}, fname, 'b'))
+ eq('a\0\0\0b', read_file(fname))
+ end)
+
+ it('writes with s and S', function()
+ eq(0, funcs.writefile({'\na\nb\n'}, fname, 'bs'))
+ eq('\0a\0b\0', read_file(fname))
+ eq(0, funcs.writefile({'a\n\n\nb'}, fname, 'bS'))
+ eq('a\0\0\0b', read_file(fname))
+ end)
+
+ it('correctly overwrites file', function()
+ eq(0, funcs.writefile({'\na\nb\n'}, fname, 'b'))
+ eq('\0a\0b\0', read_file(fname))
+ eq(0, funcs.writefile({'a\n'}, fname, 'b'))
+ eq('a\0', read_file(fname))
+ end)
+
+ it('shows correct file name when supplied numbers', function()
+ meths.set_current_dir(dname)
+ eq('\nE482: Can\'t open file 2 for writing: illegal operation on a directory',
+ redir_exec(('call writefile([42], %s)'):format(ddname_tail)))
+ end)
+
+ it('errors out with invalid arguments', function()
+ write_file(fname, 'TEST')
+ eq('\nE119: Not enough arguments for function: writefile',
+ redir_exec('call writefile()'))
+ eq('\nE119: Not enough arguments for function: writefile',
+ redir_exec('call writefile([])'))
+ eq('\nE118: Too many arguments for function: writefile',
+ redir_exec(('call writefile([], "%s", "b", 1)'):format(fname)))
+ for _, arg in ipairs({'0', '0.0', 'function("tr")', '{}', '"test"'}) do
+ eq('\nE686: Argument of writefile() must be a List',
+ redir_exec(('call writefile(%s, "%s", "b")'):format(arg, fname)))
+ end
+ for _, args in ipairs({'[], %s, "b"', '[], "' .. fname .. '", %s'}) do
+ eq('\nE806: using Float as a String',
+ redir_exec(('call writefile(%s)'):format(args:format('0.0'))))
+ eq('\nE730: using List as a String',
+ redir_exec(('call writefile(%s)'):format(args:format('[]'))))
+ eq('\nE731: using Dictionary as a String',
+ redir_exec(('call writefile(%s)'):format(args:format('{}'))))
+ eq('\nE729: using Funcref as a String',
+ redir_exec(('call writefile(%s)'):format(args:format('function("tr")'))))
+ end
+ eq('\nE5060: Unknown flag: «»',
+ redir_exec(('call writefile([], "%s", "bs«»")'):format(fname)))
+ eq('TEST', read_file(fname))
+ end)
+
+ it('does not write to file if error in list', function()
+ local args = '["tset"] + repeat([%s], 3), "' .. fname .. '"'
+ eq('\nE805: Expected a Number or a String, Float found',
+ redir_exec(('call writefile(%s)'):format(args:format('0.0'))))
+ eq(nil, read_file(fname))
+ write_file(fname, 'TEST')
+ eq('\nE745: Expected a Number or a String, List found',
+ redir_exec(('call writefile(%s)'):format(args:format('[]'))))
+ eq('TEST', read_file(fname))
+ eq('\nE728: Expected a Number or a String, Dictionary found',
+ redir_exec(('call writefile(%s)'):format(args:format('{}'))))
+ eq('TEST', read_file(fname))
+ eq('\nE703: Expected a Number or a String, Funcref found',
+ redir_exec(('call writefile(%s)'):format(args:format('function("tr")'))))
+ eq('TEST', read_file(fname))
+ end)
+end)