Problem:
Subdirectories like "visual", "insert", "normal" encourage people to
separate *related* tests for no good reason.  Typically the _mode_ is
not the relevant topic of a test (and when it is, _then_ create
an appropriate describe() or it()).

Solution:
- Delete the various `test/functional/<mode>/` subdirectories, move
  their tests to more meaningful topics.
- Rename `…/normal/` to `…/editor/`.
  - Move or merge `…/visual/*` and `…/insert/*` tests into here where
    appropriate.
- Rename `…/eval/` to `…/vimscript/`.
  - Move `…/viml/*` into here also.

* test(reorg): insert/* => editor/mode_insert_spec.lua
* test(reorg): cmdline/* => editor/mode_cmdline_spec.lua
* test(reorg): eval core tests => eval_spec.lua
This commit is contained in:
Justin M. Keyes
2021-09-17 09:16:40 -07:00
committed by GitHub
parent d56002f7b7
commit d8de4eb685
67 changed files with 322 additions and 538 deletions

View File

@@ -0,0 +1,167 @@
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, pcall_err = helpers.insert, helpers.pcall_err
local meths = helpers.meths
describe('eval-API', 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):E5555: API call: Wrong type for argument 4 when calling nvim_buf_set_lines, expecting Boolean', err)
err = exc_exec('call nvim_buf_set_lines(0, 0, -1, v:true, "string")')
eq('Vim(call):E5555: API call: Wrong type for argument 5 when calling nvim_buf_set_lines, expecting ArrayOf(String)', err)
err = exc_exec('call nvim_buf_get_number("0")')
eq('Vim(call):E5555: API call: Wrong type for argument 1 when calling nvim_buf_get_number, expecting Buffer', err)
err = exc_exec('call nvim_buf_line_count(17)')
eq('Vim(call):E5555: API call: Invalid buffer id: 17', err)
end)
it('cannot change texts if textlocked', function()
command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])")
eq('Vim(call):E5555: API call: E523: Not allowed here', pcall_err(command, "normal! yy"))
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:~ }|
|
]])
end)
it('cannot be called from sandbox', function()
eq('Vim(call):E48: Not allowed in sandbox',
pcall_err(command, "sandbox call nvim_input('ievil')"))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
end)
it('converts blobs to API strings', function()
command('let g:v1 = nvim__id(0z68656c6c6f)')
command('let g:v2 = nvim__id(v:_null_blob)')
eq(1, eval('type(g:v1)'))
eq(1, eval('type(g:v2)'))
eq('hello', eval('g:v1'))
eq('', eval('g:v2'))
end)
end)

View File

@@ -0,0 +1,306 @@
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 pcall_err = helpers.pcall_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'}) do
eq('Vim(call):E5299: Expected a Number or a String, Boolean found',
exc_exec('call ' .. func:format(var)))
end
eq('Vim(call):E5300: Expected a Number or a String',
exc_exec('call ' .. func:format('v:null')))
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(1, funcs.getbufvar(1, '&hidden'))
eq(1, funcs.getbufvar(1, '&l:hidden'))
eq(1, 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
command("setl number")
-- (note that current windows buffer is 2, but getbufvar() receives 1)
eq({id=2}, 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(true, meths.get_option('hidden'))
funcs.setbufvar(1, '&hidden', 0)
eq(false, 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'))
eq('Vim:E46: Cannot change read-only variable "b:changedtick"',
pcall_err(funcs.setbufvar, 1, 'changedtick', true))
eq(2, funcs.getbufvar(1, 'changedtick'))
end)
end)

View File

@@ -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 pcall_err = helpers.pcall_err
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('Key is read-only: changedtick',
pcall_err(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('Key is read-only: changedtick',
pcall_err(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)

View File

@@ -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)

View File

@@ -0,0 +1,406 @@
local helpers = require('test.functional.helpers')(after_each)
local call = helpers.call
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
local feed = helpers.feed
local map = helpers.tbl_map
local nvim = helpers.nvim
local parse_context = helpers.parse_context
local redir_exec = helpers.redir_exec
local source = helpers.source
local trim = helpers.trim
local write_file = helpers.write_file
local pcall_err = helpers.pcall_err
describe('context functions', function()
local fname1 = 'Xtest-functional-eval-ctx1'
local fname2 = 'Xtest-functional-eval-ctx2'
local outofbounds =
'Vim:E475: Invalid value for argument index: out of bounds'
before_each(function()
clear()
write_file(fname1, "1\n2\n3")
write_file(fname2, "a\nb\nc")
end)
after_each(function()
os.remove(fname1)
os.remove(fname2)
end)
describe('ctxpush/ctxpop', function()
it('saves and restores registers properly', function()
local regs = {'1', '2', '3', 'a'}
local vals = {'1', '2', '3', 'hjkl'}
feed('i1<cr>2<cr>3<c-[>ddddddqahjklq')
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
call('ctxpush')
call('ctxpush', {'regs'})
map(function(r) call('setreg', r, {}) end, regs)
eq({'', '', '', ''},
map(function(r) return trim(call('getreg', r)) end, regs))
call('ctxpop')
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
map(function(r) call('setreg', r, {}) end, regs)
eq({'', '', '', ''},
map(function(r) return trim(call('getreg', r)) end, regs))
call('ctxpop')
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
end)
it('saves and restores jumplist properly', function()
command('edit '..fname1)
feed('G')
feed('gg')
command('edit '..fname2)
local jumplist = call('getjumplist')
call('ctxpush')
call('ctxpush', {'jumps'})
command('clearjumps')
eq({{}, 0}, call('getjumplist'))
call('ctxpop')
eq(jumplist, call('getjumplist'))
command('clearjumps')
eq({{}, 0}, call('getjumplist'))
call('ctxpop')
eq(jumplist, call('getjumplist'))
end)
it('saves and restores buffer list properly', function()
command('edit '..fname1)
command('edit '..fname2)
command('edit TEST')
local bufs = call('map', call('getbufinfo'), 'v:val.name')
call('ctxpush')
call('ctxpush', {'bufs'})
command('%bwipeout')
eq({''}, call('map', call('getbufinfo'), 'v:val.name'))
call('ctxpop')
eq({'', unpack(bufs)}, call('map', call('getbufinfo'), 'v:val.name'))
command('%bwipeout')
eq({''}, call('map', call('getbufinfo'), 'v:val.name'))
call('ctxpop')
eq({'', unpack(bufs)}, call('map', call('getbufinfo'), 'v:val.name'))
end)
it('saves and restores global variables properly', function()
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
nvim('set_var', 'THREE', 3)
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
call('ctxpush')
call('ctxpush', {'gvars'})
nvim('del_var', 'one')
nvim('del_var', 'Two')
nvim('del_var', 'THREE')
eq('Vim:E121: Undefined variable: g:one', pcall_err(eval, 'g:one'))
eq('Vim:E121: Undefined variable: g:Two', pcall_err(eval, 'g:Two'))
eq('Vim:E121: Undefined variable: g:THREE', pcall_err(eval, 'g:THREE'))
call('ctxpop')
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
nvim('del_var', 'one')
nvim('del_var', 'Two')
nvim('del_var', 'THREE')
eq('Vim:E121: Undefined variable: g:one', pcall_err(eval, 'g:one'))
eq('Vim:E121: Undefined variable: g:Two', pcall_err(eval, 'g:Two'))
eq('Vim:E121: Undefined variable: g:THREE', pcall_err(eval, 'g:THREE'))
call('ctxpop')
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
end)
it('saves and restores script functions properly', function()
source([[
function s:greet(name)
echom 'Hello, '.a:name.'!'
endfunction
function s:greet_all(name, ...)
echom 'Hello, '.a:name.'!'
for more in a:000
echom 'Hello, '.more.'!'
endfor
endfunction
function Greet(name)
call call('s:greet', [a:name])
endfunction
function GreetAll(name, ...)
call call('s:greet_all', extend([a:name], a:000))
endfunction
function SaveSFuncs()
call ctxpush(['sfuncs'])
endfunction
function DeleteSFuncs()
delfunction s:greet
delfunction s:greet_all
endfunction
function RestoreFuncs()
call ctxpop()
endfunction
]])
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
eq('\nHello, World!'..
'\nHello, One!'..
'\nHello, Two!'..
'\nHello, Three!',
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
call('SaveSFuncs')
call('DeleteSFuncs')
eq('\nError detected while processing function Greet:'..
'\nline 1:'..
'\nE117: Unknown function: s:greet',
redir_exec([[call Greet('World')]]))
eq('\nError detected while processing function GreetAll:'..
'\nline 1:'..
'\nE117: Unknown function: s:greet_all',
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
call('RestoreFuncs')
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
eq('\nHello, World!'..
'\nHello, One!'..
'\nHello, Two!'..
'\nHello, Three!',
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
end)
it('saves and restores functions properly', function()
source([[
function Greet(name)
echom 'Hello, '.a:name.'!'
endfunction
function GreetAll(name, ...)
echom 'Hello, '.a:name.'!'
for more in a:000
echom 'Hello, '.more.'!'
endfor
endfunction
]])
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
eq('\nHello, World!'..
'\nHello, One!'..
'\nHello, Two!'..
'\nHello, Three!',
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
call('ctxpush', {'funcs'})
command('delfunction Greet')
command('delfunction GreetAll')
eq('Vim:E117: Unknown function: Greet', pcall_err(call, 'Greet', 'World'))
eq('Vim:E117: Unknown function: GreetAll',
pcall_err(call, 'GreetAll', 'World', 'One', 'Two', 'Three'))
call('ctxpop')
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
eq('\nHello, World!'..
'\nHello, One!'..
'\nHello, Two!'..
'\nHello, Three!',
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
end)
it('errors out when context stack is empty', function()
local err = 'Vim:Context stack is empty'
eq(err, pcall_err(call, 'ctxpop'))
eq(err, pcall_err(call, 'ctxpop'))
call('ctxpush')
call('ctxpush')
call('ctxpop')
call('ctxpop')
eq(err, pcall_err(call, 'ctxpop'))
end)
end)
describe('ctxsize()', function()
it('returns context stack size', function()
eq(0, call('ctxsize'))
call('ctxpush')
eq(1, call('ctxsize'))
call('ctxpush')
eq(2, call('ctxsize'))
call('ctxpush')
eq(3, call('ctxsize'))
call('ctxpop')
eq(2, call('ctxsize'))
call('ctxpop')
eq(1, call('ctxsize'))
call('ctxpop')
eq(0, call('ctxsize'))
end)
end)
describe('ctxget()', function()
it('errors out when index is out of bounds', function()
eq(outofbounds, pcall_err(call, 'ctxget'))
call('ctxpush')
eq(outofbounds, pcall_err(call, 'ctxget', 1))
call('ctxpop')
eq(outofbounds, pcall_err(call, 'ctxget', 0))
end)
it('returns context dictionary at index in context stack', function()
feed('i1<cr>2<cr>3<c-[>ddddddqahjklq')
command('edit! '..fname1)
feed('G')
feed('gg')
command('edit '..fname2)
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
nvim('set_var', 'THREE', 3)
local with_regs = {
['regs'] = {
{['rt'] = 1, ['rc'] = {'1'}, ['n'] = 49, ['ru'] = true},
{['rt'] = 1, ['rc'] = {'2'}, ['n'] = 50},
{['rt'] = 1, ['rc'] = {'3'}, ['n'] = 51},
{['rc'] = {'hjkl'}, ['n'] = 97},
}
}
local with_jumps = {
['jumps'] = eval(([[
filter(map(getjumplist()[0], 'filter(
{ "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum },
{ k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)')
]]):gsub('\n', ''))
}
local with_bufs = {
['bufs'] = eval([[
filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)')
]])
}
local with_gvars = {
['gvars'] = {{'one', 1}, {'Two', 2}, {'THREE', 3}}
}
local with_all = {
['regs'] = with_regs['regs'],
['jumps'] = with_jumps['jumps'],
['bufs'] = with_bufs['bufs'],
['gvars'] = with_gvars['gvars'],
}
call('ctxpush')
eq(with_all, parse_context(call('ctxget')))
eq(with_all, parse_context(call('ctxget', 0)))
call('ctxpush', {'gvars'})
eq(with_gvars, parse_context(call('ctxget')))
eq(with_gvars, parse_context(call('ctxget', 0)))
eq(with_all, parse_context(call('ctxget', 1)))
call('ctxpush', {'bufs'})
eq(with_bufs, parse_context(call('ctxget')))
eq(with_bufs, parse_context(call('ctxget', 0)))
eq(with_gvars, parse_context(call('ctxget', 1)))
eq(with_all, parse_context(call('ctxget', 2)))
call('ctxpush', {'jumps'})
eq(with_jumps, parse_context(call('ctxget')))
eq(with_jumps, parse_context(call('ctxget', 0)))
eq(with_bufs, parse_context(call('ctxget', 1)))
eq(with_gvars, parse_context(call('ctxget', 2)))
eq(with_all, parse_context(call('ctxget', 3)))
call('ctxpush', {'regs'})
eq(with_regs, parse_context(call('ctxget')))
eq(with_regs, parse_context(call('ctxget', 0)))
eq(with_jumps, parse_context(call('ctxget', 1)))
eq(with_bufs, parse_context(call('ctxget', 2)))
eq(with_gvars, parse_context(call('ctxget', 3)))
eq(with_all, parse_context(call('ctxget', 4)))
call('ctxpop')
eq(with_jumps, parse_context(call('ctxget')))
eq(with_jumps, parse_context(call('ctxget', 0)))
eq(with_bufs, parse_context(call('ctxget', 1)))
eq(with_gvars, parse_context(call('ctxget', 2)))
eq(with_all, parse_context(call('ctxget', 3)))
call('ctxpop')
eq(with_bufs, parse_context(call('ctxget')))
eq(with_bufs, parse_context(call('ctxget', 0)))
eq(with_gvars, parse_context(call('ctxget', 1)))
eq(with_all, parse_context(call('ctxget', 2)))
call('ctxpop')
eq(with_gvars, parse_context(call('ctxget')))
eq(with_gvars, parse_context(call('ctxget', 0)))
eq(with_all, parse_context(call('ctxget', 1)))
call('ctxpop')
eq(with_all, parse_context(call('ctxget')))
eq(with_all, parse_context(call('ctxget', 0)))
end)
end)
describe('ctxset()', function()
it('errors out when index is out of bounds', function()
eq(outofbounds, pcall_err(call, 'ctxset', {dummy = 1}))
call('ctxpush')
eq(outofbounds, pcall_err(call, 'ctxset', {dummy = 1}, 1))
call('ctxpop')
eq(outofbounds, pcall_err(call, 'ctxset', {dummy = 1}, 0))
end)
it('sets context dictionary at index in context stack', function()
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
nvim('set_var', 'THREE', 3)
call('ctxpush')
local ctx1 = call('ctxget')
nvim('set_var', 'one', 'a')
nvim('set_var', 'Two', 'b')
nvim('set_var', 'THREE', 'c')
call('ctxpush')
call('ctxpush')
local ctx2 = call('ctxget')
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
call('ctxset', ctx1)
call('ctxset', ctx2, 2)
call('ctxpop')
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
call('ctxpop')
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
nvim('set_var', 'one', 1.5)
eq({1.5, 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
call('ctxpop')
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
end)
end)
end)

View File

@@ -0,0 +1,80 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local environ = helpers.funcs.environ
local exists = helpers.funcs.exists
local system = helpers.funcs.system
local nvim_prog = helpers.nvim_prog
local command = helpers.command
local eval = helpers.eval
local setenv = helpers.funcs.setenv
describe('environment variables', function()
it('environ() handles empty env variable', function()
clear({env={EMPTY_VAR=""}})
eq("", environ()['EMPTY_VAR'])
eq(nil, environ()['DOES_NOT_EXIST'])
end)
it('exists() handles empty env variable', function()
clear({env={EMPTY_VAR=""}})
eq(1, exists('$EMPTY_VAR'))
eq(0, exists('$DOES_NOT_EXIST'))
end)
end)
describe('empty $HOME', function()
local original_home = os.getenv('HOME')
-- recover $HOME after each test
after_each(function()
if original_home ~= nil then
setenv('HOME', original_home)
end
os.remove('test_empty_home')
os.remove('./~')
end)
local function tilde_in_cwd()
-- get files in cwd
command("let test_empty_home_cwd_files = split(globpath('.', '*'), '\n')")
-- get the index of the file named '~'
command('let test_empty_home_tilde_index = index(test_empty_home_cwd_files, "./~")')
return eval('test_empty_home_tilde_index') ~= -1
end
local function write_and_test_tilde()
system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
'-c', 'write test_empty_home', '+q'})
eq(false, tilde_in_cwd())
end
it("'~' folder not created in cwd if $HOME and related env not defined", function()
command("unlet $HOME")
write_and_test_tilde()
command("let $HOMEDRIVE='C:'")
command("let $USERPROFILE='C:\\'")
write_and_test_tilde()
command("unlet $HOMEDRIVE")
write_and_test_tilde()
command("unlet $USERPROFILE")
write_and_test_tilde()
command("let $HOME='%USERPROFILE%'")
command("let $USERPROFILE='C:\\'")
write_and_test_tilde()
end)
it("'~' folder not created in cwd if writing a file with invalid $HOME", function()
setenv('HOME', '/path/does/not/exist')
write_and_test_tilde()
end)
it("'~' folder not created in cwd if writing a file with $HOME=''", function()
command("let $HOME=''")
write_and_test_tilde()
end)
end)

View File

@@ -0,0 +1,84 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local exc_exec = helpers.exc_exec
local get_cur_win_var = helpers.curwinmeths.get_var
describe('setqflist()', function()
local setqflist = helpers.funcs.setqflist
before_each(clear)
it('requires a list for {list}', function()
eq('Vim(call):E714: List required', exc_exec('call setqflist("foo")'))
eq('Vim(call):E714: List required', exc_exec('call setqflist(5)'))
eq('Vim(call):E714: List required', exc_exec('call setqflist({})'))
end)
it('requires a string for {action}', function()
eq('Vim(call):E928: String required', exc_exec('call setqflist([], 5)'))
eq('Vim(call):E928: String required', exc_exec('call setqflist([], [])'))
eq('Vim(call):E928: String required', exc_exec('call setqflist([], {})'))
end)
it('sets w:quickfix_title', function()
setqflist({''}, 'r', 'foo')
command('copen')
eq('foo', get_cur_win_var('quickfix_title'))
setqflist({}, 'r', {['title'] = 'qf_title'})
eq('qf_title', get_cur_win_var('quickfix_title'))
end)
it('allows string {what} for backwards compatibility', function()
setqflist({}, 'r', '5')
command('copen')
eq('5', get_cur_win_var('quickfix_title'))
end)
it('requires a dict for {what}', function()
eq('Vim(call):E715: Dictionary required', exc_exec('call setqflist([], "r", function("function"))'))
end)
end)
describe('setloclist()', function()
local setloclist = helpers.funcs.setloclist
before_each(clear)
it('requires a list for {list}', function()
eq('Vim(call):E714: List required', exc_exec('call setloclist(0, "foo")'))
eq('Vim(call):E714: List required', exc_exec('call setloclist(0, 5)'))
eq('Vim(call):E714: List required', exc_exec('call setloclist(0, {})'))
end)
it('requires a string for {action}', function()
eq('Vim(call):E928: String required', exc_exec('call setloclist(0, [], 5)'))
eq('Vim(call):E928: String required', exc_exec('call setloclist(0, [], [])'))
eq('Vim(call):E928: String required', exc_exec('call setloclist(0, [], {})'))
end)
it('sets w:quickfix_title for the correct window', function()
command('rightbelow vsplit')
setloclist(1, {}, 'r', 'foo')
setloclist(2, {}, 'r', 'bar')
command('lopen')
eq('bar', get_cur_win_var('quickfix_title'))
command('lclose | wincmd w | lopen')
eq('foo', get_cur_win_var('quickfix_title'))
end)
it("doesn't crash when when window is closed in the middle #13721", function()
helpers.insert([[
hello world]])
command("vsplit")
command("autocmd WinLeave * :call nvim_win_close(0, v:true)")
command("call setloclist(0, [])")
command("lopen")
helpers.assert_alive()
end)
end)

View File

@@ -0,0 +1,146 @@
-- Tests for core Vimscript "eval" behavior.
--
-- See also:
-- let_spec.lua
-- null_spec.lua
-- operators_spec.lua
--
-- Tests for the Vimscript |functions| library should live in:
-- test/functional/vimscript/<funcname>_spec.lua
-- test/functional/vimscript/functions_spec.lua
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local clear = helpers.clear
local eq = helpers.eq
local exc_exec = helpers.exc_exec
local eval = helpers.eval
local command = helpers.command
local write_file = helpers.write_file
local meths = helpers.meths
local sleep = helpers.sleep
local poke_eventloop = helpers.poke_eventloop
local feed = helpers.feed
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)
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)
describe('List support code', function()
local dur
local min_dur = 8
local len = 131072
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>')
poke_eventloop()
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>')
poke_eventloop()
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)

View File

@@ -0,0 +1,218 @@
local helpers = require('test.functional.helpers')(after_each)
local eq, clear, call, iswin, write_file, command =
helpers.eq, helpers.clear, helpers.call, helpers.iswin, helpers.write_file,
helpers.command
local exc_exec = helpers.exc_exec
local eval = helpers.eval
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))
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
eq(1, call('executable', 'null'))
eq(1, call('executable', 'true'))
eq(1, call('executable', 'false'))
end)
it('fails for invalid values', function()
for _, input in ipairs({'""', 'v:null', 'v:true', 'v:false', '{}', '[]'}) do
eq('Vim(call):E928: String required', exc_exec('call executable('..input..')'))
end
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do
eq('Vim(call):E928: String required', exc_exec('call executable('..input..')'))
end
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
eq('arg1=lemon;arg2=sky;arg3=tree;',
call('system', sibling_exe..' lemon sky tree'))
end
eq(expected, call('executable', sibling_exe))
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()
eq(0, call('executable', 'Xtest_not_executable'))
eq(0, call('executable', './Xtest_not_executable'))
end)
it('set, unqualified and not in $PATH', function()
eq(0, call('executable', 'Xtest_executable'))
end)
it('set, qualified as a path', function()
local expected = iswin() and 0 or 1
eq(expected, 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('system([…]), jobstart([…]) use $PATHEXT #9569', function()
-- Invoking `cmdscript` should find/execute `cmdscript.cmd`.
eq('much success\n', call('system', {'test/functional/fixtures/cmdscript'}))
assert(0 < call('jobstart', {'test/functional/fixtures/cmdscript'}))
end)
it('full path with extension', function()
-- Some executable we can expect in the test env.
local exe = 'printargs-test'
local exedir = 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 = 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("with weird $PATHEXT", function()
clear({env={PATHEXT=';'}})
eq(0, call('executable', '.\\test_executable_zzz'))
clear({env={PATHEXT=';;;.zzz;;'}})
eq(1, call('executable', '.\\test_executable_zzz'))
end)
it("unqualified filename, Unix-style 'shell'", function()
clear({env={PATHEXT=''}})
command('set shell=sh')
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("relative path, Unix-style 'shell' (backslashes)", function()
clear({env={PATHEXT=''}})
command('set shell=bash.exe')
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)
it('unqualified filename, $PATHEXT contains dot', function()
clear({env={PATHEXT='.;.zzz'}})
for _,ext in ipairs(exts) do
eq(1, call('executable', 'test_executable_'..ext..'.'..ext))
end
eq(1, call('executable', 'test_executable_zzz.zzz'))
clear({env={PATHEXT='.zzz;.'}})
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('relative path, $PATHEXT contains dot (backslashes)', function()
clear({env={PATHEXT='.;.zzz'}})
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)
it('ignores case of extension', function()
clear({env={PATHEXT='.ZZZ'}})
eq(1, call('executable', 'test_executable_zzz.zzz'))
end)
it('relative path does not search $PATH', function()
clear({env={PATHEXT=''}})
eq(0, call('executable', './System32/notepad.exe'))
eq(0, call('executable', '.\\System32\\notepad.exe'))
eq(0, call('executable', '../notepad.exe'))
eq(0, call('executable', '..\\notepad.exe'))
end)
end)

View File

@@ -0,0 +1,337 @@
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: }|
|
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)
it('places cursor correctly #6035', function()
local screen = Screen.new(40, 6)
screen:attach()
source([=[
" test 1: non-silenced output goes as usual
function! Test1()
echo 1234
let x = execute('echon "abcdef"', '')
echon 'ABCD'
endfunction
" test 2: silenced output does not affect ui
function! Test2()
echo 1234
let x = execute('echon "abcdef"', 'silent')
echon 'ABCD'
endfunction
" test 3: silenced! error does not affect ui
function! Test3()
echo 1234
let x = execute('echoerr "abcdef"', 'silent!')
echon 'ABCD'
endfunction
" test 4: silenced echoerr goes as usual
" bug here
function! Test4()
echo 1234
let x = execute('echoerr "abcdef"', 'silent')
echon 'ABCD'
endfunction
" test 5: silenced! echoerr does not affect ui
function! Test5()
echo 1234
let x = execute('echoerr "abcdef"', 'silent!')
echon 'ABCD'
endfunction
" test 6: silenced error goes as usual
function! Test6()
echo 1234
let x = execute('echo undefined', 'silent')
echon 'ABCD'
endfunction
" test 7: existing error does not mess the result
function! Test7()
" display from Test6() is still visible
" why does the "abcdef" goes into a newline
let x = execute('echon "abcdef"', '')
echon 'ABCD'
endfunction
]=])
feed([[:call Test1()<cr>]])
screen:expect([[
^ |
~ |
~ |
~ |
~ |
ABCD |
]])
feed([[:call Test2()<cr>]])
screen:expect([[
^ |
~ |
~ |
~ |
~ |
1234ABCD |
]])
feed([[:call Test3()<cr>]])
screen:expect([[
^ |
~ |
~ |
~ |
~ |
1234ABCD |
]])
feed([[:call Test4()<cr>]])
-- unexpected: need to fix
-- echoerr does not set did_emsg
-- "ef" was overwritten since msg_col was recovered wrongly
screen:expect([[
1234 |
Error detected while processing function|
Test4: |
line 2: |
abcdABCD |
Press ENTER or type command to continue^ |
]])
feed([[<cr>]]) -- to clear screen
feed([[:call Test5()<cr>]])
screen:expect([[
^ |
~ |
~ |
~ |
~ |
1234ABCD |
]])
feed([[:call Test6()<cr>]])
screen:expect([[
|
Error detected while processing function|
Test6: |
line 2: |
E121ABCD |
Press ENTER or type command to continue^ |
]])
feed([[:call Test7()<cr>]])
screen:expect([[
Error detected while processing function|
Test6: |
line 2: |
E121ABCD |
ABCD |
Press ENTER or type command to continue^ |
]])
end)
-- This deviates from vim behavior, but is consistent
-- with how nvim currently displays the output.
it('captures 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):E897: List or Blob required', ret)
ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "")')
eq('Vim(echo):E897: List or Blob required', ret)
ret = exc_exec('call execute("echo add(1, 1)", "silent")')
eq('Vim(echo):E897: List or Blob required', ret)
ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "silent")')
eq('Vim(echo):E897: List or Blob required', ret)
end)
end)
end)

View File

@@ -0,0 +1,40 @@
local helpers = require('test.functional.helpers')(after_each)
local eq, clear, call, iswin =
helpers.eq, helpers.clear, helpers.call, helpers.iswin
local command = helpers.command
local exc_exec = helpers.exc_exec
local matches = helpers.matches
describe('exepath()', function()
before_each(clear)
it('returns 1 for commands in $PATH', function()
local exe = iswin() and 'ping' or 'ls'
local ext_pat = iswin() and '%.EXE$' or '$'
matches(exe .. ext_pat, call('exepath', exe))
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
ext_pat = iswin() and '%.CMD$' or '$'
matches('null' .. ext_pat, call('exepath', 'null'))
matches('true' .. ext_pat, call('exepath', 'true'))
matches('false' .. ext_pat, call('exepath', 'false'))
end)
it('fails for invalid values', function()
for _, input in ipairs({'""', 'v:null', 'v:true', 'v:false', '{}', '[]'}) do
eq('Vim(call):E928: String required', exc_exec('call exepath('..input..')'))
end
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do
eq('Vim(call):E928: String required', exc_exec('call exepath('..input..')'))
end
end)
if iswin() then
it('append extension if omitted', function()
local filename = 'cmd'
local pathext = '.exe'
clear({env={PATHEXT=pathext}})
eq(call('exepath', filename..pathext), call('exepath', filename))
end)
end
end)

View File

@@ -0,0 +1,156 @@
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 getcwd = helpers.funcs.getcwd
local command = helpers.command
local write_file = helpers.write_file
local alter_slashes = helpers.alter_slashes
local function eq_slashconvert(expected, got)
eq(alter_slashes(expected), alter_slashes(got))
end
describe('fnamemodify()', function()
setup(function()
write_file('Xtest-fnamemodify.txt', [[foobar]])
end)
before_each(clear)
teardown(function()
os.remove('Xtest-fnamemodify.txt')
end)
it('handles the root path', 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)
it('handles examples from ":help filename-modifiers"', function()
local filename = "src/version.c"
local cwd = getcwd()
eq_slashconvert(cwd .. '/src/version.c', fnamemodify(filename, ':p'))
eq_slashconvert('src/version.c', fnamemodify(filename, ':p:.'))
eq_slashconvert(cwd .. '/src', fnamemodify(filename, ':p:h'))
eq_slashconvert(cwd .. '', fnamemodify(filename, ':p:h:h'))
eq('version.c', fnamemodify(filename, ':p:t'))
eq_slashconvert(cwd .. '/src/version', fnamemodify(filename, ':p:r'))
eq_slashconvert(cwd .. '/src/main.c', fnamemodify(filename, ':s?version?main?:p'))
local converted_cwd = cwd:gsub('/', '\\')
eq(converted_cwd .. '\\src\\version.c', fnamemodify(filename, ':p:gs?/?\\\\?'))
eq('src', fnamemodify(filename, ':h'))
eq('version.c', fnamemodify(filename, ':t'))
eq_slashconvert('src/version', fnamemodify(filename, ':r'))
eq('version', fnamemodify(filename, ':t:r'))
eq('c', fnamemodify(filename, ':e'))
eq_slashconvert('src/main.c', fnamemodify(filename, ':s?version?main?'))
end)
it('handles advanced examples from ":help filename-modifiers"', function()
local filename = "src/version.c.gz"
eq('gz', fnamemodify(filename, ':e'))
eq('c.gz', fnamemodify(filename, ':e:e'))
eq('c.gz', fnamemodify(filename, ':e:e:e'))
eq('c', fnamemodify(filename, ':e:e:r'))
eq_slashconvert('src/version.c', fnamemodify(filename, ':r'))
eq('c', fnamemodify(filename, ':r:e'))
eq_slashconvert('src/version', fnamemodify(filename, ':r:r'))
eq_slashconvert('src/version', fnamemodify(filename, ':r:r:r'))
end)
it('handles :h', function()
eq('.', fnamemodify('hello.txt', ':h'))
eq_slashconvert('path/to', fnamemodify('path/to/hello.txt', ':h'))
end)
it('handles :t', function()
eq('hello.txt', fnamemodify('hello.txt', ':t'))
eq_slashconvert('hello.txt', fnamemodify('path/to/hello.txt', ':t'))
end)
it('handles :r', function()
eq('hello', fnamemodify('hello.txt', ':r'))
eq_slashconvert('path/to/hello', fnamemodify('path/to/hello.txt', ':r'))
end)
it('handles :e', function()
eq('txt', fnamemodify('hello.txt', ':e'))
eq_slashconvert('txt', fnamemodify('path/to/hello.txt', ':e'))
end)
it('handles regex replacements', function()
eq('content-there-here.txt', fnamemodify('content-here-here.txt', ':s/here/there/'))
eq('content-there-there.txt', fnamemodify('content-here-here.txt', ':gs/here/there/'))
end)
it('handles shell escape', function()
local expected
if iswin() then
-- we expand with double-quotes on Windows
expected = [["hello there! quote ' newline]] .. '\n' .. [["]]
else
expected = [['hello there! quote '\'' newline]] .. '\n' .. [[']]
end
eq(expected, fnamemodify("hello there! quote ' newline\n", ':S'))
end)
it('can combine :e and :r', function()
-- simple, single extension filename
eq('c', fnamemodify('a.c', ':e'))
eq('c', fnamemodify('a.c', ':e:e'))
eq('c', fnamemodify('a.c', ':e:e:r'))
eq('c', fnamemodify('a.c', ':e:e:r:r'))
-- multi extension filename
eq('rb', fnamemodify('a.spec.rb', ':e:r'))
eq('rb', fnamemodify('a.spec.rb', ':e:r:r'))
eq('spec', fnamemodify('a.spec.rb', ':e:e:r'))
eq('spec', fnamemodify('a.spec.rb', ':e:e:r:r'))
eq('spec', fnamemodify('a.b.spec.rb', ':e:e:r'))
eq('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r'))
eq('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r'))
eq('spec', fnamemodify('a.b.spec.rb', ':r:e'))
eq('b', fnamemodify('a.b.spec.rb', ':r:r:e'))
-- extraneous :e expansions
eq('c', fnamemodify('a.b.c.d.e', ':r:r:e'))
eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e'))
-- :e never includes the whole filename, so "a.b":e:e:e --> "b"
eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
end)
end)

View File

@@ -0,0 +1,20 @@
-- Tests for misc Vimscript |functions|.
--
-- If a function is non-trivial, consider moving its spec to:
-- test/functional/vimscript/<funcname>_spec.lua
--
-- Core "eval" tests live in eval_spec.lua.
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eval = helpers.eval
local iswin = helpers.iswin
local matches = helpers.matches
before_each(clear)
it('windowsversion()', function()
clear()
matches(iswin() and '^%d+%.%d+$' or '^$', eval('windowsversion()'))
end)

View File

@@ -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)

View File

@@ -0,0 +1,28 @@
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
before_each(function()
clear()
lfs.mkdir('test-glob')
-- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
command('silent cd test-glob')
end)
after_each(function()
lfs.rmdir('test-glob')
end)
describe('glob()', function()
it("glob('.*') returns . and .. ", function()
eq({'.', '..'}, eval("glob('.*', 0, 1)"))
-- Do it again to verify scandir_next_with_dots() internal state.
eq({'.', '..'}, eval("glob('.*', 0, 1)"))
end)
it("glob('*') returns an empty list ", function()
eq({}, eval("glob('*', 0, 1)"))
-- Do it again to verify scandir_next_with_dots() internal state.
eq({}, eval("glob('*', 0, 1)"))
end)
end)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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] |\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)

View File

@@ -0,0 +1,795 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local funcs = helpers.funcs
local meths = helpers.meths
local eq = helpers.eq
local eval = helpers.eval
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(...)
source([[
language C
function Eq(exp, act)
let act = a:act
let exp = a:exp
if type(exp) != type(act)
return 0
endif
if type(exp) == type({})
if sort(keys(exp)) !=# sort(keys(act))
return 0
endif
if sort(keys(exp)) ==# ['_TYPE', '_VAL']
let exp_typ = v:msgpack_types[exp._TYPE]
let act_typ = act._TYPE
if exp_typ isnot act_typ
return 0
endif
return Eq(exp._VAL, act._VAL)
else
return empty(filter(copy(exp), '!Eq(v:val, act[v:key])'))
endif
else
if type(exp) == type([])
if len(exp) != len(act)
return 0
endif
return empty(filter(copy(exp), '!Eq(v:val, act[v:key])'))
endif
return exp ==# act
endif
return 1
endfunction
function EvalEq(exp, act_expr)
let act = eval(a:act_expr)
if Eq(a:exp, act)
return 1
else
return string(act)
endif
endfunction
]])
end
before_each(restart)
local speq = function(expected, actual_expr)
eq(1, funcs.EvalEq(expected, actual_expr))
end
it('accepts readfile()-style list', function()
eq({Test=1}, funcs.json_decode({
'{',
'\t"Test": 1',
'}',
}))
end)
it('accepts strings with newlines', function()
eq({Test=1}, funcs.json_decode([[
{
"Test": 1
}
]]))
end)
it('parses null, true, false', function()
eq(NIL, funcs.json_decode('null'))
eq(true, funcs.json_decode('true'))
eq(false, funcs.json_decode('false'))
end)
it('fails to parse incomplete null, true, false', function()
eq('Vim(call):E474: Expected null: n',
exc_exec('call json_decode("n")'))
eq('Vim(call):E474: Expected null: nu',
exc_exec('call json_decode("nu")'))
eq('Vim(call):E474: Expected null: nul',
exc_exec('call json_decode("nul")'))
eq('Vim(call):E474: Expected null: nul\n\t',
exc_exec('call json_decode("nul\\n\\t")'))
eq('Vim(call):E474: Expected true: t',
exc_exec('call json_decode("t")'))
eq('Vim(call):E474: Expected true: tr',
exc_exec('call json_decode("tr")'))
eq('Vim(call):E474: Expected true: tru',
exc_exec('call json_decode("tru")'))
eq('Vim(call):E474: Expected true: tru\t\n',
exc_exec('call json_decode("tru\\t\\n")'))
eq('Vim(call):E474: Expected false: f',
exc_exec('call json_decode("f")'))
eq('Vim(call):E474: Expected false: fa',
exc_exec('call json_decode("fa")'))
eq('Vim(call):E474: Expected false: fal',
exc_exec('call json_decode("fal")'))
eq('Vim(call):E474: Expected false: fal <',
exc_exec('call json_decode(" fal <")'))
eq('Vim(call):E474: Expected false: fals',
exc_exec('call json_decode("fals")'))
end)
it('parses integer numbers', function()
eq(100000, funcs.json_decode('100000'))
eq(-100000, funcs.json_decode('-100000'))
eq(100000, funcs.json_decode(' 100000 '))
eq(-100000, funcs.json_decode(' -100000 '))
eq(0, funcs.json_decode('0'))
eq(0, funcs.json_decode('-0'))
end)
it('fails to parse +numbers and .number', function()
eq('Vim(call):E474: Unidentified byte: +1000',
exc_exec('call json_decode("+1000")'))
eq('Vim(call):E474: Unidentified byte: .1000',
exc_exec('call json_decode(".1000")'))
end)
it('fails to parse numbers with leading zeroes', function()
eq('Vim(call):E474: Leading zeroes are not allowed: 00.1',
exc_exec('call json_decode("00.1")'))
eq('Vim(call):E474: Leading zeroes are not allowed: 01',
exc_exec('call json_decode("01")'))
eq('Vim(call):E474: Leading zeroes are not allowed: -01',
exc_exec('call json_decode("-01")'))
eq('Vim(call):E474: Leading zeroes are not allowed: -001.0',
exc_exec('call json_decode("-001.0")'))
end)
it('fails to parse incomplete numbers', function()
eq('Vim(call):E474: Missing number after minus sign: -.1',
exc_exec('call json_decode("-.1")'))
eq('Vim(call):E474: Missing number after minus sign: -',
exc_exec('call json_decode("-")'))
eq('Vim(call):E474: Missing number after decimal dot: -1.',
exc_exec('call json_decode("-1.")'))
eq('Vim(call):E474: Missing number after decimal dot: 0.',
exc_exec('call json_decode("0.")'))
eq('Vim(call):E474: Missing exponent: 0.0e',
exc_exec('call json_decode("0.0e")'))
eq('Vim(call):E474: Missing exponent: 0.0e+',
exc_exec('call json_decode("0.0e+")'))
eq('Vim(call):E474: Missing exponent: 0.0e-',
exc_exec('call json_decode("0.0e-")'))
eq('Vim(call):E474: Missing exponent: 0.0e-',
exc_exec('call json_decode("0.0e-")'))
eq('Vim(call):E474: Missing number after decimal dot: 1.e5',
exc_exec('call json_decode("1.e5")'))
eq('Vim(call):E474: Missing number after decimal dot: 1.e+5',
exc_exec('call json_decode("1.e+5")'))
eq('Vim(call):E474: Missing number after decimal dot: 1.e+',
exc_exec('call json_decode("1.e+")'))
end)
it('parses floating-point numbers', function()
eq('100000.0', eval('string(json_decode("100000.0"))'))
eq(100000.5, funcs.json_decode('100000.5'))
eq(-100000.5, funcs.json_decode('-100000.5'))
eq(-100000.5e50, funcs.json_decode('-100000.5e50'))
eq(100000.5e50, funcs.json_decode('100000.5e50'))
eq(100000.5e50, funcs.json_decode('100000.5e+50'))
eq(-100000.5e-50, funcs.json_decode('-100000.5e-50'))
eq(100000.5e-50, funcs.json_decode('100000.5e-50'))
eq(100000e-50, funcs.json_decode('100000e-50'))
eq(0.5, funcs.json_decode('0.5'))
eq(0.005, funcs.json_decode('0.005'))
eq(0.005, funcs.json_decode('0.00500'))
eq(0.5, funcs.json_decode('0.00500e+002'))
eq(0.00005, funcs.json_decode('0.00500e-002'))
eq(-0.0, funcs.json_decode('-0.0'))
eq(-0.0, funcs.json_decode('-0.0e0'))
eq(-0.0, funcs.json_decode('-0.0e+0'))
eq(-0.0, funcs.json_decode('-0.0e-0'))
eq(-0.0, funcs.json_decode('-0e-0'))
eq(-0.0, funcs.json_decode('-0e-2'))
eq(-0.0, funcs.json_decode('-0e+2'))
eq(0.0, funcs.json_decode('0.0'))
eq(0.0, funcs.json_decode('0.0e0'))
eq(0.0, funcs.json_decode('0.0e+0'))
eq(0.0, funcs.json_decode('0.0e-0'))
eq(0.0, funcs.json_decode('0e-0'))
eq(0.0, funcs.json_decode('0e-2'))
eq(0.0, funcs.json_decode('0e+2'))
end)
it('fails to parse numbers with spaces inside', function()
eq('Vim(call):E474: Missing number after minus sign: - 1000',
exc_exec('call json_decode("- 1000")'))
eq('Vim(call):E474: Missing number after decimal dot: 0. ',
exc_exec('call json_decode("0. ")'))
eq('Vim(call):E474: Missing number after decimal dot: 0. 0',
exc_exec('call json_decode("0. 0")'))
eq('Vim(call):E474: Missing exponent: 0.0e 1',
exc_exec('call json_decode("0.0e 1")'))
eq('Vim(call):E474: Missing exponent: 0.0e+ 1',
exc_exec('call json_decode("0.0e+ 1")'))
eq('Vim(call):E474: Missing exponent: 0.0e- 1',
exc_exec('call json_decode("0.0e- 1")'))
end)
it('fails to parse "," and ":"', function()
eq('Vim(call):E474: Comma not inside container: , ',
exc_exec('call json_decode(" , ")'))
eq('Vim(call):E474: Colon not inside container: : ',
exc_exec('call json_decode(" : ")'))
end)
it('parses empty containers', function()
eq({}, funcs.json_decode('[]'))
eq('[]', eval('string(json_decode("[]"))'))
end)
it('fails to parse "[" and "{"', function()
eq('Vim(call):E474: Unexpected end of input: {',
exc_exec('call json_decode("{")'))
eq('Vim(call):E474: Unexpected end of input: [',
exc_exec('call json_decode("[")'))
end)
it('fails to parse "}" and "]"', function()
eq('Vim(call):E474: No container to close: ]',
exc_exec('call json_decode("]")'))
eq('Vim(call):E474: No container to close: }',
exc_exec('call json_decode("}")'))
end)
it('fails to parse containers which are closed by different brackets',
function()
eq('Vim(call):E474: Closing dictionary with square bracket: ]',
exc_exec('call json_decode("{]")'))
eq('Vim(call):E474: Closing list with curly bracket: }',
exc_exec('call json_decode("[}")'))
end)
it('fails to parse concat inside container', function()
eq('Vim(call):E474: Expected comma before list item: []]',
exc_exec('call json_decode("[[][]]")'))
eq('Vim(call):E474: Expected comma before list item: {}]',
exc_exec('call json_decode("[{}{}]")'))
eq('Vim(call):E474: Expected comma before list item: ]',
exc_exec('call json_decode("[1 2]")'))
eq('Vim(call):E474: Expected comma before dictionary key: ": 4}',
exc_exec('call json_decode("{\\"1\\": 2 \\"3\\": 4}")'))
eq('Vim(call):E474: Expected colon before dictionary value: , "3" 4}',
exc_exec('call json_decode("{\\"1\\" 2, \\"3\\" 4}")'))
end)
it('fails to parse containers with leading comma or colon', function()
eq('Vim(call):E474: Leading comma: ,}',
exc_exec('call json_decode("{,}")'))
eq('Vim(call):E474: Leading comma: ,]',
exc_exec('call json_decode("[,]")'))
eq('Vim(call):E474: Using colon not in dictionary: :]',
exc_exec('call json_decode("[:]")'))
eq('Vim(call):E474: Unexpected colon: :}',
exc_exec('call json_decode("{:}")'))
end)
it('fails to parse containers with trailing comma', function()
eq('Vim(call):E474: Trailing comma: ]',
exc_exec('call json_decode("[1,]")'))
eq('Vim(call):E474: Trailing comma: }',
exc_exec('call json_decode("{\\"1\\": 2,}")'))
end)
it('fails to parse dictionaries with missing value', function()
eq('Vim(call):E474: Expected value after colon: }',
exc_exec('call json_decode("{\\"1\\":}")'))
eq('Vim(call):E474: Expected value: }',
exc_exec('call json_decode("{\\"1\\"}")'))
end)
it('fails to parse containers with two commas or colons', function()
eq('Vim(call):E474: Duplicate comma: , "2": 2}',
exc_exec('call json_decode("{\\"1\\": 1,, \\"2\\": 2}")'))
eq('Vim(call):E474: Duplicate comma: , "2", 2]',
exc_exec('call json_decode("[\\"1\\", 1,, \\"2\\", 2]")'))
eq('Vim(call):E474: Duplicate colon: : 2}',
exc_exec('call json_decode("{\\"1\\": 1, \\"2\\":: 2}")'))
eq('Vim(call):E474: Comma after colon: , 2}',
exc_exec('call json_decode("{\\"1\\": 1, \\"2\\":, 2}")'))
eq('Vim(call):E474: Unexpected colon: : "2": 2}',
exc_exec('call json_decode("{\\"1\\": 1,: \\"2\\": 2}")'))
eq('Vim(call):E474: Unexpected colon: :, "2": 2}',
exc_exec('call json_decode("{\\"1\\": 1:, \\"2\\": 2}")'))
end)
it('fails to parse concat of two values', function()
eq('Vim(call):E474: Trailing characters: []',
exc_exec('call json_decode("{}[]")'))
end)
it('parses containers', function()
eq({1}, funcs.json_decode('[1]'))
eq({NIL, 1}, funcs.json_decode('[null, 1]'))
eq({['1']=2}, funcs.json_decode('{"1": 2}'))
eq({['1']=2, ['3']={{['4']={['5']={{}, 1}}}}},
funcs.json_decode('{"1": 2, "3": [{"4": {"5": [[], 1]}}]}'))
end)
it('fails to parse incomplete strings', function()
eq('Vim(call):E474: Expected string end: \t"',
exc_exec('call json_decode("\\t\\"")'))
eq('Vim(call):E474: Expected string end: \t"abc',
exc_exec('call json_decode("\\t\\"abc")'))
eq('Vim(call):E474: Unfinished escape sequence: \t"abc\\',
exc_exec('call json_decode("\\t\\"abc\\\\")'))
eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u',
exc_exec('call json_decode("\\t\\"abc\\\\u")'))
eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u0',
exc_exec('call json_decode("\\t\\"abc\\\\u0")'))
eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u00',
exc_exec('call json_decode("\\t\\"abc\\\\u00")'))
eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u000',
exc_exec('call json_decode("\\t\\"abc\\\\u000")'))
eq('Vim(call):E474: Expected four hex digits after \\u: \\u" ',
exc_exec('call json_decode("\\t\\"abc\\\\u\\" ")'))
eq('Vim(call):E474: Expected four hex digits after \\u: \\u0" ',
exc_exec('call json_decode("\\t\\"abc\\\\u0\\" ")'))
eq('Vim(call):E474: Expected four hex digits after \\u: \\u00" ',
exc_exec('call json_decode("\\t\\"abc\\\\u00\\" ")'))
eq('Vim(call):E474: Expected four hex digits after \\u: \\u000" ',
exc_exec('call json_decode("\\t\\"abc\\\\u000\\" ")'))
eq('Vim(call):E474: Expected string end: \t"abc\\u0000',
exc_exec('call json_decode("\\t\\"abc\\\\u0000")'))
end)
it('fails to parse unknown escape sequnces', function()
eq('Vim(call):E474: Unknown escape sequence: \\a"',
exc_exec('call json_decode("\\t\\"\\\\a\\"")'))
end)
it('parses strings properly', function()
eq('\n', funcs.json_decode('"\\n"'))
eq('', funcs.json_decode('""'))
eq('\\/"\t\b\n\r\f', funcs.json_decode([["\\\/\"\t\b\n\r\f"]]))
eq('/a', funcs.json_decode([["\/a"]]))
-- Unicode characters: 2-byte, 3-byte, 4-byte
eq({
'«',
'',
'\240\144\128\128',
}, funcs.json_decode({
'[',
'"«",',
'"ફ",',
'"\240\144\128\128"',
']',
}))
end)
it('fails on strings with invalid bytes', function()
eq('Vim(call):E474: Only UTF-8 strings allowed: \255"',
exc_exec('call json_decode("\\t\\"\\xFF\\"")'))
eq('Vim(call):E474: ASCII control characters cannot be present inside string: ',
exc_exec('call json_decode(["\\"\\n\\""])'))
-- 0xC2 starts 2-byte unicode character
eq('Vim(call):E474: Only UTF-8 strings allowed: \194"',
exc_exec('call json_decode("\\t\\"\\xC2\\"")'))
-- 0xE0 0xAA starts 3-byte unicode character
eq('Vim(call):E474: Only UTF-8 strings allowed: \224"',
exc_exec('call json_decode("\\t\\"\\xE0\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \224\170"',
exc_exec('call json_decode("\\t\\"\\xE0\\xAA\\"")'))
-- 0xF0 0x90 0x80 starts 4-byte unicode character
eq('Vim(call):E474: Only UTF-8 strings allowed: \240"',
exc_exec('call json_decode("\\t\\"\\xF0\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \240\144"',
exc_exec('call json_decode("\\t\\"\\xF0\\x90\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \240\144\128"',
exc_exec('call json_decode("\\t\\"\\xF0\\x90\\x80\\"")'))
-- 0xF9 0x80 0x80 0x80 starts 5-byte unicode character
eq('Vim(call):E474: Only UTF-8 strings allowed: \249"',
exc_exec('call json_decode("\\t\\"\\xF9\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \249\128"',
exc_exec('call json_decode("\\t\\"\\xF9\\x80\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \249\128\128"',
exc_exec('call json_decode("\\t\\"\\xF9\\x80\\x80\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \249\128\128\128"',
exc_exec('call json_decode("\\t\\"\\xF9\\x80\\x80\\x80\\"")'))
-- 0xFC 0x90 0x80 0x80 0x80 starts 6-byte unicode character
eq('Vim(call):E474: Only UTF-8 strings allowed: \252"',
exc_exec('call json_decode("\\t\\"\\xFC\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144"',
exc_exec('call json_decode("\\t\\"\\xFC\\x90\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144\128"',
exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144\128\128"',
exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\x80\\"")'))
eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144\128\128\128"',
exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\x80\\x80\\"")'))
-- Specification does not allow unquoted characters above 0x10FFFF
eq('Vim(call):E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: \249\128\128\128\128"',
exc_exec('call json_decode("\\t\\"\\xF9\\x80\\x80\\x80\\x80\\"")'))
eq('Vim(call):E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: \252\144\128\128\128\128"',
exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\x80\\x80\\x80\\"")'))
-- '"\249\128\128\128\128"',
-- '"\252\144\128\128\128\128"',
end)
it('parses surrogate pairs properly', function()
eq('\240\144\128\128', funcs.json_decode('"\\uD800\\uDC00"'))
eq('\237\160\128a\237\176\128', funcs.json_decode('"\\uD800a\\uDC00"'))
eq('\237\160\128\t\237\176\128', funcs.json_decode('"\\uD800\\t\\uDC00"'))
eq('\237\160\128', funcs.json_decode('"\\uD800"'))
eq('\237\160\128a', funcs.json_decode('"\\uD800a"'))
eq('\237\160\128\t', funcs.json_decode('"\\uD800\\t"'))
eq('\237\176\128', funcs.json_decode('"\\uDC00"'))
eq('\237\176\128a', funcs.json_decode('"\\uDC00a"'))
eq('\237\176\128\t', funcs.json_decode('"\\uDC00\\t"'))
eq('\237\176\128', funcs.json_decode('"\\uDC00"'))
eq('a\237\176\128', funcs.json_decode('"a\\uDC00"'))
eq('\t\237\176\128', funcs.json_decode('"\\t\\uDC00"'))
eq('\237\160\128¬', funcs.json_decode('"\\uD800\\u00AC"'))
eq('\237\160\128\237\160\128', funcs.json_decode('"\\uD800\\uD800"'))
end)
local sp_decode_eq = function(expected, json)
meths.set_var('__json', json)
speq(expected, 'json_decode(g:__json)')
command('unlet! g:__json')
end
it('parses strings with NUL properly', function()
sp_decode_eq({_TYPE='string', _VAL={'\n'}}, '"\\u0000"')
sp_decode_eq({_TYPE='string', _VAL={'\n', '\n'}}, '"\\u0000\\n\\u0000"')
sp_decode_eq({_TYPE='string', _VAL={'\n«\n'}}, '"\\u0000\\u00AB\\u0000"')
end)
it('parses dictionaries with duplicate keys to special maps', function()
sp_decode_eq({_TYPE='map', _VAL={{'a', 1}, {'a', 2}}},
'{"a": 1, "a": 2}')
sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'a', 2}}},
'{"b": 3, "a": 1, "a": 2}')
sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}}},
'{"b": 3, "a": 1, "c": 4, "a": 2}')
sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}},
'{"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}')
sp_decode_eq({{_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}},
'[{"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}]')
sp_decode_eq({{d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}},
'[{"d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]')
sp_decode_eq({1, {d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}},
'[1, {"d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]')
sp_decode_eq({1, {a={}, d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}},
'[1, {"a": [], "d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]')
end)
it('parses dictionaries with empty keys to special maps', function()
sp_decode_eq({_TYPE='map', _VAL={{'', 4}}},
'{"": 4}')
sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}},
'{"b": 3, "a": 1, "c": 4, "d": 2, "": 4}')
sp_decode_eq({_TYPE='map', _VAL={{'', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}},
'{"": 3, "a": 1, "c": 4, "d": 2, "": 4}')
sp_decode_eq({{_TYPE='map', _VAL={{'', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}}},
'[{"": 3, "a": 1, "c": 4, "d": 2, "": 4}]')
end)
it('parses dictionaries with keys with NUL bytes to special maps', function()
sp_decode_eq({_TYPE='map', _VAL={{{_TYPE='string', _VAL={'a\n', 'b'}}, 4}}},
'{"a\\u0000\\nb": 4}')
sp_decode_eq({_TYPE='map', _VAL={{{_TYPE='string', _VAL={'a\n', 'b', ''}}, 4}}},
'{"a\\u0000\\nb\\n": 4}')
sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {{_TYPE='string', _VAL={'\n'}}, 4}}},
'{"b": 3, "a": 1, "c": 4, "d": 2, "\\u0000": 4}')
end)
it('parses U+00C3 correctly', function()
eq('\195\131', funcs.json_decode('"\195\131"'))
end)
it('fails to parse empty string', function()
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode("")'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode([])'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode([""])'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode(" ")'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode("\\t")'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode("\\n")'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode(" \\t\\n \\n\\t\\t \\n\\t\\n \\n \\t\\n\\t ")'))
end)
it('accepts all spaces in every position where space may be put', function()
local s = ' \t\n\r \t\r\n \n\t\r \n\r\t \r\t\n \r\n\t\t \n\r\t \r\n\t\n \r\t\n\r \t\r \n\t\r\n \n \t\r\n \r\t\n\t \r\n\t\r \n\r \t\n\r\t \r \t\n\r \n\t\r\t \n\r\t\n \r\n \t\r\n\t'
local str = ('%s{%s"key"%s:%s[%s"val"%s,%s"val2"%s]%s,%s"key2"%s:%s1%s}%s'):gsub('%%s', s)
eq({key={'val', 'val2'}, key2=1}, funcs.json_decode(str))
end)
it('does not overflow when writing error message about decoding ["", ""]',
function()
eq('\nE474: Attempt to decode a blank string'
.. '\nE474: Failed to parse \n',
redir_exec('call json_decode(["", ""])'))
end)
end)
describe('json_encode() function', function()
before_each(function()
clear()
command('language C')
end)
it('dumps strings', function()
eq('"Test"', funcs.json_encode('Test'))
eq('""', funcs.json_encode(''))
eq('"\\t"', funcs.json_encode('\t'))
eq('"\\n"', funcs.json_encode('\n'))
eq('"\\u001B"', funcs.json_encode('\27'))
eq('"þÿþ"', funcs.json_encode('þÿþ'))
end)
it('dumps blobs', function()
eq('[]', eval('json_encode(0z)'))
eq('[222, 173, 190, 239]', eval('json_encode(0zDEADBEEF)'))
end)
it('dumps numbers', function()
eq('0', funcs.json_encode(0))
eq('10', funcs.json_encode(10))
eq('-10', funcs.json_encode(-10))
end)
it('dumps floats', function()
eq('0.0', eval('json_encode(0.0)'))
eq('10.5', funcs.json_encode(10.5))
eq('-10.5', funcs.json_encode(-10.5))
eq('-1.0e-5', funcs.json_encode(-1e-5))
eq('1.0e50', eval('json_encode(1.0e50)'))
end)
it('fails to dump NaN and infinite values', function()
eq('Vim(call):E474: Unable to represent NaN value in JSON',
exc_exec('call json_encode(str2float("nan"))'))
eq('Vim(call):E474: Unable to represent infinity in JSON',
exc_exec('call json_encode(str2float("inf"))'))
eq('Vim(call):E474: Unable to represent infinity in JSON',
exc_exec('call json_encode(-str2float("inf"))'))
end)
it('dumps lists', function()
eq('[]', funcs.json_encode({}))
eq('[[]]', funcs.json_encode({{}}))
eq('[[], []]', funcs.json_encode({{}, {}}))
end)
it('dumps dictionaries', function()
eq('{}', eval('json_encode({})'))
eq('{"d": []}', funcs.json_encode({d={}}))
eq('{"d": [], "e": []}', funcs.json_encode({d={}, e={}}))
end)
it('cannot dump generic mapping with generic mapping keys and values',
function()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
eq('false', eval('json_encode(todump)'))
end)
it('can dump special NIL mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
eq('null', eval('json_encode(todump)'))
end)
it('fails to dump a function reference', function()
eq('Vim(call):E474: Error while dumping encode_tv2json() argument, itself: attempt to dump function reference',
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()
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()
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()
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()
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()
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()
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()
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()
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)
it('fails when called with no arguments', function()
eq('Vim(call):E119: Not enough arguments for function: json_encode',
exc_exec('call json_encode()'))
end)
it('fails when called with two arguments', function()
eq('Vim(call):E118: Too many arguments for function: json_encode',
exc_exec('call json_encode(["", ""], 1)'))
end)
it('ignores improper values in &isprint', function()
meths.set_option('isprint', '1')
eq(1, eval('"\1" =~# "\\\\p"'))
eq('"\\u0001"', funcs.json_encode('\1'))
end)
it('fails when using surrogate character in a UTF-8 string', function()
eq('Vim(call):E474: UTF-8 string contains code point which belongs to a surrogate pair: \237\160\128',
exc_exec('call json_encode("\237\160\128")'))
eq('Vim(call):E474: UTF-8 string contains code point which belongs to a surrogate pair: \237\175\191',
exc_exec('call json_encode("\237\175\191")'))
end)
it('dumps control characters as expected', function()
eq([["\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000B\f\r\u000E\u000F\u0010\u0011\u0012\u0013"]],
eval('json_encode({"_TYPE": v:msgpack_types.string, "_VAL": ["\n\1\2\3\4\5\6\7\8\9", "\11\12\13\14\15\16\17\18\19"]})'))
end)
it('can dump NULL string', function()
eq('""', eval('json_encode($XXX_UNEXISTENT_VAR_XXX)'))
end)
it('can dump NULL blob', function()
eq('[]', eval('json_encode(v:_null_blob)'))
end)
it('can dump NULL list', function()
eq('[]', eval('json_encode(v:_null_list)'))
end)
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)

View File

@@ -0,0 +1,30 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq
local exc_exec, source = helpers.exc_exec, helpers.source
describe('vimscript', function()
before_each(clear)
it('parses `<SID>` with turkish locale', function()
if exc_exec('lang ctype tr_TR.UTF-8') ~= 0 then
pending("Locale tr_TR.UTF-8 not supported")
return
end
source([[
func! <sid>_dummy_function()
echo 1
endfunc
au VimEnter * call <sid>_dummy_function()
]])
eq(nil, string.find(eval('v:errmsg'), '^E129'))
end)
it('str2float is not affected by locale', function()
if exc_exec('lang ctype sv_SE.UTF-8') ~= 0 then
pending("Locale sv_SE.UTF-8 not supported")
return
end
clear{env={LANG="", LC_NUMERIC="sv_SE.UTF-8"}}
eq(2.2, eval('str2float("2.2")'))
end)
end)

View File

@@ -0,0 +1,93 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local clear = helpers.clear
local command = helpers.command
local eval = helpers.eval
local meths = helpers.meths
local redir_exec = helpers.redir_exec
local source = helpers.source
local nvim_dir = helpers.nvim_dir
before_each(clear)
describe(':let', 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)
it("multibyte env var #8398 #9267", function()
command("let $NVIM_TEST = 'AìaB'")
eq('AìaB', eval('$NVIM_TEST'))
command("let $NVIM_TEST = 'AaあB'")
eq('AaあB', eval('$NVIM_TEST'))
local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]]
command("let $NVIM_TEST = '"..mbyte.."'")
eq(mbyte, eval('$NVIM_TEST'))
end)
it("multibyte env var to child process #8398 #9267", function()
local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])"
command("let $NVIM_TEST = 'AìaB'")
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
command("let $NVIM_TEST = 'AaあB'")
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]]
command("let $NVIM_TEST = '"..mbyte.."'")
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
end)
it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function()
source([[
func! s:f()
let l:x = [1]
let g:x = l:
endfunc
for _ in range(2)
call s:f()
endfor
call garbagecollect()
call feedkeys('i', 't')
]])
eq(1, eval('1'))
end)
end)

View File

@@ -0,0 +1,163 @@
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',
script=0,
silent=0,
rhs='bar',
expr=0,
sid=0,
buffer=0,
nowait=0,
mode='n',
noremap=1,
lnum=0,
}
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,
script=0,
sid = 0,
silent = 0,
lnum = 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)

View File

@@ -0,0 +1,157 @@
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
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('does not fail if highlight group is not defined', function()
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, 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)
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)

View File

@@ -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

View File

@@ -0,0 +1,19 @@
local helpers = require('test.functional.helpers')(after_each)
local assert_alive = helpers.assert_alive
local clear, command, write_file = helpers.clear, helpers.command, helpers.write_file
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)
assert_alive()
end)
end)

View File

@@ -0,0 +1,755 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local funcs = helpers.funcs
local eval, eq = helpers.eval, helpers.eq
local command = helpers.command
local nvim = helpers.nvim
local exc_exec = helpers.exc_exec
describe('msgpack*() functions', function()
before_each(clear)
local obj_test = function(msg, obj)
it(msg, function()
nvim('set_var', 'obj', obj)
eq(obj, eval('msgpackparse(msgpackdump(g:obj))'))
eq(obj, eval('msgpackparse(msgpackdump(g:obj, "B"))'))
end)
end
-- 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
-- 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
-- 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
-- 5 identical shada files.
local big_obj = {
1, 1436711454, 78, {
encoding="utf-8",
max_kbyte=10,
pid=19269,
version="NVIM 0.0.0-alpha+201507121634"
},
8, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
8, 1436711391, 8, { file="" },
4, 1436700940, 30, { 0, "call mkdir('/tmp/tty/tty')" },
4, 1436701355, 35, { 0, "call mkdir('/tmp/tty/tty', 'p')" },
4, 1436701368, 24, { 0, "call mkdir('/', 'p')" },
4, 1436701375, 26, { 0, "call mkdir('/tty/tty')" },
4, 1436701383, 30, { 0, "call mkdir('/tty/tty/tty')" },
4, 1436701407, 35, { 0, "call mkdir('/usr/tty/tty', 'p')" },
4, 1436701666, 35, { 0, "call mkdir('/tty/tty/tty', 'p')" },
4, 1436708101, 25, { 0, "echo msgpackdump([1])" },
4, 1436708966, 6, { 0, "cq" },
4, 1436709606, 25, { 0, "echo msgpackdump([5])" },
4, 1436709610, 26, { 0, "echo msgpackdump([10])" },
4, 1436709615, 31, { 0, "echo msgpackdump([5, 5, 5])" },
4, 1436709618, 35, { 0, "echo msgpackdump([5, 5, 5, 10])" },
4, 1436709634, 57, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1}]])"
},
4, 1436709651, 67, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}]])"
},
4, 1436709660, 70, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}], 0])"
},
4, 1436710095, 29, { 0, "echo msgpackparse([\"\\n\"])" },
4, 1436710100, 28, { 0, "echo msgpackparse([\"j\"])" },
4, 1436710109, 31, { 0, "echo msgpackparse([\"\", \"\"])" },
4, 1436710424, 33, { 0, "echo msgpackparse([\"\", \"\\n\"])" },
4, 1436710428, 32, { 0, "echo msgpackparse([\"\", \"j\"])" },
4, 1436711142, 14, { 0, "echo mpack" },
4, 1436711196, 45, { 0, "let lengths = map(mpack[:], 'len(v:val)')" },
4, 1436711206, 16, { 0, "echo lengths" },
4, 1436711244, 92, {
0,
("let sum = len(lengths) - 1 | call map(copy(lengths), "
.. "'extend(g:, {\"sum\": sum + v:val})')")
},
4, 1436711245, 12, { 0, "echo sum" },
4, 1436711398, 10, { 0, "echo s" },
4, 1436711404, 41, { 0, "let mpack = readfile('/tmp/foo', 'b')" },
4, 1436711408, 41, { 0, "let shada_objects=msgpackparse(mpack)" },
4, 1436711415, 22, { 0, "echo shada_objects" },
4, 1436711451, 30, { 0, "e ~/.nvim/shada/main.shada" },
4, 1436711454, 6, { 0, "qa" },
4, 1436711442, 9, { 1, "test", 47 },
4, 1436711443, 15, { 1, "aontsuesan", 47 },
2, 1436711443, 38, { hlsearch=1, pat="aontsuesan", smartcase=1 },
2, 0, 31, { islast=0, pat="", smartcase=1, sub=1 },
3, 0, 3, { "" },
10, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
1, 1436711454, 78, {
encoding="utf-8",
max_kbyte=10,
pid=19269,
version="NVIM 0.0.0-alpha+201507121634"
},
8, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
8, 1436711391, 8, { file="" },
4, 1436700940, 30, { 0, "call mkdir('/tmp/tty/tty')" },
4, 1436701355, 35, { 0, "call mkdir('/tmp/tty/tty', 'p')" },
4, 1436701368, 24, { 0, "call mkdir('/', 'p')" },
4, 1436701375, 26, { 0, "call mkdir('/tty/tty')" },
4, 1436701383, 30, { 0, "call mkdir('/tty/tty/tty')" },
4, 1436701407, 35, { 0, "call mkdir('/usr/tty/tty', 'p')" },
4, 1436701666, 35, { 0, "call mkdir('/tty/tty/tty', 'p')" },
4, 1436708101, 25, { 0, "echo msgpackdump([1])" },
4, 1436708966, 6, { 0, "cq" },
4, 1436709606, 25, { 0, "echo msgpackdump([5])" },
4, 1436709610, 26, { 0, "echo msgpackdump([10])" },
4, 1436709615, 31, { 0, "echo msgpackdump([5, 5, 5])" },
4, 1436709618, 35, { 0, "echo msgpackdump([5, 5, 5, 10])" },
4, 1436709634, 57, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1}]])"
},
4, 1436709651, 67, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}]])"
},
4, 1436709660, 70, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}], 0])"
},
4, 1436710095, 29, { 0, "echo msgpackparse([\"\\n\"])" },
4, 1436710100, 28, { 0, "echo msgpackparse([\"j\"])" },
4, 1436710109, 31, { 0, "echo msgpackparse([\"\", \"\"])" },
4, 1436710424, 33, { 0, "echo msgpackparse([\"\", \"\\n\"])" },
4, 1436710428, 32, { 0, "echo msgpackparse([\"\", \"j\"])" },
4, 1436711142, 14, { 0, "echo mpack" },
4, 1436711196, 45, { 0, "let lengths = map(mpack[:], 'len(v:val)')" },
4, 1436711206, 16, { 0, "echo lengths" },
4, 1436711244, 92, {
0,
("let sum = len(lengths) - 1 | call map(copy(lengths), "
.. "'extend(g:, {\"sum\": sum + v:val})')")
},
4, 1436711245, 12, { 0, "echo sum" },
4, 1436711398, 10, { 0, "echo s" },
4, 1436711404, 41, { 0, "let mpack = readfile('/tmp/foo', 'b')" },
4, 1436711408, 41, { 0, "let shada_objects=msgpackparse(mpack)" },
4, 1436711415, 22, { 0, "echo shada_objects" },
4, 1436711451, 30, { 0, "e ~/.nvim/shada/main.shada" },
4, 1436711454, 6, { 0, "qa" },
4, 1436711442, 9, { 1, "test", 47 },
4, 1436711443, 15, { 1, "aontsuesan", 47 },
2, 1436711443, 38, { hlsearch=1, pat="aontsuesan", smartcase=1 },
2, 0, 31, { islast=0, pat="", smartcase=1, sub=1 },
3, 0, 3, { "" },
10, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
1, 1436711454, 78, {
encoding="utf-8",
max_kbyte=10,
pid=19269,
version="NVIM 0.0.0-alpha+201507121634"
},
8, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
8, 1436711391, 8, { file="" },
4, 1436700940, 30, { 0, "call mkdir('/tmp/tty/tty')" },
4, 1436701355, 35, { 0, "call mkdir('/tmp/tty/tty', 'p')" },
4, 1436701368, 24, { 0, "call mkdir('/', 'p')" },
4, 1436701375, 26, { 0, "call mkdir('/tty/tty')" },
4, 1436701383, 30, { 0, "call mkdir('/tty/tty/tty')" },
4, 1436701407, 35, { 0, "call mkdir('/usr/tty/tty', 'p')" },
4, 1436701666, 35, { 0, "call mkdir('/tty/tty/tty', 'p')" },
4, 1436708101, 25, { 0, "echo msgpackdump([1])" },
4, 1436708966, 6, { 0, "cq" },
4, 1436709606, 25, { 0, "echo msgpackdump([5])" },
4, 1436709610, 26, { 0, "echo msgpackdump([10])" },
4, 1436709615, 31, { 0, "echo msgpackdump([5, 5, 5])" },
4, 1436709618, 35, { 0, "echo msgpackdump([5, 5, 5, 10])" },
4, 1436709634, 57, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1}]])"
},
4, 1436709651, 67, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}]])"
},
4, 1436709660, 70, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}], 0])"
},
4, 1436710095, 29, { 0, "echo msgpackparse([\"\\n\"])" },
4, 1436710100, 28, { 0, "echo msgpackparse([\"j\"])" },
4, 1436710109, 31, { 0, "echo msgpackparse([\"\", \"\"])" },
4, 1436710424, 33, { 0, "echo msgpackparse([\"\", \"\\n\"])" },
4, 1436710428, 32, { 0, "echo msgpackparse([\"\", \"j\"])" },
4, 1436711142, 14, { 0, "echo mpack" },
4, 1436711196, 45, { 0, "let lengths = map(mpack[:], 'len(v:val)')" },
4, 1436711206, 16, { 0, "echo lengths" },
4, 1436711244, 92, {
0,
("let sum = len(lengths) - 1 | call map(copy(lengths), "
.. "'extend(g:, {\"sum\": sum + v:val})')")
},
4, 1436711245, 12, { 0, "echo sum" },
4, 1436711398, 10, { 0, "echo s" },
4, 1436711404, 41, { 0, "let mpack = readfile('/tmp/foo', 'b')" },
4, 1436711408, 41, { 0, "let shada_objects=msgpackparse(mpack)" },
4, 1436711415, 22, { 0, "echo shada_objects" },
4, 1436711451, 30, { 0, "e ~/.nvim/shada/main.shada" },
4, 1436711454, 6, { 0, "qa" },
4, 1436711442, 9, { 1, "test", 47 },
4, 1436711443, 15, { 1, "aontsuesan", 47 },
2, 1436711443, 38, { hlsearch=1, pat="aontsuesan", smartcase=1 },
2, 0, 31, { islast=0, pat="", smartcase=1, sub=1 },
3, 0, 3, { "" },
10, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
1, 1436711454, 78, {
encoding="utf-8",
max_kbyte=10,
pid=19269,
version="NVIM 0.0.0-alpha+201507121634"
},
8, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
8, 1436711391, 8, { file="" },
4, 1436700940, 30, { 0, "call mkdir('/tmp/tty/tty')" },
4, 1436701355, 35, { 0, "call mkdir('/tmp/tty/tty', 'p')" },
4, 1436701368, 24, { 0, "call mkdir('/', 'p')" },
4, 1436701375, 26, { 0, "call mkdir('/tty/tty')" },
4, 1436701383, 30, { 0, "call mkdir('/tty/tty/tty')" },
4, 1436701407, 35, { 0, "call mkdir('/usr/tty/tty', 'p')" },
4, 1436701666, 35, { 0, "call mkdir('/tty/tty/tty', 'p')" },
4, 1436708101, 25, { 0, "echo msgpackdump([1])" },
4, 1436708966, 6, { 0, "cq" },
4, 1436709606, 25, { 0, "echo msgpackdump([5])" },
4, 1436709610, 26, { 0, "echo msgpackdump([10])" },
4, 1436709615, 31, { 0, "echo msgpackdump([5, 5, 5])" },
4, 1436709618, 35, { 0, "echo msgpackdump([5, 5, 5, 10])" },
4, 1436709634, 57, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1}]])"
},
4, 1436709651, 67, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}]])"
},
4, 1436709660, 70, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}], 0])"
},
4, 1436710095, 29, { 0, "echo msgpackparse([\"\\n\"])" },
4, 1436710100, 28, { 0, "echo msgpackparse([\"j\"])" },
4, 1436710109, 31, { 0, "echo msgpackparse([\"\", \"\"])" },
4, 1436710424, 33, { 0, "echo msgpackparse([\"\", \"\\n\"])" },
4, 1436710428, 32, { 0, "echo msgpackparse([\"\", \"j\"])" },
4, 1436711142, 14, { 0, "echo mpack" },
4, 1436711196, 45, { 0, "let lengths = map(mpack[:], 'len(v:val)')" },
4, 1436711206, 16, { 0, "echo lengths" },
4, 1436711244, 92, {
0,
("let sum = len(lengths) - 1 | call map(copy(lengths), "
.. "'extend(g:, {\"sum\": sum + v:val})')")
},
4, 1436711245, 12, { 0, "echo sum" },
4, 1436711398, 10, { 0, "echo s" },
4, 1436711404, 41, { 0, "let mpack = readfile('/tmp/foo', 'b')" },
4, 1436711408, 41, { 0, "let shada_objects=msgpackparse(mpack)" },
4, 1436711415, 22, { 0, "echo shada_objects" },
4, 1436711451, 30, { 0, "e ~/.nvim/shada/main.shada" },
4, 1436711454, 6, { 0, "qa" },
4, 1436711442, 9, { 1, "test", 47 },
4, 1436711443, 15, { 1, "aontsuesan", 47 },
2, 1436711443, 38, { hlsearch=1, pat="aontsuesan", smartcase=1 },
2, 0, 31, { islast=0, pat="", smartcase=1, sub=1 },
3, 0, 3, { "" },
10, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
1, 1436711454, 78, {
encoding="utf-8",
max_kbyte=10,
pid=19269,
version="NVIM 0.0.0-alpha+201507121634"
},
8, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" },
8, 1436711391, 8, { file="" },
4, 1436700940, 30, { 0, "call mkdir('/tmp/tty/tty')" },
4, 1436701355, 35, { 0, "call mkdir('/tmp/tty/tty', 'p')" },
4, 1436701368, 24, { 0, "call mkdir('/', 'p')" },
4, 1436701375, 26, { 0, "call mkdir('/tty/tty')" },
4, 1436701383, 30, { 0, "call mkdir('/tty/tty/tty')" },
4, 1436701407, 35, { 0, "call mkdir('/usr/tty/tty', 'p')" },
4, 1436701666, 35, { 0, "call mkdir('/tty/tty/tty', 'p')" },
4, 1436708101, 25, { 0, "echo msgpackdump([1])" },
4, 1436708966, 6, { 0, "cq" },
4, 1436709606, 25, { 0, "echo msgpackdump([5])" },
4, 1436709610, 26, { 0, "echo msgpackdump([10])" },
4, 1436709615, 31, { 0, "echo msgpackdump([5, 5, 5])" },
4, 1436709618, 35, { 0, "echo msgpackdump([5, 5, 5, 10])" },
4, 1436709634, 57, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1}]])"
},
4, 1436709651, 67, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}]])"
},
4, 1436709660, 70, {
0,
"echo msgpackdump([5, 5, 5, 10, [10, 20, {\"abc\": 1, \"def\": 0}], 0])"
},
4, 1436710095, 29, { 0, "echo msgpackparse([\"\\n\"])" },
4, 1436710100, 28, { 0, "echo msgpackparse([\"j\"])" },
4, 1436710109, 31, { 0, "echo msgpackparse([\"\", \"\"])" },
4, 1436710424, 33, { 0, "echo msgpackparse([\"\", \"\\n\"])" },
4, 1436710428, 32, { 0, "echo msgpackparse([\"\", \"j\"])" },
4, 1436711142, 14, { 0, "echo mpack" },
4, 1436711196, 45, { 0, "let lengths = map(mpack[:], 'len(v:val)')" },
4, 1436711206, 16, { 0, "echo lengths" },
4, 1436711244, 92, {
0,
("let sum = len(lengths) - 1 | call map(copy(lengths), "
.. "'extend(g:, {\"sum\": sum + v:val})')")
},
4, 1436711245, 12, { 0, "echo sum" },
4, 1436711398, 10, { 0, "echo s" },
4, 1436711404, 41, { 0, "let mpack = readfile('/tmp/foo', 'b')" },
4, 1436711408, 41, { 0, "let shada_objects=msgpackparse(mpack)" },
4, 1436711415, 22, { 0, "echo shada_objects" },
4, 1436711451, 30, { 0, "e ~/.nvim/shada/main.shada" },
4, 1436711454, 6, { 0, "qa" },
4, 1436711442, 9, { 1, "test", 47 },
4, 1436711443, 15, { 1, "aontsuesan", 47 },
2, 1436711443, 38, { hlsearch=1, pat="aontsuesan", smartcase=1 },
2, 0, 31, { islast=0, pat="", smartcase=1, sub=1 },
3, 0, 3, { "" },
10, 1436711451, 40, { file="/home/zyx/.nvim/shada/main.shada" }
}
obj_test('are able to dump and restore rather big object', big_obj)
obj_test('are able to dump and restore floating-point value', {0.125})
it('can restore and dump UINT64_MAX', function()
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
command('call assert_equal(0xFFFFFFFFFFFFFFFF, parsed[0])')
eq({}, eval('v:errors'))
else
eq({_TYPE={}, _VAL={1, 3, 0x7FFFFFFF, 0x7FFFFFFF}}, eval('parsed[0]'))
end
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump INT64_MIN', function()
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
command('call assert_equal(-0x7fffffffffffffff - 1, parsed[0])')
eq({}, eval('v:errors'))
else
eq({_TYPE={}, _VAL={-1, 2, 0, 0}}, eval('parsed[0]'))
end
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump BIN string with zero byte', function()
command('let dumped = ["\\xC4\\x01\\n"]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq({'\000'}, eval('parsed'))
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump STR string with zero byte', function()
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()
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()
command('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
eq({0.125}, eval('msgpackparse(msgpackdump([todump]))'))
end)
end)
local blobstr = function(list)
local l = {}
for i,v in ipairs(list) do
l[i] = v:gsub('\n', '\000')
end
return table.concat(l, '\n')
end
-- Test msgpackparse() with a readfile()-style list and a blob argument
local parse_eq = function(expect, list_arg)
local blob_expr = '0z' .. blobstr(list_arg):gsub('(.)', function(c)
return ('%.2x'):format(c:byte())
end)
eq(expect, funcs.msgpackparse(list_arg))
command('let g:parsed = msgpackparse(' .. blob_expr .. ')')
eq(expect, eval('g:parsed'))
end
describe('msgpackparse() function', function()
before_each(clear)
it('restores nil as v:null', function()
parse_eq(eval('[v:null]'), {'\192'})
end)
it('restores boolean false as v:false', function()
parse_eq({false}, {'\194'})
end)
it('restores boolean true as v:true', function()
parse_eq({true}, {'\195'})
end)
it('restores FIXSTR as special dict', function()
parse_eq({{_TYPE={}, _VAL={'ab'}}}, {'\162ab'})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.string'))
end)
it('restores BIN 8 as string', function()
parse_eq({'ab'}, {'\196\002ab'})
end)
it('restores FIXEXT1 as special dictionary', function()
parse_eq({{_TYPE={}, _VAL={0x10, {"", ""}}}}, {'\212\016', ''})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.ext'))
end)
it('restores MAP with BIN key as special dictionary', function()
parse_eq({{_TYPE={}, _VAL={{'a', ''}}}}, {'\129\196\001a\196\n'})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
it('restores MAP with duplicate STR keys as special dictionary', function()
command('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]')
-- FIXME Internal error bug, can't use parse_eq() here
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'))
eq(1, eval('g:parsed[0]._VAL[0][0]._TYPE is v:msgpack_types.string'))
eq(1, eval('g:parsed[0]._VAL[1][0]._TYPE is v:msgpack_types.string'))
end)
it('restores MAP with MAP key as special dictionary', function()
parse_eq({{_TYPE={}, _VAL={{{}, ''}}}}, {'\129\128\196\n'})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
it('msgpackparse(systemlist(...)) does not segfault. #3135', function()
local cmd = "sort(keys(msgpackparse(systemlist('"
..helpers.nvim_prog.." --api-info'))[0]))"
eval(cmd)
eval(cmd) -- do it again (try to force segfault)
local api_info = eval(cmd) -- do it again
eq({'error_types', 'functions', 'types',
'ui_events', 'ui_options', 'version'}, api_info)
end)
it('fails when called with no arguments', function()
eq('Vim(call):E119: Not enough arguments for function: msgpackparse',
exc_exec('call msgpackparse()'))
end)
it('fails when called with two arguments', function()
eq('Vim(call):E118: Too many arguments for function: msgpackparse',
exc_exec('call msgpackparse(["", ""], 1)'))
end)
it('fails to parse a string', function()
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse("abcdefghijklmnopqrstuvwxyz")'))
end)
it('fails to parse a number', function()
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(127)'))
end)
it('fails to parse a dictionary', function()
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse({})'))
end)
it('fails to parse a funcref', function()
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(function("tr"))'))
end)
it('fails to parse a partial', function()
command('function T() dict\nendfunction')
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(function("T", [1, 2], {}))'))
end)
it('fails to parse a float', function()
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(0.0)'))
end)
it('fails on incomplete msgpack string', function()
local expected = 'Vim(call):E475: Invalid argument: Incomplete msgpack string'
eq(expected, exc_exec([[call msgpackparse(["\xc4"])]]))
eq(expected, exc_exec([[call msgpackparse(["\xca", "\x02\x03"])]]))
eq(expected, exc_exec('call msgpackparse(0zc4)'))
eq(expected, exc_exec('call msgpackparse(0zca0a0203)'))
end)
it('fails when unable to parse msgpack string', function()
local expected = 'Vim(call):E475: Invalid argument: Failed to parse msgpack string'
eq(expected, exc_exec([[call msgpackparse(["\xc1"])]]))
eq(expected, exc_exec('call msgpackparse(0zc1)'))
end)
end)
describe('msgpackdump() function', function()
before_each(clear)
local dump_eq = function(exp_list, arg_expr)
eq(exp_list, eval('msgpackdump(' .. arg_expr .. ')'))
eq(blobstr(exp_list), eval('msgpackdump(' .. arg_expr .. ', "B")'))
end
it('dumps string as BIN 8', function()
dump_eq({'\196\004Test'}, '["Test"]')
end)
it('dumps blob as BIN 8', function()
dump_eq({'\196\005Bl\nb!'}, '[0z426c006221]')
end)
it('can dump generic mapping with generic mapping keys and values', function()
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])')
dump_eq({'\129\128\128'}, '[todump]')
end)
it('can dump v:true', function()
dump_eq({'\195'}, '[v:true]')
end)
it('can dump v:false', function()
dump_eq({'\194'}, '[v:false]')
end)
it('can dump v:null', function()
dump_eq({'\192'}, '[v:null]')
end)
it('can dump special bool mapping (true)', function()
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
dump_eq({'\195'}, '[todump]')
end)
it('can dump special bool mapping (false)', function()
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
dump_eq({'\194'}, '[todump]')
end)
it('can dump special nil mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
dump_eq({'\192'}, '[todump]')
end)
it('can dump special ext mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
dump_eq({'\212\005', ''}, '[todump]')
end)
it('can dump special array mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
dump_eq({'\146\005\145\196\n'}, '[todump]')
end)
it('can dump special UINT64_MAX mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
dump_eq({'\207\255\255\255\255\255\255\255\255'}, '[todump]')
end)
it('can dump special INT64_MIN mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [-1, 2, 0, 0]')
dump_eq({'\211\128\n\n\n\n\n\n\n'}, '[todump]')
end)
it('fails to dump a function reference', function()
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()
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()
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()
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()
command('let inter = {}')
command('let todump = {"a": inter, "b": inter}')
dump_eq({"\130\161a\128\161b\128"}, '[todump]')
end)
it('can dump list with two same lists inside', function()
command('let inter = []')
command('let todump = [inter, inter]')
dump_eq({"\146\144\144"}, '[todump]')
end)
it('fails to dump a recursive list in a special dict', function()
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()
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()
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()
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()
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()
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)
it('fails when called with no arguments', function()
eq('Vim(call):E119: Not enough arguments for function: msgpackdump',
exc_exec('call msgpackdump()'))
end)
it('fails when called with three arguments', function()
eq('Vim(call):E118: Too many arguments for function: msgpackdump',
exc_exec('call msgpackdump(["", ""], 1, 2)'))
end)
it('fails to dump a string', function()
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump("abcdefghijklmnopqrstuvwxyz")'))
end)
it('fails to dump a number', function()
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump(127)'))
end)
it('fails to dump a dictionary', function()
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump({})'))
end)
it('fails to dump a funcref', function()
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
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)'))
end)
it('fails to dump special value', function()
for _, val in ipairs({'v:true', 'v:false', 'v:null'}) do
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump(' .. val .. ')'))
end
end)
it('can dump NULL string', function()
dump_eq({'\196\n'}, '[$XXX_UNEXISTENT_VAR_XXX]')
dump_eq({'\196\n'}, '[{"_TYPE": v:msgpack_types.binary, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}]')
dump_eq({'\160'}, '[{"_TYPE": v:msgpack_types.string, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}]')
end)
it('can dump NULL blob', function()
eq({'\196\n'}, eval('msgpackdump([v:_null_blob])'))
end)
it('can dump NULL list', function()
eq({'\144'}, eval('msgpackdump([v:_null_list])'))
end)
it('can dump NULL dictionary', function()
eq({'\128'}, eval('msgpackdump([v:_null_dict])'))
end)
end)

View File

@@ -0,0 +1,170 @@
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 = v:_null_string')
command('let V = $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, Dictionary or Blob')
-- Subjectable behaviour
null_expr_test('is equal to empty list', 'L == []', 0, 1)
null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 1)
-- Correct behaviour
null_expr_test('can be indexed with error message for empty list', 'L[0]',
'E684: list index out of range: 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', 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 line() with window id', 'line(L, 1000)', 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 the mouse (q or 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)
null_expr_test('does not crash execute()', 'execute(L)', 0, '')
end)
describe('dict', function()
it('does not crash when indexing NULL dict', function()
eq('\nE716: Key not present in Dictionary: "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)
describe('string', function()
null_test('does not crash :echomsg', 'echomsg S', 0)
null_test('does not crash :execute', 'execute S', 0)
null_expr_test('does not crash execute()', 'execute(S)', 0, '')
null_expr_test('makes executable() error out', 'executable(S)', 'E928: String required', 0)
null_expr_test('makes timer_start() error out', 'timer_start(0, S)', 'E921: Invalid callback argument', -1)
null_expr_test('does not crash filereadable()', 'filereadable(S)', 0, 0)
null_expr_test('does not crash filewritable()', 'filewritable(S)', 0, 0)
null_expr_test('does not crash fnamemodify()', 'fnamemodify(S, S)', 0, '')
null_expr_test('does not crash getfperm()', 'getfperm(S)', 0, '')
null_expr_test('does not crash getfsize()', 'getfsize(S)', 0, -1)
null_expr_test('does not crash getftime()', 'getftime(S)', 0, -1)
null_expr_test('does not crash getftype()', 'getftype(S)', 0, '')
null_expr_test('does not crash glob()', 'glob(S)', 0, '')
null_expr_test('does not crash globpath()', 'globpath(S, S)', 0, '')
null_expr_test('does not crash mkdir()', 'mkdir(S)', 0, 0)
null_expr_test('does not crash sort()', 'sort(["b", S, "a"])', 0, {'', 'a', 'b'})
null_expr_test('does not crash split()', 'split(S)', 0, {})
null_test('can be used to set an option', 'let &grepprg = S', 0)
null_expr_test('is equal to non-existent variable', 'S == V', 0, 1)
end)
end)

View File

@@ -0,0 +1,28 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local eval = helpers.eval
local clear = helpers.clear
describe('Division operator', function()
before_each(clear)
it('returns infinity on {positive}/0.0', function()
eq('str2float(\'inf\')', eval('string(1.0/0.0)'))
eq('str2float(\'inf\')', eval('string(1.0e-100/0.0)'))
eq('str2float(\'inf\')', eval('string(1.0e+100/0.0)'))
eq('str2float(\'inf\')', eval('string((1.0/0.0)/0.0)'))
end)
it('returns -infinity on {negative}/0.0', function()
eq('-str2float(\'inf\')', eval('string((-1.0)/0.0)'))
eq('-str2float(\'inf\')', eval('string((-1.0e-100)/0.0)'))
eq('-str2float(\'inf\')', eval('string((-1.0e+100)/0.0)'))
eq('-str2float(\'inf\')', eval('string((-1.0/0.0)/0.0)'))
end)
it('returns NaN on 0.0/0.0', function()
eq('str2float(\'nan\')', eval('string(0.0/0.0)'))
eq('str2float(\'nan\')', eval('string(-(0.0/0.0))'))
eq('str2float(\'nan\')', eval('string((-0.0)/0.0)'))
end)
end)

View File

@@ -0,0 +1,92 @@
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()
before_each(clear)
it('works with zero and %b', function()
eq('0', funcs.printf('%lb', 0))
eq('0', funcs.printf('%llb', 0))
eq('0', funcs.printf('%zb', 0))
end)
it('works with one and %b', function()
eq('1', funcs.printf('%b', 1))
eq('1', funcs.printf('%lb', 1))
eq('1', funcs.printf('%llb', 1))
eq('1', funcs.printf('%zb', 1))
end)
it('works with 0xff and %b', function()
eq('11111111', funcs.printf('%b', 0xff))
eq('11111111', funcs.printf('%lb', 0xff))
eq('11111111', funcs.printf('%llb', 0xff))
eq('11111111', funcs.printf('%zb', 0xff))
end)
it('accepts width modifier with %b', function()
eq(' 1', funcs.printf('%3b', 1))
end)
it('accepts prefix modifier with %b', function()
eq('0b1', funcs.printf('%#b', 1))
end)
it('writes capital B with %B', function()
eq('0B1', funcs.printf('%#B', 1))
end)
it('accepts prefix, zero-fill and width modifiers with %b', function()
eq('0b001', funcs.printf('%#05b', 1))
end)
it('accepts prefix and width modifiers with %b', function()
eq(' 0b1', funcs.printf('%#5b', 1))
end)
it('does not write prefix for zero with prefix and width modifier used with %b', function()
eq(' 0', funcs.printf('%#5b', 0))
end)
it('accepts precision modifier with %b', function()
eq('00000', funcs.printf('%.5b', 0))
end)
it('accepts all modifiers with %b at once', function()
-- zero-fill modifier is ignored when used with left-align
-- force-sign and add-blank are ignored
-- use-grouping-characters modifier is ignored always
eq('0b00011 ', funcs.printf('% \'+#0-10.5b', 3))
end)
it('errors out when %b modifier is used for a list', function()
eq('Vim(call):E745: Using a List as a Number', exc_exec('call printf("%b", [])'))
end)
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)

View File

@@ -0,0 +1,53 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, ok = helpers.clear, helpers.eq, helpers.ok
local neq, command, funcs = helpers.neq, helpers.command, helpers.funcs
local reltime, reltimestr, reltimefloat = funcs.reltime, funcs.reltimestr, funcs.reltimefloat
describe('reltimestr(), reltimefloat()', function()
before_each(clear)
it('acceptance', function()
local now = reltime()
command('sleep 10m')
local later = reltime()
local elapsed = reltime(now)
neq(reltimestr(elapsed), '0.0')
ok(reltimefloat(elapsed) > 0.0)
-- original vim test for < 0.1, but easily fails on travis
ok(nil ~= string.match(reltimestr(elapsed), "0%."))
ok(reltimefloat(elapsed) < 1.0)
local same = reltime(now, now)
local samestr = string.gsub(reltimestr(same), ' ', '')
samestr = string.sub(samestr, 1, 5)
eq('0.000', samestr)
eq(0.0, reltimefloat(same))
local differs = reltime(now, later)
neq(reltimestr(differs), '0.0')
ok(reltimefloat(differs) > 0.0)
-- original vim test for < 0.1, but easily fails on travis
ok(nil ~= string.match(reltimestr(differs), "0%."))
ok(reltimefloat(differs) < 1.0)
end)
it('(start - end) returns negative #10452', function()
local older_time = reltime()
command('sleep 1m')
local newer_time = reltime()
-- Start/end swapped: should be something like -0.002123.
local tm_s = tonumber(reltimestr(reltime(newer_time, older_time)))
local tm_f = reltimefloat(reltime(newer_time, older_time))
ok(tm_s < 0 and tm_s > -10)
ok(tm_f < 0 and tm_f > -10)
-- Not swapped: should be something like 0.002123.
tm_s = tonumber(reltimestr(reltime(older_time, newer_time)))
tm_f = reltimefloat(reltime(older_time, newer_time))
ok(tm_s > 0 and tm_s < 10)
ok(tm_f > 0 and tm_f < 10)
end)
end)

View File

@@ -0,0 +1,156 @@
local helpers = require('test.functional.helpers')(after_each)
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 iswin = helpers.iswin
local ok = helpers.ok
local matches = helpers.matches
local pcall_err = helpers.pcall_err
local function clear_serverlist()
for _, server in pairs(funcs.serverlist()) do
funcs.serverstop(server)
end
end
describe('server', function()
before_each(clear)
it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function()
-- Unset $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'))
eq(1, eval("serverstop('"..s.."')"))
eq('', eval('$NVIM_LISTEN_ADDRESS'))
end)
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,
'v:servername was not initialized')
-- v:servername is readonly so we cannot unset it--but we can test that it
-- does not get set again thereafter.
local s = funcs.serverstart()
assert(s ~= nil and s:len() > 0, "serverstart() returned empty")
neq(initial_server, s)
-- serverstop() does _not_ modify v:servername...
eq(1, funcs.serverstop(s))
eq(initial_server, meths.get_vvar('servername'))
-- ...unless we stop _all_ servers.
eq(1, funcs.serverstop(funcs.serverlist()[1]))
eq('', meths.get_vvar('servername'))
-- v:servername will take the next available server.
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() returns false for invalid input', function()
eq(0, eval("serverstop('')"))
eq(0, eval("serverstop('bogus-socket-name')"))
end)
it('parses endpoints correctly', function()
clear_serverlist()
eq({}, funcs.serverlist())
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
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()
eq('Vim:Failed to start server: invalid argument',
pcall_err(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 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.."')"))
end
local new_servs = eval('serverlist()')
-- Exactly #servs servers should be added.
eq(n + #servs, #new_servs)
-- The new servers should be at the end of the list.
for i = 1, #servs do
eq(servs[i], new_servs[i + n])
eq(1, eval("serverstop('"..servs[i].."')"))
end
-- 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)

View File

@@ -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)

View File

@@ -0,0 +1,57 @@
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):E362: Using a boolean 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({
['E362: Using a boolean value as a Float']=true,
['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)

View File

@@ -0,0 +1,190 @@
local helpers = require('test.functional.helpers')(after_each)
local exc_exec = helpers.exc_exec
local command = helpers.command
local funcs = helpers.funcs
local clear = helpers.clear
local eval = helpers.eval
local eq = helpers.eq
local meths = helpers.meths
local NIL = helpers.NIL
describe('Special values', function()
before_each(clear)
it('do not cause error when freed', function()
command([[
function Test()
try
return v:true
finally
return 'something else'
endtry
endfunction
]])
eq(0, exc_exec('call Test()'))
end)
it('work with empty()', function()
eq(0, funcs.empty(true))
eq(1, funcs.empty(false))
eq(1, funcs.empty(NIL))
end)
it('can be stringified and evaled back', function()
eq(true, funcs.eval(funcs.string(true)))
eq(false, funcs.eval(funcs.string(false)))
eq(NIL, funcs.eval(funcs.string(NIL)))
end)
it('work with is/isnot properly', function()
eq(1, eval('v:null is v:null'))
eq(0, eval('v:null is v:true'))
eq(0, eval('v:null is v:false'))
eq(1, eval('v:true is v:true'))
eq(0, eval('v:true is v:false'))
eq(1, eval('v:false is v:false'))
eq(0, eval('v:null is 0'))
eq(0, eval('v:true is 0'))
eq(0, eval('v:false is 0'))
eq(0, eval('v:null is 1'))
eq(0, eval('v:true is 1'))
eq(0, eval('v:false is 1'))
eq(0, eval('v:null is ""'))
eq(0, eval('v:true is ""'))
eq(0, eval('v:false is ""'))
eq(0, eval('v:null is "null"'))
eq(0, eval('v:true is "true"'))
eq(0, eval('v:false is "false"'))
eq(0, eval('v:null is []'))
eq(0, eval('v:true is []'))
eq(0, eval('v:false is []'))
eq(0, eval('v:null isnot v:null'))
eq(1, eval('v:null isnot v:true'))
eq(1, eval('v:null isnot v:false'))
eq(0, eval('v:true isnot v:true'))
eq(1, eval('v:true isnot v:false'))
eq(0, eval('v:false isnot v:false'))
eq(1, eval('v:null isnot 0'))
eq(1, eval('v:true isnot 0'))
eq(1, eval('v:false isnot 0'))
eq(1, eval('v:null isnot 1'))
eq(1, eval('v:true isnot 1'))
eq(1, eval('v:false isnot 1'))
eq(1, eval('v:null isnot ""'))
eq(1, eval('v:true isnot ""'))
eq(1, eval('v:false isnot ""'))
eq(1, eval('v:null isnot "null"'))
eq(1, eval('v:true isnot "true"'))
eq(1, eval('v:false isnot "false"'))
eq(1, eval('v:null isnot []'))
eq(1, eval('v:true isnot []'))
eq(1, eval('v:false isnot []'))
end)
it('work with +/-/* properly', function()
eq(1, eval('0 + v:true'))
eq(0, eval('0 + v:null'))
eq(0, eval('0 + v:false'))
eq(-1, eval('0 - v:true'))
eq( 0, eval('0 - v:null'))
eq( 0, eval('0 - v:false'))
eq(1, eval('1 * v:true'))
eq(0, eval('1 * v:null'))
eq(0, eval('1 * v:false'))
end)
it('does not work with +=/-=/.=', function()
meths.set_var('true', true)
meths.set_var('false', false)
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'))
eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let null += 1'))
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'))
eq('Vim(let):E734: Wrong variable type for -=', exc_exec('let null -= 1'))
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'))
eq('Vim(let):E734: Wrong variable type for .=', exc_exec('let null .= 1'))
end)
it('work with . (concat) properly', function()
eq("true", eval('"" . v:true'))
eq("null", eval('"" . v:null'))
eq("false", eval('"" . v:false'))
end)
it('work with type()', function()
eq(6, funcs.type(true))
eq(6, funcs.type(false))
eq(7, funcs.type(NIL))
end)
it('work with copy() and deepcopy()', function()
eq(true, funcs.deepcopy(true))
eq(false, funcs.deepcopy(false))
eq(NIL, funcs.deepcopy(NIL))
eq(true, funcs.copy(true))
eq(false, funcs.copy(false))
eq(NIL, funcs.copy(NIL))
end)
it('fails in index', function()
eq('Vim(echo):E909: Cannot index a special variable', exc_exec('echo v:true[0]'))
eq('Vim(echo):E909: Cannot index a special variable', exc_exec('echo v:false[0]'))
eq('Vim(echo):E909: Cannot index a special variable', exc_exec('echo v:null[0]'))
end)
it('is accepted by assert_true and assert_false', function()
funcs.assert_false(false)
funcs.assert_false(true)
funcs.assert_false(NIL)
funcs.assert_true(false)
funcs.assert_true(true)
funcs.assert_true(NIL)
eq({
'Expected False but got v:true',
'Expected False but got v:null',
'Expected True but got v:false',
'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)

View File

@@ -0,0 +1,277 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local command = helpers.command
local meths = helpers.meths
local eval = helpers.eval
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local funcs = helpers.funcs
local NIL = helpers.NIL
local source = helpers.source
local dedent = helpers.dedent
describe('string() function', function()
before_each(clear)
describe('used to represent floating-point values', function()
it('dumps NaN values', function()
eq('str2float(\'nan\')', eval('string(str2float(\'nan\'))'))
end)
it('dumps infinite values', function()
eq('str2float(\'inf\')', eval('string(str2float(\'inf\'))'))
eq('-str2float(\'inf\')', eval('string(str2float(\'-inf\'))'))
end)
it('dumps regular values', function()
eq('1.5', funcs.string(1.5))
eq('1.56e-20', funcs.string(1.56000e-020))
eq('0.0', eval('string(0.0)'))
end)
it('dumps special v: values', function()
eq('v:true', eval('string(v:true)'))
eq('v:false', eval('string(v:false)'))
eq('v:null', eval('string(v:null)'))
eq('v:true', funcs.string(true))
eq('v:false', funcs.string(false))
eq('v:null', funcs.string(NIL))
end)
it('dumps values with at most six digits after the decimal point',
function()
eq('1.234568e-20', funcs.string(1.23456789123456789123456789e-020))
eq('1.234568', funcs.string(1.23456789123456789123456789))
end)
it('dumps values with at most seven digits before the decimal point',
function()
eq('1234567.891235', funcs.string(1234567.89123456789123456789))
eq('1.234568e7', funcs.string(12345678.9123456789123456789))
end)
it('dumps negative values', function()
eq('-1.5', funcs.string(-1.5))
eq('-1.56e-20', funcs.string(-1.56000e-020))
eq('-1.234568e-20', funcs.string(-1.23456789123456789123456789e-020))
eq('-1.234568', funcs.string(-1.23456789123456789123456789))
eq('-1234567.891235', funcs.string(-1234567.89123456789123456789))
eq('-1.234568e7', funcs.string(-12345678.9123456789123456789))
end)
end)
describe('used to represent numbers', function()
it('dumps regular values', function()
eq('0', funcs.string(0))
eq('-1', funcs.string(-1))
eq('1', funcs.string(1))
end)
it('dumps large values', function()
eq('2147483647', funcs.string(2^31-1))
eq('-2147483648', funcs.string(-2^31))
end)
end)
describe('used to represent strings', function()
it('dumps regular strings', function()
eq('\'test\'', funcs.string('test'))
end)
it('dumps empty strings', function()
eq('\'\'', funcs.string(''))
end)
it('dumps strings with \' inside', function()
eq('\'\'\'\'\'\'\'\'', funcs.string('\'\'\''))
eq('\'a\'\'b\'\'\'\'\'', funcs.string('a\'b\'\''))
eq('\'\'\'b\'\'\'\'d\'', funcs.string('\'b\'\'d'))
eq('\'a\'\'b\'\'c\'\'d\'', funcs.string('a\'b\'c\'d'))
end)
it('dumps NULL strings', function()
eq('\'\'', eval('string($XXX_UNEXISTENT_VAR_XXX)'))
end)
it('dumps NULL lists', function()
eq('[]', eval('string(v:_null_list)'))
end)
it('dumps NULL dictionaries', function()
eq('{}', eval('string(v:_null_dict)'))
end)
end)
describe('used to represent funcrefs', function()
before_each(function()
source([[
function Test1()
endfunction
function s:Test2() dict
endfunction
function g:Test3() dict
endfunction
let g:Test2_f = function('s:Test2')
]])
end)
it('dumps references to built-in functions', function()
eq('function(\'function\')', eval('string(function("function"))'))
end)
it('dumps references to user functions', function()
eq('function(\'Test1\')', eval('string(function("Test1"))'))
eq('function(\'g:Test3\')', eval('string(function("g:Test3"))'))
end)
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()
it('dumps empty list', function()
eq('[]', funcs.string({}))
end)
it('dumps nested lists', function()
eq('[[[[[]]]]]', funcs.string({{{{{}}}}}))
end)
it('dumps nested non-empty lists', function()
eq('[1, [[3, [[5], 4]], 2]]', funcs.string({1, {{3, {{5}, 4}}, 2}}))
end)
it('errors when dumping recursive lists', function()
meths.set_var('l', {})
eval('add(l, l)')
eq('Vim(echo):E724: unable to correctly dump variable with self-referencing container',
exc_exec('echo string(l)'))
end)
it('dumps recursive lists despite the error', function()
meths.set_var('l', {})
eval('add(l, l)')
eq('\nE724: unable to correctly dump variable with self-referencing container\n[{E724@0}]',
redir_exec('echo string(l)'))
eq('\nE724: unable to correctly dump variable with self-referencing container\n[[{E724@1}]]',
redir_exec('echo string([l])'))
end)
end)
describe('used to represent dictionaries', function()
it('dumps empty dictionary', 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)
it('errors when dumping recursive dictionaries', function()
meths.set_var('d', {d=1})
eval('extend(d, {"d": d})')
eq('Vim(echo):E724: unable to correctly dump variable with self-referencing container',
exc_exec('echo string(d)'))
end)
it('dumps recursive dictionaries despite the error', function()
meths.set_var('d', {d=1})
eval('extend(d, {"d": d})')
eq('\nE724: unable to correctly dump variable with self-referencing container\n{\'d\': {E724@0}}',
redir_exec('echo string(d)'))
eq('\nE724: unable to correctly dump variable with self-referencing container\n{\'out\': {\'d\': {E724@1}}}',
redir_exec('echo string({"out": d})'))
end)
end)
end)

View File

@@ -0,0 +1,589 @@
local helpers = require('test.functional.helpers')(after_each)
local assert_alive = helpers.assert_alive
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 os_kill = helpers.os_kill
local pcall_err = helpers.pcall_err
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
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('throws error if cmd[0] is not executable', function()
eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable",
pcall_err(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.
eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable",
pcall_err(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', 'Write-Output', '%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)
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 Write-Output ''\^"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('Write-Output a b')]]))
eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]]))
eq('a b\n', eval([[system('Write-Output "a b"')]]))
end)
end
it('works with powershell w/ UTF-8 text (#13713)', function()
if not helpers.has_powershell() then
pending("not tested; powershell was not found", function() end)
return
end
-- Should work with recommended config used in helper
helpers.set_shell_powershell()
eq('ああ\n', eval([[system('Write-Output "ああ"')]]))
-- Sanity test w/ default encoding
-- * on Windows, expected to default to Western European enc
-- * on Linux, expected to default to UTF8
command([[let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ']])
eq(iswin() and '??\n' or 'ああ\n', eval([[system('Write-Output "ああ"')]]))
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()
nvim('set_option', 'shell', 'fake_shell')
nvim('set_option', 'shellcmdflag', 'cmdflag')
screen:try_resize(72, 14)
feed(':4verbose echo system("echo hi")<cr>')
if iswin() then
screen:expect{any=[[Executing command: "'fake_shell' 'cmdflag' '"echo hi"'"]]}
else
screen:expect{any=[[Executing command: "'fake_shell' 'cmdflag' '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 <Enter> to 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
assert_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
assert_alive()
end)
it('works with an empty string', function()
eq("test\n", eval('system("echo test", "")'))
assert_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)
it("with a program that doesn't close stdout will exit properly after passing input", function()
local out = eval(string.format("system('%s', 'clip-data')", nvim_dir..'/streams-test'))
assert(out:sub(0, 5) == 'pid: ', out)
os_kill(out:match("%d+"))
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 <Enter> to 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)
it("with a program that doesn't close stdout will exit properly after passing input", function()
local out = eval(string.format("systemlist('%s', 'clip-data')", nvim_dir..'/streams-test'))
assert(out[1]:sub(0, 5) == 'pid: ', out)
os_kill(out[1]:match("%d+"))
end)
it('works with powershell w/ UTF-8 text (#13713)', function()
if not helpers.has_powershell() then
pending("not tested; powershell was not found", function() end)
return
end
-- Should work with recommended config used in helper
helpers.set_shell_powershell()
eq({iswin() and '\r' or ''}, eval([[systemlist('Write-Output あ')]]))
-- Sanity test w/ default encoding
-- * on Windows, expected to default to Western European enc
-- * on Linux, expected to default to UTF8
command([[let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ']])
eq({iswin() and '?\r' or ''}, eval([[systemlist('Write-Output あ')]]))
end)
end)

View File

@@ -0,0 +1,265 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local feed, eq, eval, ok = helpers.feed, helpers.eq, helpers.eval, helpers.ok
local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run
local clear, command, funcs = helpers.clear, helpers.command, helpers.funcs
local curbufmeths = helpers.curbufmeths
local load_adjust = helpers.load_adjust
local retry = helpers.retry
describe('timers', function()
before_each(function()
clear()
source([[
let g:val = 0
func MyHandler(timer)
let g:val += 1
endfunc
]])
end)
it('works one-shot', function()
eq(0, eval("[timer_start(10, 'MyHandler'), g:val][1]"))
run(nil, nil, nil, load_adjust(100))
eq(1,eval("g:val"))
end)
it('works one-shot when repeat=0', function()
eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 0}), g:val][1]"))
run(nil, nil, nil, load_adjust(100))
eq(1, eval("g:val"))
end)
it('works with repeat two', function()
eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 2}), g:val][1]"))
run(nil, nil, nil, load_adjust(20))
retry(nil, load_adjust(300), function()
eq(2, eval("g:val"))
end)
end)
it('are triggered during sleep', function()
source([[
let g:val = -1
func! MyHandler(timer)
if g:val >= 0
let g:val += 1
if g:val == 2
call timer_stop(a:timer)
endif
endif
endfunc
]])
eval("timer_start(10, 'MyHandler', {'repeat': -1})")
nvim_async("command", "sleep 10")
eq(-1, eval("g:val")) -- timer did nothing yet.
nvim_async("command", "let g:val = 0")
run(nil, nil, nil, load_adjust(20))
retry(nil, nil, function()
eq(2, eval("g:val"))
end)
end)
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]"))
retry(nil, nil, function()
eq(1000, eval("g:val"))
end)
end)
it('can be started during sleep', function()
nvim_async("command", "sleep 10")
-- this also tests that remote requests works during sleep
eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 2}), g:val][1]"))
run(nil, nil, nil, load_adjust(20))
retry(nil, load_adjust(300), function() eq(2,eval("g:val")) end)
end)
it('are paused when event processing is disabled', function()
command("call timer_start(5, 'MyHandler', {'repeat': -1})")
run(nil, nil, nil, load_adjust(10))
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, load_adjust(30))
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(5, 'MyHandler', {'repeat': -1})")
nvim_async("command", "let g:val = 0 | let g:c = getchar()")
retry(nil, nil, function()
local val = eval("g:val")
ok(val >= 2, "expected >= 2, got: "..tostring(val))
eq(0, eval("getchar(1)"))
end)
feed("c")
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([[
let g:cont = 0
func! AddItem(timer)
if !g:cont
return
endif
call timer_stop(a:timer)
call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3'])
" Meant to test for what Vim tests in Test_peek_and_get_char.
call getchar(1)
redraw
endfunc
]])
nvim_async("command", "let g:c2 = getchar()")
nvim_async("command", "call timer_start("..load_adjust(100)..", 'AddItem', {'repeat': -1})")
screen:expect([[
ITEM 1 |
ITEM 2 |
{1:~ }|
{1:~ }|
{1:~ }|
^ |
]])
nvim_async("command", "let g:cont = 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_init_val = eval("[timer_start(5, 'MyHandler', {'repeat': -1}), g:val]")
eq(0, t_init_val[2])
run(nil, nil, nil, load_adjust(30))
funcs.timer_stop(t_init_val[1])
local count = eval("g:val")
run(nil, load_adjust(300), nil, load_adjust(30))
local count2 = eval("g:val")
-- when count is eval:ed after timer_stop this should be non-racy
eq(count, count2)
end)
it('can be stopped from the handler', function()
source([[
func! MyHandler(timer)
let g:val += 1
if g:val == 3
call timer_stop(a:timer)
" check double stop is ignored
call timer_stop(a:timer)
endif
endfunc
]])
eq(0, eval("g:val"))
command("call timer_start(10, 'MyHandler', {'repeat': -1})")
retry(nil, nil, function()
eq(3, eval("g:val"))
end)
end)
it('can have two timers', function()
source([[
let g:val2 = 0
func! MyHandler2(timer)
let g:val2 += 1
endfunc
]])
command("call timer_start(2, 'MyHandler', {'repeat': 3})")
command("call timer_start(4, 'MyHandler2', {'repeat': 2})")
retry(nil, nil, function()
eq(3, eval("g:val"))
eq(2, eval("g:val2"))
end)
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 10m
let g:val += 1
endfunc
]])
command("call timer_start(5, 'MyHandler', {'repeat': 1})")
run(nil, nil, nil, load_adjust(20))
retry(nil, load_adjust(150), function()
eq(1, eval("g:val"))
end)
end)
it("doesn't mess up the cmdline", function()
local screen = Screen.new(40, 6)
screen:attach()
screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} )
source([[
let g:val = 0
func! MyHandler(timer)
while !g:val
return
endwhile
call timer_stop(a:timer)
echo "evil"
redraw
let g:val = 2
endfunc
]])
command("call timer_start(100, 'MyHandler', {'repeat': -1})")
feed(":good")
screen:expect([[
|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
:good^ |
]])
command('let g:val = 1')
screen:expect{grid=[[
|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
:good^ |
]], intermediate=true, timeout=load_adjust(200)}
eq(2, eval('g:val'))
end)
end)

View File

@@ -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):E362: Using a boolean 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)

View File

@@ -0,0 +1,15 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq
local command = helpers.command
describe('v:event', function()
before_each(clear)
it('is empty before any autocommand', function()
eq({}, eval('v:event'))
end)
it('is immutable', function()
eq(false, pcall(command, 'let v:event = {}'))
eq(false, pcall(command, 'let v:event.mykey = {}'))
end)
end)

View File

@@ -0,0 +1,78 @@
local helpers = require('test.functional.helpers')(after_each)
local call = helpers.call
local clear = helpers.clear
local command = helpers.command
local eval = helpers.eval
local eq = helpers.eq
local feed = helpers.feed
local feed_command = helpers.feed_command
local next_msg = helpers.next_msg
local nvim = helpers.nvim
local source = helpers.source
local pcall_err = helpers.pcall_err
before_each(function()
clear()
local channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
end)
describe('wait()', function()
it('waits and returns 0 when condition is satisfied', function()
source([[
let g:_awake = 0
call timer_start(100, { -> nvim_command('let g:_awake = 1') })
]])
eq(0, eval('g:_awake'))
eq(0, eval('wait(1500, { -> g:_awake })'))
eq(1, eval('g:_awake'))
eq(0, eval('wait(0, 1)'))
end)
it('returns -1 on timeout', function()
eq(-1, eval('wait(0, 0)'))
eq(-1, eval('wait(50, 0)'))
end)
it('returns -2 when interrupted', function()
feed_command('call rpcnotify(g:channel, "ready") | '..
'call rpcnotify(g:channel, "wait", wait(-1, 0))')
eq({'notification', 'ready', {}}, next_msg())
feed('<c-c>')
eq({'notification', 'wait', {-2}}, next_msg())
end)
it('returns -3 on error', function()
command('silent! let ret = wait(-1, "error")')
eq(-3, eval('ret'))
command('let ret = 0 | silent! let ret = wait(-1, { -> error })')
eq(-3, eval('ret'))
end)
it('evaluates the condition on given interval', function()
source([[
function Count()
let g:counter += 1
return g:counter
endfunction
]])
-- XXX: flaky (#11137)
helpers.retry(nil, nil, function()
nvim('set_var', 'counter', 0)
eq(-1, call('wait', 20, 'Count() >= 5', 99999))
end)
nvim('set_var', 'counter', 0)
eq(0, call('wait', 10000, 'Count() >= 5', 5))
eq(5, nvim('get_var', 'counter'))
end)
it('validates args', function()
eq('Vim:E475: Invalid value for argument 1', pcall_err(call, 'wait', '', 1))
eq('Vim:E475: Invalid value for argument 3', pcall_err(call, 'wait', 0, 1, -1))
eq('Vim:E475: Invalid value for argument 3', pcall_err(call, 'wait', 0, 1, 0))
eq('Vim:E475: Invalid value for argument 3', pcall_err(call, 'wait', 0, 1, ''))
end)
end)

View File

@@ -0,0 +1,156 @@
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('writes list with a null string to a file', function()
eq(0, exc_exec(
('call writefile([v:_null_string], "%s", "b")'):format(
fname)))
eq('', read_file(fname))
eq(0, exc_exec(('call writefile([v:_null_string], "%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('\nE475: Invalid argument: writefile() first argument must be a List or a Blob',
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)