aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md13
-rw-r--r--test/functional/api/vim_spec.lua22
-rw-r--r--test/functional/autocmd/termclose_spec.lua14
-rw-r--r--test/functional/core/job_spec.lua3
-rw-r--r--test/functional/eval/buf_functions_spec.lua302
-rw-r--r--test/functional/eval/container_functions_spec.lua24
-rw-r--r--test/functional/eval/input_spec.lua38
-rw-r--r--test/functional/eval/match_functions_spec.lua61
-rw-r--r--test/functional/eval/minmax_functions_spec.lua51
-rw-r--r--test/functional/eval/null_spec.lua138
-rw-r--r--test/functional/eval/sort_spec.lua41
-rw-r--r--test/functional/eval/string_spec.lua10
-rw-r--r--test/functional/eval/timer_spec.lua2
-rw-r--r--test/functional/eval/writefile_spec.lua9
-rw-r--r--test/functional/ex_cmds/dict_notifications_spec.lua91
-rw-r--r--test/functional/ex_cmds/quickfix_commands_spec.lua83
-rw-r--r--test/functional/ex_cmds/write_spec.lua43
-rw-r--r--test/functional/helpers.lua30
-rw-r--r--test/functional/legacy/051_highlight_spec.lua2
-rw-r--r--test/functional/legacy/062_tab_pages_spec.lua16
-rw-r--r--test/functional/legacy/063_match_and_matchadd_spec.lua5
-rw-r--r--test/functional/legacy/arglist_spec.lua29
-rw-r--r--test/functional/normal/fold_spec.lua296
-rw-r--r--test/functional/options/pastetoggle_spec.lua37
-rw-r--r--test/functional/shada/shada_spec.lua6
-rw-r--r--test/functional/terminal/cursor_spec.lua17
-rw-r--r--test/functional/terminal/scrollback_spec.lua37
-rw-r--r--test/functional/terminal/tui_spec.lua1
-rw-r--r--test/functional/ui/cursor_spec.lua192
-rw-r--r--test/functional/ui/highlight_spec.lua142
-rw-r--r--test/functional/ui/mouse_spec.lua2
-rw-r--r--test/functional/ui/screen.lua39
-rw-r--r--test/functional/ui/screen_basic_spec.lua50
-rw-r--r--test/functional/viml/completion_spec.lua36
-rw-r--r--test/helpers.lua78
-rw-r--r--test/unit/eval/decode_spec.lua2
-rw-r--r--test/unit/eval/helpers.lua311
-rw-r--r--test/unit/eval/tricks_spec.lua20
-rw-r--r--test/unit/eval/tv_clear_spec.lua12
-rw-r--r--test/unit/eval/typval_spec.lua2933
-rw-r--r--test/unit/helpers.lua272
-rw-r--r--test/unit/os/env_spec.lua30
-rw-r--r--test/unit/os/fileio_spec.lua85
-rw-r--r--test/unit/testtest_spec.lua19
44 files changed, 5220 insertions, 424 deletions
diff --git a/test/README.md b/test/README.md
index df66f24626..2857cc0ecf 100644
--- a/test/README.md
+++ b/test/README.md
@@ -27,8 +27,8 @@ groups by the semantic component they are testing.
Test behaviour is affected by environment variables. Currently supported
(Functional, Unit, Benchmarks) (when Defined; when set to _1_; when defined,
-treated as Integer; when defined, treated as String; !must be defined to
-function properly):
+treated as Integer; when defined, treated as String; when defined, treated as
+Number; !must be defined to function properly):
`GDB` (F) (D): makes nvim instances to be run under `gdbserver`. It will be
accessible on `localhost:7777`: use `gdb build/bin/nvim`, type `target remote
@@ -99,3 +99,12 @@ get backtrace from).
approximately 90% of the tests. Should be used when finding cores is too hard
for some reason. Normally (on OS X or when `NVIM_TEST_CORE_GLOB_DIRECTORY` is
defined and this variable is not) cores are checked for after each test.
+
+`NVIM_TEST_RUN_TESTTEST` (U) (1): allows running `test/unit/testtest_spec.lua`
+used to check how testing infrastructure works.
+
+`NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level: `0`
+disables tracing (the fastest, but you get no data if tests crash and there was
+no core dump generated), `1` or empty/undefined leaves only C function cals and
+returns in the trace (faster then recording everything), `2` records all
+function calls, returns and lua source lines exuecuted.
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 3348368a36..8f9f155110 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -153,6 +153,28 @@ describe('api', function()
nvim('set_option', 'equalalways', false)
ok(not nvim('get_option', 'equalalways'))
end)
+
+ it('works to get global value of local options', function()
+ eq(false, nvim('get_option', 'lisp'))
+ eq(8, nvim('get_option', 'shiftwidth'))
+ end)
+
+ it('works to set global value of local options', function()
+ nvim('set_option', 'lisp', true)
+ eq(true, nvim('get_option', 'lisp'))
+ eq(false, helpers.curbuf('get_option', 'lisp'))
+ eq(nil, nvim('command_output', 'setglobal lisp?'):match('nolisp'))
+ eq('nolisp', nvim('command_output', 'setlocal lisp?'):match('nolisp'))
+ nvim('set_option', 'shiftwidth', 20)
+ eq('20', nvim('command_output', 'setglobal shiftwidth?'):match('%d+'))
+ eq('8', nvim('command_output', 'setlocal shiftwidth?'):match('%d+'))
+ end)
+
+ it('most window-local options have no global value', function()
+ local status, err = pcall(nvim, 'get_option', 'foldcolumn')
+ eq(false, status)
+ ok(err:match('Invalid option name') ~= nil)
+ end)
end)
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua
index ecda1bffb7..d4beab22e4 100644
--- a/test/functional/autocmd/termclose_spec.lua
+++ b/test/functional/autocmd/termclose_spec.lua
@@ -14,17 +14,11 @@ describe('TermClose event', function()
nvim('set_option', 'shellcmdflag', 'EXE')
end)
- local function eq_err(expected, actual)
- if expected ~= actual then
- error('expected: '..tostring(expected)..', actual: '..tostring(actual))
- end
- end
-
it('triggers when terminal job ends', function()
command('autocmd TermClose * let g:test_termclose = 23')
command('terminal')
command('call jobstop(b:terminal_job_id)')
- retry(nil, nil, function() eq_err(23, eval('g:test_termclose')) end)
+ retry(nil, nil, function() eq(23, eval('g:test_termclose')) end)
end)
it('reports the correct <abuf>', function()
@@ -35,12 +29,12 @@ describe('TermClose event', function()
eq(2, eval('bufnr("%")'))
command('terminal')
- retry(nil, nil, function() eq_err(3, eval('bufnr("%")')) end)
+ retry(nil, nil, function() eq(3, eval('bufnr("%")')) end)
command('buffer 1')
- retry(nil, nil, function() eq_err(1, eval('bufnr("%")')) end)
+ retry(nil, nil, function() eq(1, eval('bufnr("%")')) end)
command('3bdelete!')
- retry(nil, nil, function() eq_err('3', eval('g:abuf')) end)
+ retry(nil, nil, function() eq('3', eval('g:abuf')) end)
end)
end)
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index e442c2a317..9ee91f2fe9 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -8,6 +8,7 @@ local clear, eq, eval, exc_exec, execute, feed, insert, neq, next_msg, nvim,
local command = helpers.command
local wait = helpers.wait
local iswin = helpers.iswin
+local get_pathsep = helpers.get_pathsep
local Screen = require('test.functional.ui.screen')
describe('jobs', function()
@@ -65,7 +66,7 @@ describe('jobs', function()
end)
it('changes to given `cwd` directory', function()
- local dir = eval('resolve(tempname())')
+ local dir = eval("resolve(tempname())"):gsub("/", get_pathsep())
mkdir(dir)
nvim('command', "let g:job_opts.cwd = '" .. dir .. "'")
if iswin() then
diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua
new file mode 100644
index 0000000000..db50874c53
--- /dev/null
+++ b/test/functional/eval/buf_functions_spec.lua
@@ -0,0 +1,302 @@
+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 fname = 'Xtest-functional-eval-buf_functions'
+local fname2 = fname .. '.2'
+local dirname = fname .. '.d'
+
+before_each(clear)
+
+for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
+ 'getbufline(%s, 1)', 'getbufvar(%s, "changedtick")',
+ 'setbufvar(%s, "f", 0)'}) do
+ local funcname = func:match('%w+')
+ describe(funcname .. '() function', function()
+ it('errors out when receives v:true/v:false/v:null', function()
+ -- Not compatible with Vim: in Vim it always results in buffer not found
+ -- without any error messages.
+ for _, var in ipairs({'v:true', 'v:false', 'v:null'}) do
+ eq('Vim(call):E5300: Expected a Number or a String',
+ exc_exec('call ' .. func:format(var)))
+ end
+ end)
+ it('errors out when receives invalid argument', function()
+ eq('Vim(call):E745: Expected a Number or a String, List found',
+ exc_exec('call ' .. func:format('[]')))
+ eq('Vim(call):E728: Expected a Number or a String, Dictionary found',
+ exc_exec('call ' .. func:format('{}')))
+ eq('Vim(call):E805: Expected a Number or a String, Float found',
+ exc_exec('call ' .. func:format('0.0')))
+ eq('Vim(call):E703: Expected a Number or a String, Funcref found',
+ exc_exec('call ' .. func:format('function("tr")')))
+ end)
+ end)
+end
+
+describe('bufname() function', function()
+ it('returns empty string when buffer was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.bufname(2))
+ eq('', funcs.bufname('non-existent-buffer'))
+ eq('', funcs.bufname('#'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('', funcs.bufname('X'))
+ end)
+ before_each(function()
+ lfs.mkdir(dirname)
+ end)
+ after_each(function()
+ lfs.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()
+ lfs.rmdir(dirname)
+ end)
+ it('returns expected window number', function()
+ eq(1, funcs.bufwinnr('%'))
+ command('file ' .. fname)
+ command('vsplit')
+ command('split ' .. fname2)
+ eq(2, funcs.bufwinnr(fname))
+ eq(1, funcs.bufwinnr(fname2))
+ eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
+ meths.set_current_dir(dirname)
+ eq(2, funcs.bufwinnr(fname))
+ eq(1, funcs.bufwinnr(fname2))
+ eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
+ eq(1, funcs.bufwinnr('%'))
+ eq(2, funcs.bufwinnr(1))
+ eq(1, funcs.bufwinnr(2))
+ eq(-1, funcs.bufwinnr(3))
+ eq(1, funcs.bufwinnr('$'))
+ end)
+end)
+
+describe('getbufline() function', function()
+ it('returns empty list when buffer was not found', function()
+ command('file ' .. fname)
+ eq({}, funcs.getbufline(2, 1))
+ eq({}, funcs.getbufline('non-existent-buffer', 1))
+ eq({}, funcs.getbufline('#', 1))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq({}, funcs.getbufline('X', 1))
+ end)
+ it('returns empty list when range is invalid', function()
+ eq({}, funcs.getbufline(1, 0))
+ curbufmeths.set_lines(0, 1, false, {'foo', 'bar', 'baz'})
+ eq({}, funcs.getbufline(1, 2, 1))
+ eq({}, funcs.getbufline(1, -10, -20))
+ eq({}, funcs.getbufline(1, -2, -1))
+ eq({}, funcs.getbufline(1, -1, 9999))
+ end)
+ it('returns expected lines', function()
+ meths.set_option('hidden', true)
+ command('file ' .. fname)
+ curbufmeths.set_lines(0, 1, false, {'foo\0', '\0bar', 'baz'})
+ command('edit ' .. fname2)
+ curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
+ eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, 9999))
+ eq({'abc\n', '\ndef', 'ghi'}, funcs.getbufline(2, 1, 9999))
+ eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, '$'))
+ eq({'baz'}, funcs.getbufline(1, '$', '$'))
+ eq({'baz'}, funcs.getbufline(1, '$', 9999))
+ end)
+end)
+
+describe('getbufvar() function', function()
+ it('returns empty list when buffer was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.getbufvar(2, '&autoindent'))
+ eq('', funcs.getbufvar('non-existent-buffer', '&autoindent'))
+ eq('', funcs.getbufvar('#', '&autoindent'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('', funcs.getbufvar('X', '&autoindent'))
+ end)
+ it('returns empty list when variable/option/etc was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.getbufvar(1, '&autondent'))
+ eq('', funcs.getbufvar(1, 'changedtic'))
+ end)
+ it('returns expected option value', function()
+ eq(0, funcs.getbufvar(1, '&autoindent'))
+ eq(0, funcs.getbufvar(1, '&l:autoindent'))
+ eq(0, funcs.getbufvar(1, '&g:autoindent'))
+ -- Also works with global-only options
+ eq(0, funcs.getbufvar(1, '&hidden'))
+ eq(0, funcs.getbufvar(1, '&l:hidden'))
+ eq(0, funcs.getbufvar(1, '&g:hidden'))
+ -- Also works with window-local options
+ eq(0, funcs.getbufvar(1, '&number'))
+ eq(0, funcs.getbufvar(1, '&l:number'))
+ eq(0, funcs.getbufvar(1, '&g:number'))
+ command('new')
+ -- But with window-local options it probably does not what you expect
+ curwinmeths.set_option('number', true)
+ -- (note that current window’s buffer is 2, but getbufvar() receives 1)
+ eq(2, bufmeths.get_number(curwinmeths.get_buf()))
+ eq(1, funcs.getbufvar(1, '&number'))
+ eq(1, funcs.getbufvar(1, '&l:number'))
+ -- You can get global value though, if you find this useful.
+ eq(0, funcs.getbufvar(1, '&g:number'))
+ end)
+ it('returns expected variable value', function()
+ eq(2, funcs.getbufvar(1, 'changedtick'))
+ curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
+ eq(3, funcs.getbufvar(1, 'changedtick'))
+ curbufmeths.set_var('test', true)
+ eq(true, funcs.getbufvar(1, 'test'))
+ eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
+ command('new')
+ eq(3, funcs.getbufvar(1, 'changedtick'))
+ eq(true, funcs.getbufvar(1, 'test'))
+ eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
+ end)
+end)
+
+describe('setbufvar() function', function()
+ it('throws the error or ignores the input when buffer was not found', function()
+ command('file ' .. fname)
+ eq(0,
+ exc_exec('call setbufvar(2, "&autoindent", 0)'))
+ eq('Vim(call):E94: No matching buffer for non-existent-buffer',
+ exc_exec('call setbufvar("non-existent-buffer", "&autoindent", 0)'))
+ eq(0,
+ exc_exec('call setbufvar("#", "&autoindent", 0)'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('Vim(call):E93: More than one match for X',
+ exc_exec('call setbufvar("X", "&autoindent", 0)'))
+ end)
+ it('may set options, including window-local and global values', function()
+ local buf1 = meths.get_current_buf()
+ eq(false, curwinmeths.get_option('number'))
+ command('split')
+ command('new')
+ eq(2, bufmeths.get_number(curwinmeths.get_buf()))
+ funcs.setbufvar(1, '&number', true)
+ local windows = curtabmeths.list_wins()
+ eq(false, winmeths.get_option(windows[1], 'number'))
+ eq(true, winmeths.get_option(windows[2], 'number'))
+ eq(false, winmeths.get_option(windows[3], 'number'))
+ eq(false, winmeths.get_option(meths.get_current_win(), 'number'))
+
+ eq(false, meths.get_option('hidden'))
+ funcs.setbufvar(1, '&hidden', true)
+ eq(true, meths.get_option('hidden'))
+
+ eq(false, bufmeths.get_option(buf1, 'autoindent'))
+ funcs.setbufvar(1, '&autoindent', true)
+ eq(true, bufmeths.get_option(buf1, 'autoindent'))
+ eq('Vim(call):E355: Unknown option: xxx',
+ exc_exec('call setbufvar(1, "&xxx", 0)'))
+ end)
+ it('may set variables', function()
+ local buf1 = meths.get_current_buf()
+ command('split')
+ command('new')
+ eq(2, curbufmeths.get_number())
+ funcs.setbufvar(1, 'number', true)
+ eq(true, bufmeths.get_var(buf1, 'number'))
+ eq('Vim(call):E461: Illegal variable name: b:',
+ exc_exec('call setbufvar(1, "", 0)'))
+ eq(true, bufmeths.get_var(buf1, 'number'))
+ funcs.setbufvar(1, 'changedtick', true)
+ -- eq(true, bufmeths.get_var(buf1, 'changedtick'))
+ eq(2, funcs.getbufvar(1, 'changedtick'))
+ end)
+end)
diff --git a/test/functional/eval/container_functions_spec.lua b/test/functional/eval/container_functions_spec.lua
new file mode 100644
index 0000000000..04a3248c49
--- /dev/null
+++ b/test/functional/eval/container_functions_spec.lua
@@ -0,0 +1,24 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local meths = helpers.meths
+local clear = helpers.clear
+
+before_each(clear)
+
+describe('extend()', function()
+ it('suceeds to extend list with itself', function()
+ meths.set_var('l', {1, {}})
+ eq({1, {}, 1, {}}, eval('extend(l, l)'))
+ eq({1, {}, 1, {}}, meths.get_var('l'))
+
+ meths.set_var('l', {1, {}})
+ eq({1, {}, 1, {}}, eval('extend(l, l, 0)'))
+ eq({1, {}, 1, {}}, meths.get_var('l'))
+
+ meths.set_var('l', {1, {}})
+ eq({1, 1, {}, {}}, eval('extend(l, l, 1)'))
+ eq({1, 1, {}, {}}, meths.get_var('l'))
+ end)
+end)
diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua
new file mode 100644
index 0000000000..393fc10175
--- /dev/null
+++ b/test/functional/eval/input_spec.lua
@@ -0,0 +1,38 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local feed = helpers.feed
+local clear = helpers.clear
+local command = helpers.command
+
+local screen
+
+before_each(function()
+ clear()
+ screen = Screen.new(25, 5)
+ screen:attach()
+end)
+
+describe('input()', function()
+ it('works correctly with multiline prompts', function()
+ feed([[:call input("Test\nFoo")<CR>]])
+ screen:expect([[
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ Test |
+ Foo^ |
+ ]], {{bold=true, foreground=Screen.colors.Blue}})
+ end)
+ it('works correctly with multiline prompts and :echohl', function()
+ command('hi Test ctermfg=Red guifg=Red term=bold')
+ feed([[:echohl Test | call input("Test\nFoo")<CR>]])
+ screen:expect([[
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:Test} |
+ {2:Foo}^ |
+ ]], {{bold=true, foreground=Screen.colors.Blue}, {foreground=Screen.colors.Red}})
+ end)
+end)
diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua
new file mode 100644
index 0000000000..3150d89f62
--- /dev/null
+++ b/test/functional/eval/match_functions_spec.lua
@@ -0,0 +1,61 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local clear = helpers.clear
+local funcs = helpers.funcs
+local command = helpers.command
+
+before_each(clear)
+
+describe('setmatches()', function()
+ it('correctly handles case when both group and pattern entries are numbers',
+ function()
+ command('hi def link 1 PreProc')
+ eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}}))
+ eq({{
+ group='1',
+ pattern='2',
+ id=3,
+ priority=4,
+ }}, funcs.getmatches())
+ eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4, conceal=5}}))
+ eq({{
+ group='1',
+ pattern='2',
+ id=3,
+ priority=4,
+ conceal='5',
+ }}, funcs.getmatches())
+ eq(0, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}))
+ eq({{
+ group='1',
+ pos1={2},
+ pos2={6},
+ id=3,
+ priority=4,
+ conceal='5',
+ }}, funcs.getmatches())
+ end)
+
+ it('fails with -1 if highlight group is not defined', function()
+ eq(-1, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}}))
+ eq({}, funcs.getmatches())
+ eq(-1, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}))
+ eq({}, funcs.getmatches())
+ end)
+end)
+
+describe('matchadd()', function()
+ it('correctly works when first two arguments and conceal are numbers at once',
+ function()
+ command('hi def link 1 PreProc')
+ eq(4, funcs.matchadd(1, 2, 3, 4, {conceal=5}))
+ eq({{
+ group='1',
+ pattern='2',
+ priority=3,
+ id=4,
+ conceal='5',
+ }}, funcs.getmatches())
+ end)
+end)
diff --git a/test/functional/eval/minmax_functions_spec.lua b/test/functional/eval/minmax_functions_spec.lua
new file mode 100644
index 0000000000..c6eb754f91
--- /dev/null
+++ b/test/functional/eval/minmax_functions_spec.lua
@@ -0,0 +1,51 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local clear = helpers.clear
+local funcs = helpers.funcs
+local redir_exec = helpers.redir_exec
+
+before_each(clear)
+for _, func in ipairs({'min', 'max'}) do
+ describe(func .. '()', function()
+ it('gives a single error message when multiple values failed conversions',
+ function()
+ eq('\nE745: Using a List as a Number\n0',
+ redir_exec('echo ' .. func .. '([-5, [], [], [], 5])'))
+ eq('\nE745: Using a List as a Number\n0',
+ redir_exec('echo ' .. func .. '({1:-5, 2:[], 3:[], 4:[], 5:5})'))
+ for errmsg, errinput in pairs({
+ ['E745: Using a List as a Number'] = '[]',
+ ['E805: Using a Float as a Number'] = '0.0',
+ ['E703: Using a Funcref as a Number'] = 'function("tr")',
+ ['E728: Using a Dictionary as a Number'] = '{}',
+ }) do
+ eq('\n' .. errmsg .. '\n0',
+ redir_exec('echo ' .. func .. '([' .. errinput .. '])'))
+ eq('\n' .. errmsg .. '\n0',
+ redir_exec('echo ' .. func .. '({1:' .. errinput .. '})'))
+ end
+ end)
+ it('works with arrays/dictionaries with zero items', function()
+ eq(0, funcs[func]({}))
+ eq(0, eval(func .. '({})'))
+ end)
+ it('works with arrays/dictionaries with one item', function()
+ eq(5, funcs[func]({5}))
+ eq(5, funcs[func]({test=5}))
+ end)
+ it('works with NULL arrays/dictionaries', function()
+ eq(0, eval(func .. '(v:_null_list)'))
+ eq(0, eval(func .. '(v:_null_dict)'))
+ end)
+ it('errors out for invalid types', function()
+ for _, errinput in ipairs({'1', 'v:true', 'v:false', 'v:null',
+ 'function("tr")', '""'}) do
+ eq(('\nE712: Argument of %s() must be a List or Dictionary\n0'):format(
+ func),
+ redir_exec('echo ' .. func .. '(' .. errinput .. ')'))
+ end
+ end)
+ end)
+end
diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua
new file mode 100644
index 0000000000..6fd30caec9
--- /dev/null
+++ b/test/functional/eval/null_spec.lua
@@ -0,0 +1,138 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local curbufmeths = helpers.curbufmeths
+local redir_exec = helpers.redir_exec
+local exc_exec = helpers.exc_exec
+local command = helpers.command
+local clear = helpers.clear
+local meths = helpers.meths
+local funcs = helpers.funcs
+local eq = helpers.eq
+
+describe('NULL', function()
+ before_each(function()
+ clear()
+ command('let L = v:_null_list')
+ command('let D = v:_null_dict')
+ command('let S = $XXX_NONEXISTENT_VAR_XXX')
+ end)
+ local tmpfname = 'Xtest-functional-viml-null'
+ after_each(function()
+ os.remove(tmpfname)
+ end)
+ local null_test = function(name, cmd, err)
+ it(name, function()
+ eq(err, exc_exec(cmd))
+ end)
+ end
+ local null_expr_test = function(name, expr, err, val, after)
+ it(name, function()
+ eq((err == 0) and ('') or ('\n' .. err),
+ redir_exec('let g:_var = ' .. expr))
+ if val == nil then
+ eq(0, funcs.exists('g:_var'))
+ else
+ eq(val, meths.get_var('_var'))
+ end
+ if after ~= nil then
+ after()
+ end
+ end)
+ end
+ describe('list', function()
+ -- Incorrect behaviour
+
+ -- FIXME map() should not return 0 without error
+ null_expr_test('does not crash map()', 'map(L, "v:val")', 0, 0)
+ -- FIXME map() should not return 0 without error
+ null_expr_test('does not crash filter()', 'filter(L, "1")', 0, 0)
+ -- FIXME map() should at least return L
+ null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 0)
+ -- FIXME filter() should at least return L
+ null_expr_test('makes filter() return v:_null_list', 'map(L, "1") is# L', 0, 0)
+ -- FIXME add() should not return 1 at all
+ null_expr_test('does not crash add()', 'add(L, 0)', 0, 1)
+ 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})
+ -- FIXME should be accepted by inputlist()
+ null_expr_test('is accepted as an empty list by inputlist()',
+ '[feedkeys("\\n"), inputlist(L)]', 'E686: Argument of inputlist() must be a List', {0, 0})
+ -- FIXME should be accepted by writefile(), return {0, {}}
+ null_expr_test('is accepted as an empty list by writefile()',
+ ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname),
+ 'E484: Can\'t open file ' .. tmpfname, {0, {}})
+ -- FIXME should give error message
+ null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0)
+ -- FIXME should return 0
+ null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1)
+ -- FIXME should return 0
+ null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, -1)
+ -- FIXME should return 0
+ null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, -1)
+ -- FIXME should return empty list or error out
+ null_expr_test('is accepted by sort()', 'sort(L)', 0, 0)
+ -- FIXME Should return 1
+ null_expr_test('is accepted by sort()', 'sort(L) is L', 0, 0)
+ -- FIXME should not error out
+ null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected')
+ -- FIXME should not error out
+ null_test('is accepted by :lexpr', 'lexpr L', 'Vim(lexpr):E777: String or List expected')
+ null_test('is accepted by :for', 'for x in L|throw x|endfor', 0)
+
+ -- Subjectable behaviour
+
+ -- FIXME Should return 1
+ null_expr_test('is equal to empty list', 'L == []', 0, 0)
+ -- FIXME Should return 1
+ null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0)
+ -- FIXME Should return 1
+ null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0)
+
+ -- Crashes
+
+ -- null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0)
+ -- null_expr_test('does not crash setline', 'setline(1, L)', 0, 0)
+ -- null_expr_test('does not crash system()', 'system("cat", L)', 0, '')
+ -- null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {})
+
+ -- Correct behaviour
+ null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function()
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+ end)
+ null_expr_test('is identical to itself', 'L is L', 0, 1)
+ null_expr_test('can be sliced', 'L[:]', 0, {})
+ null_expr_test('can be copied', 'copy(L)', 0, {})
+ null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {})
+ null_expr_test('does not crash when indexed', 'L[1]',
+ 'E684: list index out of range: 1\nE15: Invalid expression: L[1]', nil)
+ null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0)
+ null_expr_test('does not crash col()', 'col(L)', 0, 0)
+ null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0)
+ null_expr_test('does not crash line()', 'line(L)', 0, 0)
+ null_expr_test('does not crash count()', 'count(L, 1)', 0, 0)
+ null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1)
+ null_expr_test('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)
+ end)
+ describe('dict', function()
+ it('does not crash when indexing NULL dict', function()
+ eq('\nE716: Key not present in Dictionary: test\nE15: Invalid expression: v:_null_dict.test',
+ redir_exec('echo v:_null_dict.test'))
+ end)
+ null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0)
+ null_expr_test('makes extend do nothing', 'extend({1: 2}, D)', 0, {['1']=2})
+ end)
+end)
diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua
new file mode 100644
index 0000000000..4e5a0afba4
--- /dev/null
+++ b/test/functional/eval/sort_spec.lua
@@ -0,0 +1,41 @@
+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
+
+before_each(clear)
+
+describe('sort()', function()
+ it('errors out when sorting special values', function()
+ eq('Vim(call):E907: Using a special value as a Float',
+ exc_exec('call sort([v:true, v:false], "f")'))
+ end)
+
+ it('sorts “wrong” values between -0.0001 and 0.0001, preserving order',
+ function()
+ meths.set_var('list', {true, false, NIL, {}, {a=42}, 'check',
+ 0.0001, -0.0001})
+ command('call insert(g:list, function("tr"))')
+ local error_lines = funcs.split(
+ funcs.execute('silent! call sort(g:list, "f")'), '\n')
+ local errors = {}
+ for _, err in ipairs(error_lines) do
+ errors[err] = true
+ end
+ eq({
+ ['E891: Using a Funcref as a Float']=true,
+ ['E892: Using a String as a Float']=true,
+ ['E893: Using a List as a Float']=true,
+ ['E894: Using a Dictionary as a Float']=true,
+ ['E907: Using a special value as a Float']=true,
+ }, errors)
+ eq('[-1.0e-4, function(\'tr\'), v:true, v:false, v:null, [], {\'a\': 42}, \'check\', 1.0e-4]',
+ eval('string(g:list)'))
+ end)
+end)
diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua
index f6279e85e8..adc1af9b8e 100644
--- a/test/functional/eval/string_spec.lua
+++ b/test/functional/eval/string_spec.lua
@@ -7,7 +7,6 @@ local eval = helpers.eval
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local funcs = helpers.funcs
-local write_file = helpers.write_file
local NIL = helpers.NIL
local source = helpers.source
local dedent = helpers.dedent
@@ -105,10 +104,8 @@ describe('string() function', function()
end)
describe('used to represent funcrefs', function()
- local fname = 'Xtest-functional-eval-string_spec-fref-script.vim'
-
before_each(function()
- write_file(fname, [[
+ source([[
function Test1()
endfunction
@@ -120,11 +117,6 @@ describe('string() function', function()
let g:Test2_f = function('s:Test2')
]])
- command('source ' .. fname)
- end)
-
- after_each(function()
- os.remove(fname)
end)
it('dumps references to built-in functions', function()
diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua
index 4353619ff0..b3c4cd07eb 100644
--- a/test/functional/eval/timer_spec.lua
+++ b/test/functional/eval/timer_spec.lua
@@ -49,7 +49,7 @@ describe('timers', function()
it('works with zero timeout', function()
-- timer_start does still not invoke the callback immediately
eq(0,eval("[timer_start(0, 'MyHandler', {'repeat': 1000}), g:val][1]"))
- run(nil, nil, nil, 300)
+ run(nil, nil, nil, 400)
eq(1000,eval("g:val"))
end)
diff --git a/test/functional/eval/writefile_spec.lua b/test/functional/eval/writefile_spec.lua
index 3052c616e0..2f84114b9b 100644
--- a/test/functional/eval/writefile_spec.lua
+++ b/test/functional/eval/writefile_spec.lua
@@ -80,6 +80,13 @@ describe('writefile()', function()
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))
@@ -115,6 +122,8 @@ describe('writefile()', function()
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)
diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua
index 30753c34ac..e3b4a1c504 100644
--- a/test/functional/ex_cmds/dict_notifications_spec.lua
+++ b/test/functional/ex_cmds/dict_notifications_spec.lua
@@ -9,7 +9,7 @@ local eval = helpers.eval
describe('dictionary change notifications', function()
local channel
- setup(function()
+ before_each(function()
clear()
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
@@ -18,19 +18,15 @@ describe('dictionary change notifications', function()
-- the same set of tests are applied to top-level dictionaries(g:, b:, w: and
-- t:) and a dictionary variable, so we generate them in the following
-- function.
- local function gentests(dict_expr, dict_expr_suffix, dict_init)
- if not dict_expr_suffix then
- dict_expr_suffix = ''
- end
-
+ local function gentests(dict_expr, dict_init)
local function update(opval, key)
if not key then
key = 'watched'
end
if opval == '' then
- nvim('command', "unlet "..dict_expr..dict_expr_suffix..key)
+ command(('unlet %s[\'%s\']'):format(dict_expr, key))
else
- nvim('command', "let "..dict_expr..dict_expr_suffix..key.." "..opval)
+ command(('let %s[\'%s\'] %s'):format(dict_expr, key, opval))
end
end
@@ -48,9 +44,9 @@ describe('dictionary change notifications', function()
eq({'notification', 'values', {key, vals}}, next_msg())
end
- describe('watcher', function()
+ describe(dict_expr .. ' watcher', function()
if dict_init then
- setup(function()
+ before_each(function()
source(dict_init)
end)
end
@@ -58,7 +54,7 @@ describe('dictionary change notifications', function()
before_each(function()
source([[
function! g:Changed(dict, key, value)
- if a:dict != ]]..dict_expr..[[ |
+ if a:dict isnot ]]..dict_expr..[[ |
throw 'invalid dict'
endif
call rpcnotify(g:channel, 'values', a:key, a:value)
@@ -143,6 +139,32 @@ describe('dictionary change notifications', function()
]])
end)
+ it('is triggered for empty keys', function()
+ command([[
+ call dictwatcheradd(]]..dict_expr..[[, "", "g:Changed")
+ ]])
+ update('= 1', '')
+ verify_value({new = 1}, '')
+ update('= 2', '')
+ verify_value({old = 1, new = 2}, '')
+ command([[
+ call dictwatcherdel(]]..dict_expr..[[, "", "g:Changed")
+ ]])
+ end)
+
+ it('is triggered for empty keys when using catch-all *', function()
+ command([[
+ call dictwatcheradd(]]..dict_expr..[[, "*", "g:Changed")
+ ]])
+ update('= 1', '')
+ verify_value({new = 1}, '')
+ update('= 2', '')
+ verify_value({old = 1, new = 2}, '')
+ command([[
+ call dictwatcherdel(]]..dict_expr..[[, "*", "g:Changed")
+ ]])
+ end)
+
-- test a sequence of updates of different types to ensure proper memory
-- management(with ASAN)
local function test_updates(tests)
@@ -190,10 +212,10 @@ describe('dictionary change notifications', function()
gentests('b:')
gentests('w:')
gentests('t:')
- gentests('g:dict_var', '.', 'let g:dict_var = {}')
+ gentests('g:dict_var', 'let g:dict_var = {}')
describe('multiple watchers on the same dict/key', function()
- setup(function()
+ before_each(function()
source([[
function! g:Watcher1(dict, key, value)
call rpcnotify(g:channel, '1', a:key, a:value)
@@ -213,13 +235,37 @@ describe('dictionary change notifications', function()
end)
it('only removes watchers that fully match dict, key and callback', function()
+ nvim('command', 'let g:key = "value"')
+ eq({'notification', '1', {'key', {new = 'value'}}}, next_msg())
+ eq({'notification', '2', {'key', {new = 'value'}}}, next_msg())
nvim('command', 'call dictwatcherdel(g:, "key", "g:Watcher1")')
nvim('command', 'let g:key = "v2"')
eq({'notification', '2', {'key', {old = 'value', new = 'v2'}}}, next_msg())
end)
end)
+ it('errors out when adding to v:_null_dict', function()
+ command([[
+ function! g:Watcher1(dict, key, value)
+ call rpcnotify(g:channel, '1', a:key, a:value)
+ endfunction
+ ]])
+ eq('Vim(call):E46: Cannot change read-only variable "dictwatcheradd() argument"',
+ exc_exec('call dictwatcheradd(v:_null_dict, "x", "g:Watcher1")'))
+ end)
+
describe('errors', function()
+ before_each(function()
+ source([[
+ function! g:Watcher1(dict, key, value)
+ call rpcnotify(g:channel, '1', a:key, a:value)
+ endfunction
+ function! g:Watcher2(dict, key, value)
+ call rpcnotify(g:channel, '2', a:key, a:value)
+ endfunction
+ ]])
+ end)
+
-- WARNING: This suite depends on the above tests
it('fails to remove if no watcher with matching callback is found', function()
eq("Vim(call):Couldn't find a watcher matching key and callback",
@@ -236,15 +282,24 @@ describe('dictionary change notifications', function()
command('call dictwatcherdel(g:, "key", "g:InvalidCb")')
end)
- it('fails with empty keys', function()
- eq("Vim(call):E713: Cannot use empty key for Dictionary",
- exc_exec('call dictwatcheradd(g:, "", "g:Watcher1")'))
- eq("Vim(call):E713: Cannot use empty key for Dictionary",
- exc_exec('call dictwatcherdel(g:, "", "g:Watcher1")'))
+ it('fails to remove watcher from v:_null_dict', function()
+ eq("Vim(call):Couldn't find a watcher matching key and callback",
+ exc_exec('call dictwatcherdel(v:_null_dict, "x", "g:Watcher2")'))
end)
+ --[[
+ [ it("fails to add/remove if the callback doesn't exist", function()
+ [ eq("Vim(call):Function g:InvalidCb doesn't exist",
+ [ exc_exec('call dictwatcheradd(g:, "key", "g:InvalidCb")'))
+ [ eq("Vim(call):Function g:InvalidCb doesn't exist",
+ [ exc_exec('call dictwatcherdel(g:, "key", "g:InvalidCb")'))
+ [ end)
+ ]]
+
it('does not fail to replace a watcher function', function()
source([[
+ let g:key = 'v2'
+ call dictwatcheradd(g:, "key", "g:Watcher2")
function! g:ReplaceWatcher2()
function! g:Watcher2(dict, key, value)
call rpcnotify(g:channel, '2b', a:key, a:value)
diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua
new file mode 100644
index 0000000000..5ab34db3fb
--- /dev/null
+++ b/test/functional/ex_cmds/quickfix_commands_spec.lua
@@ -0,0 +1,83 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local clear = helpers.clear
+local funcs = helpers.funcs
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local write_file = helpers.write_file
+local curbufmeths = helpers.curbufmeths
+
+local file_base = 'Xtest-functional-ex_cmds-quickfix_commands'
+
+before_each(clear)
+
+for _, c in ipairs({'l', 'c'}) do
+ local file = ('%s.%s'):format(file_base, c)
+ local filecmd = c .. 'file'
+ local getfcmd = c .. 'getfile'
+ local addfcmd = c .. 'addfile'
+ local getlist = (c == 'c') and funcs.getqflist or (
+ function() return funcs.getloclist(0) end)
+
+ describe((':%s*file commands'):format(c), function()
+ before_each(function()
+ write_file(file, ([[
+ %s-1.res:700:10:Line 700
+ %s-2.res:800:15:Line 800
+ ]]):format(file, file))
+ end)
+ after_each(function()
+ os.remove(file)
+ end)
+
+ it('work', function()
+ command(('%s %s'):format(filecmd, file))
+ -- Second line of each entry (i.e. `nr=-1, …`) was obtained from actual
+ -- results. First line (i.e. `{lnum=…`) was obtained from legacy test.
+ local list = {
+ {lnum=700, col=10, text='Line 700',
+ nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''},
+ {lnum=800, col=15, text='Line 800',
+ nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''},
+ }
+ eq(list, getlist())
+ eq(('%s-1.res'):format(file), funcs.bufname(list[1].bufnr))
+ eq(('%s-2.res'):format(file), funcs.bufname(list[2].bufnr))
+
+ -- Run cfile/lfile from a modified buffer
+ command('enew!')
+ curbufmeths.set_lines(1, 1, true, {'Quickfix'})
+ eq(('Vim(%s):E37: No write since last change (add ! to override)'):format(
+ filecmd),
+ exc_exec(('%s %s'):format(filecmd, file)))
+
+ write_file(file, ([[
+ %s-3.res:900:30:Line 900
+ ]]):format(file))
+ command(('%s %s'):format(addfcmd, file))
+ list[#list + 1] = {
+ lnum=900, col=30, text='Line 900',
+ nr=-1, bufnr=5, valid=1, pattern='', vcol=0, ['type']='',
+ }
+ eq(list, getlist())
+ eq(('%s-3.res'):format(file), funcs.bufname(list[3].bufnr))
+
+ write_file(file, ([[
+ %s-1.res:222:77:Line 222
+ %s-2.res:333:88:Line 333
+ ]]):format(file, file))
+ command('enew!')
+ command(('%s %s'):format(getfcmd, file))
+ list = {
+ {lnum=222, col=77, text='Line 222',
+ nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''},
+ {lnum=333, col=88, text='Line 333',
+ nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''},
+ }
+ eq(list, getlist())
+ eq(('%s-1.res'):format(file), funcs.bufname(list[1].bufnr))
+ eq(('%s-2.res'):format(file), funcs.bufname(list[2].bufnr))
+ end)
+ end)
+end
diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua
index 4ac9f312ef..9c2687971e 100644
--- a/test/functional/ex_cmds/write_spec.lua
+++ b/test/functional/ex_cmds/write_spec.lua
@@ -1,15 +1,28 @@
local helpers = require('test.functional.helpers')(after_each)
+local lfs = require('lfs')
local eq, eval, clear, write_file, execute, source, insert =
helpers.eq, helpers.eval, helpers.clear, helpers.write_file,
helpers.execute, helpers.source, helpers.insert
+local redir_exec = helpers.redir_exec
+local exc_exec = helpers.exc_exec
+local command = helpers.command
+local funcs = helpers.funcs
+local meths = helpers.meths
if helpers.pending_win32(pending) then return end
+local fname = 'Xtest-functional-ex_cmds-write'
+local fname_bak = fname .. '~'
+local fname_broken = fname_bak .. 'broken'
+
describe(':write', function()
local function cleanup()
os.remove('test_bkc_file.txt')
os.remove('test_bkc_link.txt')
os.remove('test_fifo')
+ os.remove(fname)
+ os.remove(fname_bak)
+ os.remove(fname_broken)
end
before_each(function()
clear()
@@ -63,4 +76,34 @@ describe(':write', function()
eq(text.."\n", fifo:read("*all"))
fifo:close()
end)
+
+ it('errors out correctly', function()
+ command('let $HOME=""')
+ eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~'))
+ -- Message from check_overwrite
+ eq(('\nE17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'),
+ redir_exec('write .'))
+ meths.set_option('writeany', true)
+ -- Message from buf_write
+ eq(('\nE502: "." is a directory'),
+ redir_exec('write .'))
+ funcs.mkdir(fname_bak)
+ meths.set_option('backupdir', '.')
+ meths.set_option('backup', true)
+ write_file(fname, 'content0')
+ eq(0, exc_exec('edit ' .. fname))
+ funcs.setline(1, 'TTY')
+ eq('Vim(write):E510: Can\'t make backup file (add ! to override)',
+ exc_exec('write'))
+ meths.set_option('backup', false)
+ funcs.setfperm(fname, 'r--------')
+ eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)',
+ exc_exec('write'))
+ os.remove(fname)
+ os.remove(fname_bak)
+ write_file(fname_bak, 'TTYX')
+ lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true)
+ eq('Vim(write):E166: Can\'t open linked file for writing',
+ exc_exec('write!'))
+ end)
end)
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 13a0cff137..335cf3c3ff 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -16,6 +16,7 @@ local eq = global_helpers.eq
local ok = global_helpers.ok
local map = global_helpers.map
local filter = global_helpers.filter
+local dedent = global_helpers.dedent
local start_dir = lfs.currentdir()
-- XXX: NVIM_PROG takes precedence, QuickBuild sets it.
@@ -23,7 +24,7 @@ local nvim_prog = os.getenv('NVIM_PROG') or os.getenv('NVIM_PRG') or 'build/bin/
-- Default settings for the test session.
local nvim_set = 'set shortmess+=I background=light noswapfile noautoindent'
..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
- ..' belloff= noshowcmd noruler'
+ ..' belloff= noshowcmd noruler nomore'
local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
'--cmd', nvim_set, '--embed'}
@@ -191,28 +192,6 @@ local function nvim_feed(input)
end
end
-local function dedent(str)
- -- find minimum common indent across lines
- local indent = nil
- for line in str:gmatch('[^\n]+') do
- local line_indent = line:match('^%s+') or ''
- if indent == nil or #line_indent < #indent then
- indent = line_indent
- end
- end
- if indent == nil or #indent == 0 then
- -- no minimum common indent
- return str
- end
- -- create a pattern for the indent
- indent = indent:gsub('%s', '[ \t]')
- -- strip it from the first line
- str = str:gsub('^'..indent, '')
- -- strip it from the remaining lines
- str = str:gsub('[\n]'..indent, '\n')
- return str
-end
-
local function feed(...)
for _, v in ipairs({...}) do
nvim_feed(dedent(v))
@@ -570,6 +549,10 @@ local curbufmeths = create_callindex(curbuf)
local curwinmeths = create_callindex(curwin)
local curtabmeths = create_callindex(curtab)
+local function get_pathsep()
+ return funcs.fnamemodify('.', ':p'):sub(-1)
+end
+
local M = {
prepend_argv = prepend_argv,
clear = clear,
@@ -635,6 +618,7 @@ local M = {
tmpname = tmpname,
meth_pcall = meth_pcall,
NIL = mpack.NIL,
+ get_pathsep = get_pathsep,
}
return function(after_each)
diff --git a/test/functional/legacy/051_highlight_spec.lua b/test/functional/legacy/051_highlight_spec.lua
index ef392d8c67..d4d9b7d997 100644
--- a/test/functional/legacy/051_highlight_spec.lua
+++ b/test/functional/legacy/051_highlight_spec.lua
@@ -16,7 +16,7 @@ describe(':highlight', function()
local screen = Screen.new(35, 10)
screen:attach()
-- Basic test if ":highlight" doesn't crash
- execute('highlight')
+ execute('set more', 'highlight')
-- FIXME(tarruda): We need to be sure the prompt is displayed before
-- continuing, or risk a race condition where some of the following input
-- is discarded resulting in test failure
diff --git a/test/functional/legacy/062_tab_pages_spec.lua b/test/functional/legacy/062_tab_pages_spec.lua
index d5b10b160e..71a0a77354 100644
--- a/test/functional/legacy/062_tab_pages_spec.lua
+++ b/test/functional/legacy/062_tab_pages_spec.lua
@@ -99,10 +99,6 @@ describe('tab pages', function()
eq(7, eval('tabpagenr()'))
execute('tabmove')
eq(10, eval('tabpagenr()'))
- execute('tabmove -20')
- eq(1, eval('tabpagenr()'))
- execute('tabmove +20')
- eq(10, eval('tabpagenr()'))
execute('0tabmove')
eq(1, eval('tabpagenr()'))
execute('$tabmove')
@@ -172,7 +168,7 @@ describe('tab pages', function()
C tabnext 1
autocmd TabDestructive TabEnter * nested
\ :C tabnext 2 | C tabclose 3
- C tabnext 3
+ C tabnext 2
let g:r+=[tabpagenr().'/'.tabpagenr('$')]
endfunction
call Test()
@@ -233,22 +229,14 @@ describe('tab pages', function()
WinEnter
TabEnter
BufEnter
- === tabnext 3 ===
- BufLeave
- WinLeave
- TabLeave
- WinEnter
- TabEnter
=== tabnext 2 ===
- BufLeave
WinLeave
TabLeave
WinEnter
TabEnter
=== tabnext 2 ===
=== tabclose 3 ===
- BufEnter
- === tabclose 3 ===
2/2]])
+ eq(2, eval("tabpagenr('$')"))
end)
end)
diff --git a/test/functional/legacy/063_match_and_matchadd_spec.lua b/test/functional/legacy/063_match_and_matchadd_spec.lua
index 298e0a31ea..5818bb6b3a 100644
--- a/test/functional/legacy/063_match_and_matchadd_spec.lua
+++ b/test/functional/legacy/063_match_and_matchadd_spec.lua
@@ -97,11 +97,10 @@ describe('063: Test for ":match", "matchadd()" and related functions', function(
-- Check that "setmatches()" will not add two matches with the same ID. The
-- expected behaviour (for now) is to add the first match but not the
- -- second and to return 0 (even though it is a matter of debate whether
- -- this can be considered successful behaviour).
+ -- second and to return -1.
execute("let r1 = setmatches([{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1}, {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 10, 'id': 1}])")
feed("<cr>")
- eq(0, eval("r1"))
+ eq(-1, eval("r1"))
eq({{group = 'MyGroup1', pattern = 'TODO', priority = 10, id = 1}}, eval('getmatches()'))
-- Check that "setmatches()" returns 0 if successful and otherwise -1.
diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua
index f5e3522972..b9075620dc 100644
--- a/test/functional/legacy/arglist_spec.lua
+++ b/test/functional/legacy/arglist_spec.lua
@@ -269,4 +269,33 @@ describe('argument list commands', function()
eq(0, eval('argidx()'))
execute('%argd')
end)
+
+
+ it('test for autocommand that redefines the argument list, when doing ":all"', function()
+ execute('autocmd BufReadPost Xxx2 next Xxx2 Xxx1')
+ execute("call writefile(['test file Xxx1'], 'Xxx1')")
+ execute("call writefile(['test file Xxx2'], 'Xxx2')")
+ execute("call writefile(['test file Xxx3'], 'Xxx3')")
+
+ execute('new')
+ -- redefine arglist; go to Xxx1
+ execute('next! Xxx1 Xxx2 Xxx3')
+ -- open window for all args
+ execute('all')
+ eq('test file Xxx1', eval('getline(1)'))
+ execute('wincmd w')
+ execute('wincmd w')
+ eq('test file Xxx1', eval('getline(1)'))
+ -- should now be in Xxx2
+ execute('rewind')
+ eq('test file Xxx2', eval('getline(1)'))
+
+ execute('autocmd! BufReadPost Xxx2')
+ execute('enew! | only')
+ execute("call delete('Xxx1')")
+ execute("call delete('Xxx2')")
+ execute("call delete('Xxx3')")
+ execute('argdelete Xxx*')
+ execute('bwipe! Xxx1 Xxx2 Xxx3')
+ end)
end)
diff --git a/test/functional/normal/fold_spec.lua b/test/functional/normal/fold_spec.lua
index a2a2a35a8b..fc055c4e7a 100644
--- a/test/functional/normal/fold_spec.lua
+++ b/test/functional/normal/fold_spec.lua
@@ -5,9 +5,16 @@ local insert = helpers.insert
local feed = helpers.feed
local expect = helpers.expect
local execute = helpers.execute
+local funcs = helpers.funcs
+local foldlevel = funcs.foldlevel
+local foldclosedend = funcs.foldclosedend
+local eq = helpers.eq
describe('Folds', function()
+ local tempfname = 'Xtest-fold.txt'
clear()
+ before_each(function() execute('enew!') end)
+ after_each(function() os.remove(tempfname) end)
it('manual folding adjusts with filter', function()
insert([[
1
@@ -44,4 +51,293 @@ describe('Folds', function()
8
9]])
end)
+ describe('adjusting folds after :move', function()
+ local function manually_fold_indent()
+ -- setting foldmethod twice is a trick to get vim to set the folds for me
+ execute('set foldmethod=indent', 'set foldmethod=manual')
+ -- Ensure that all folds will get closed (makes it easier to test the
+ -- length of folds).
+ execute('set foldminlines=0')
+ -- Start with all folds open (so :move ranges aren't affected by closed
+ -- folds).
+ execute('%foldopen!')
+ end
+
+ local function get_folds()
+ local rettab = {}
+ for i = 1, funcs.line('$') do
+ table.insert(rettab, foldlevel(i))
+ end
+ return rettab
+ end
+
+ local function test_move_indent(insert_string, move_command)
+ -- This test is easy because we just need to ensure that the resulting
+ -- fold is the same as calculated when creating folds from scratch.
+ insert(insert_string)
+ execute(move_command)
+ local after_move_folds = get_folds()
+ -- Doesn't change anything, but does call foldUpdateAll()
+ execute('set foldminlines=0')
+ eq(after_move_folds, get_folds())
+ -- Set up the buffer with insert_string for the manual fold testing.
+ execute('enew!')
+ insert(insert_string)
+ manually_fold_indent()
+ execute(move_command)
+ end
+
+ it('neither closes nor corrupts folds', function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+ a
+ a
+a
+ a
+ a
+ a
+ a
+ a
+a
+ a
+ a
+ a
+ a
+ a]], '7,12m0')
+ expect([[
+a
+ a
+ a
+ a
+ a
+ a
+a
+ a
+ a
+ a
+ a
+ a
+a
+ a
+ a
+ a
+ a
+ a]])
+ -- lines are not closed, folds are correct
+ for i = 1,funcs.line('$') do
+ eq(-1, funcs.foldclosed(i))
+ if i == 1 or i == 7 or i == 13 then
+ eq(0, foldlevel(i))
+ elseif i == 4 then
+ eq(2, foldlevel(i))
+ else
+ eq(1, foldlevel(i))
+ end
+ end
+ -- folds are not corrupted
+ feed('zM')
+ eq(6, foldclosedend(2))
+ eq(12, foldclosedend(8))
+ eq(18, foldclosedend(14))
+ end)
+ it("doesn't split a fold when the move is within it", function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+ a
+ a
+ a
+ a
+ a
+a]], '5m6')
+ eq({0, 1, 1, 2, 2, 2, 2, 1, 1, 0}, get_folds())
+ end)
+ it('truncates folds that end in the moved range', function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+ a
+a
+a]], '4,5m6')
+ eq({0, 1, 2, 0, 0, 0, 0}, get_folds())
+ end)
+ it('moves folds that start between moved range and destination', function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+ a
+a
+a
+ a
+ a
+ a
+a
+a
+ a]], '3,4m$')
+ eq({0, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1, 0, 0}, get_folds())
+ end)
+ it('does not affect folds outside changed lines', function()
+ test_move_indent([[
+ a
+ a
+ a
+a
+a
+a
+ a
+ a
+ a]], '4m5')
+ eq({1, 1, 1, 0, 0, 0, 1, 1, 1}, get_folds())
+ end)
+ it('moves and truncates folds that start in moved range', function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+ a
+a
+a
+a
+a
+a]], '1,3m7')
+ eq({0, 0, 0, 0, 0, 1, 2, 0, 0, 0}, get_folds())
+ end)
+ it('breaks a fold when moving text into it', function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+ a
+a
+a]], '$m4')
+ eq({0, 1, 2, 2, 0, 0, 0}, get_folds())
+ end)
+ it('adjusts correctly when moving a range backwards', function()
+ test_move_indent([[
+a
+ a
+ a
+ a
+a]], '2,3m0')
+ eq({1, 2, 0, 0, 0}, get_folds())
+ end)
+ end)
+ it('updates correctly on :read', function()
+ -- luacheck: ignore 621
+ helpers.write_file(tempfname, [[
+ a
+
+
+ a]])
+ insert([[
+ a
+ a
+ a
+ a
+ ]])
+ execute('set foldmethod=indent', '2', '%foldopen')
+ execute('read ' .. tempfname)
+ -- Just to check we have the correct file text.
+ expect([[
+ a
+ a
+ a
+
+
+ a
+ a
+ a
+ ]])
+ for i = 1,2 do
+ eq(1, funcs.foldlevel(i))
+ end
+ for i = 3,5 do
+ eq(0, funcs.foldlevel(i))
+ end
+ for i = 6,8 do
+ eq(1, funcs.foldlevel(i))
+ end
+ end)
+ it('combines folds when removing separating space', function()
+ -- luacheck: ignore 621
+ insert([[
+ a
+ a
+ a
+ a
+ a
+ a
+ a
+ a
+ ]])
+ execute('set foldmethod=indent', '3,5d')
+ eq(5, funcs.foldclosedend(1))
+ end)
+ it("doesn't combine folds that have a specified end", function()
+ insert([[
+ {{{
+ }}}
+
+
+
+ {{{
+
+ }}}
+ ]])
+ execute('set foldmethod=marker', '3,5d', '%foldclose')
+ eq(2, funcs.foldclosedend(1))
+ end)
+ it('splits folds according to >N and <N with foldexpr', function()
+ helpers.source([[
+ function TestFoldExpr(lnum)
+ let thisline = getline(a:lnum)
+ if thisline == 'a'
+ return 1
+ elseif thisline == 'b'
+ return 0
+ elseif thisline == 'c'
+ return '<1'
+ elseif thisline == 'd'
+ return '>1'
+ endif
+ return 0
+ endfunction
+ ]])
+ helpers.write_file(tempfname, [[
+ b
+ b
+ a
+ a
+ d
+ a
+ a
+ c]])
+ insert([[
+ a
+ a
+ a
+ a
+ a
+ a
+ ]])
+ execute('set foldmethod=expr', 'set foldexpr=TestFoldExpr(v:lnum)', '2', 'foldopen')
+ execute('read ' .. tempfname, '%foldclose')
+ eq(2, funcs.foldclosedend(1))
+ eq(0, funcs.foldlevel(3))
+ eq(0, funcs.foldlevel(4))
+ eq(6, funcs.foldclosedend(5))
+ eq(10, funcs.foldclosedend(7))
+ eq(14, funcs.foldclosedend(11))
+ end)
end)
diff --git a/test/functional/options/pastetoggle_spec.lua b/test/functional/options/pastetoggle_spec.lua
new file mode 100644
index 0000000000..e449df31f5
--- /dev/null
+++ b/test/functional/options/pastetoggle_spec.lua
@@ -0,0 +1,37 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local feed = helpers.feed
+local execute = helpers.execute
+local eq = helpers.eq
+local eval = helpers.eval
+local sleep = helpers.sleep
+
+describe("'pastetoggle' option", function()
+ before_each(function()
+ clear()
+ execute('set nopaste')
+ execute('set pastetoggle=a')
+ end)
+ it("toggles 'paste'", function()
+ eq(eval('&paste'), 0)
+ feed('a')
+ -- Need another key so that the vgetorpeek() function returns.
+ feed('j')
+ eq(eval('&paste'), 1)
+ end)
+ it("multiple key 'pastetoggle' is waited for", function()
+ eq(eval('&paste'), 0)
+ local pastetoggle = 'lllll'
+ execute('set pastetoggle=' .. pastetoggle)
+ execute('set timeoutlen=1', 'set ttimoutlen=10000')
+ feed(pastetoggle:sub(0, 2))
+ -- sleep() for long enough that vgetorpeek() is gotten into, but short
+ -- enough that ttimeoutlen is not reached.
+ sleep(200)
+ feed(pastetoggle:sub(3, -1))
+ -- Need another key so that the vgetorpeek() function returns.
+ feed('j')
+ eq(eval('&paste'), 1)
+ end)
+end)
diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua
index 32598fc399..ca44026852 100644
--- a/test/functional/shada/shada_spec.lua
+++ b/test/functional/shada/shada_spec.lua
@@ -180,8 +180,7 @@ describe('ShaDa support code', function()
nvim_command('undo')
nvim_command('set shada+=%')
nvim_command('wshada! ' .. shada_fname)
- local readme_fname = paths.test_source_path .. '/README.md'
- readme_fname = helpers.eval( 'resolve("' .. readme_fname .. '")' )
+ local readme_fname = funcs.resolve(paths.test_source_path) .. '/README.md'
eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname))
nvim_command('set shada+=r~')
nvim_command('wshada! ' .. shada_fname)
@@ -189,7 +188,8 @@ describe('ShaDa support code', function()
nvim_command('set shada-=r~')
nvim_command('wshada! ' .. shada_fname)
eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname))
- nvim_command('set shada+=r' .. paths.test_source_path)
+ nvim_command('set shada+=r' .. funcs.escape(
+ funcs.escape(paths.test_source_path, '$~'), ' "\\,'))
nvim_command('wshada! ' .. shada_fname)
eq({}, find_file(readme_fname))
end)
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index d990f92c3a..84f14585fa 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -60,16 +60,17 @@ describe('terminal cursor', function()
]])
end)
- pending('is positioned correctly when focused', function()
+ it('is positioned correctly when focused', function()
feed('i')
+ helpers.wait()
screen:expect([[
- 1 tty ready |
- 2 {1: } |
- 3 |
- 4 |
- 5 |
- 6 |
- -- TERMINAL -- |
+ {7: 1 }tty ready |
+ {7: 2 }{1: } |
+ {7: 3 } |
+ {7: 4 } |
+ {7: 5 } |
+ {7: 6 } |
+ {3:-- TERMINAL --} |
]])
end)
end)
diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua
index 81649f2bde..4ead288a19 100644
--- a/test/functional/terminal/scrollback_spec.lua
+++ b/test/functional/terminal/scrollback_spec.lua
@@ -8,6 +8,7 @@ local command = helpers.command
local wait = helpers.wait
local retry = helpers.retry
local curbufmeths = helpers.curbufmeths
+local nvim = helpers.nvim
local feed_data = thelpers.feed_data
if helpers.pending_win32(pending) then return end
@@ -368,6 +369,12 @@ describe("'scrollback' option", function()
clear()
end)
+ local function set_fake_shell()
+ -- shell-test.c is a fake shell that prints its arguments and exits.
+ nvim('set_option', 'shell', nvim_dir..'/shell-test')
+ nvim('set_option', 'shellcmdflag', 'EXE')
+ end
+
local function expect_lines(expected, epsilon)
local ep = epsilon and epsilon or 0
local actual = eval("line('$')")
@@ -421,12 +428,13 @@ describe("'scrollback' option", function()
screen:detach()
end)
- it('defaults to 1000', function()
- execute('terminal')
+ it('defaults to 1000 in terminal buffers', function()
+ set_fake_shell()
+ command('terminal')
eq(1000, curbufmeths.get_option('scrollback'))
end)
- it('error if set to invalid values', function()
+ it('error if set to invalid value', function()
local status, rv = pcall(command, 'set scrollback=-2')
eq(false, status) -- assert failure
eq('E474:', string.match(rv, "E%d*:"))
@@ -437,15 +445,32 @@ describe("'scrollback' option", function()
end)
it('defaults to -1 on normal buffers', function()
- execute('new')
+ command('new')
eq(-1, curbufmeths.get_option('scrollback'))
end)
- it('error if set on a normal buffer', function()
+ it(':setlocal in a normal buffer is an error', function()
command('new')
- execute('set scrollback=42')
+ execute('setlocal scrollback=42')
feed('<CR>')
eq('E474:', string.match(eval("v:errmsg"), "E%d*:"))
+ eq(-1, curbufmeths.get_option('scrollback'))
+ end)
+
+ it(':set updates local value and global default', function()
+ set_fake_shell()
+ command('set scrollback=42') -- set global and (attempt) local
+ eq(-1, curbufmeths.get_option('scrollback')) -- normal buffer: -1
+ command('terminal')
+ eq(42, curbufmeths.get_option('scrollback')) -- inherits global default
+ command('setlocal scrollback=99')
+ eq(99, curbufmeths.get_option('scrollback'))
+ command('set scrollback<') -- reset to global default
+ eq(42, curbufmeths.get_option('scrollback'))
+ command('setglobal scrollback=734') -- new global default
+ eq(42, curbufmeths.get_option('scrollback')) -- local value did not change
+ command('terminal')
+ eq(734, curbufmeths.get_option('scrollback'))
end)
end)
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 0e5c437c28..90051b8cd5 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -322,6 +322,7 @@ describe("tui 't_Co' (terminal colors)", function()
helpers.nvim_prog))
thelpers.feed_data(":echo &t_Co\n")
+ helpers.wait()
local tline
if maxcolors == 8 then
tline = "~ "
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
new file mode 100644
index 0000000000..02e9422781
--- /dev/null
+++ b/test/functional/ui/cursor_spec.lua
@@ -0,0 +1,192 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear, meths = helpers.clear, helpers.meths
+local eq = helpers.eq
+local command = helpers.command
+
+describe('ui/cursor', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(25, 5)
+ screen:attach()
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ it("'guicursor' is published as a UI event", function()
+ local expected_cursor_style = {
+ cmdline_hover = {
+ mouse_shape = 0,
+ short_name = 'e' },
+ cmdline_insert = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 25,
+ cursor_shape = 'vertical',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'ci' },
+ cmdline_normal = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 0,
+ cursor_shape = 'block',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'c' },
+ cmdline_replace = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 20,
+ cursor_shape = 'horizontal',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'cr' },
+ insert = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 25,
+ cursor_shape = 'vertical',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'i' },
+ more = {
+ mouse_shape = 0,
+ short_name = 'm' },
+ more_lastline = {
+ mouse_shape = 0,
+ short_name = 'ml' },
+ normal = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 0,
+ cursor_shape = 'block',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'n' },
+ operator = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 50,
+ cursor_shape = 'horizontal',
+ hl_id = 46,
+ id_lm = 46,
+ mouse_shape = 0,
+ short_name = 'o' },
+ replace = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 20,
+ cursor_shape = 'horizontal',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'r' },
+ showmatch = {
+ blinkoff = 150,
+ blinkon = 175,
+ blinkwait = 175,
+ cell_percentage = 0,
+ cursor_shape = 'block',
+ hl_id = 46,
+ id_lm = 46,
+ short_name = 'sm' },
+ statusline_drag = {
+ mouse_shape = 0,
+ short_name = 'sd' },
+ statusline_hover = {
+ mouse_shape = 0,
+ short_name = 's' },
+ visual = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 0,
+ cursor_shape = 'block',
+ hl_id = 46,
+ id_lm = 47,
+ mouse_shape = 0,
+ short_name = 'v' },
+ visual_select = {
+ blinkoff = 250,
+ blinkon = 400,
+ blinkwait = 700,
+ cell_percentage = 35,
+ cursor_shape = 'vertical',
+ hl_id = 46,
+ id_lm = 46,
+ mouse_shape = 0,
+ short_name = 've' },
+ vsep_drag = {
+ mouse_shape = 0,
+ short_name = 'vd' },
+ vsep_hover = {
+ mouse_shape = 0,
+ short_name = 'vs' }
+ }
+
+ screen:expect(function()
+ -- Default 'guicursor' published on startup.
+ eq(expected_cursor_style, screen._cursor_style)
+ eq(true, screen._cursor_style_enabled)
+ eq('normal', screen.mode)
+ end)
+
+ -- Event is published ONLY if the cursor style changed.
+ screen._cursor_style = nil
+ command("echo 'test'")
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ test |
+ ]], nil, nil, function()
+ eq(nil, screen._cursor_style)
+ end)
+
+ -- Change the cursor style.
+ meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173,ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42')
+ screen:expect(function()
+ eq('vertical', screen._cursor_style.normal.cursor_shape)
+ eq('horizontal', screen._cursor_style.visual_select.cursor_shape)
+ eq('vertical', screen._cursor_style.operator.cursor_shape)
+ eq('block', screen._cursor_style.insert.cursor_shape)
+ eq('vertical', screen._cursor_style.showmatch.cursor_shape)
+ eq(171, screen._cursor_style.normal.blinkwait)
+ eq(172, screen._cursor_style.normal.blinkoff)
+ eq(173, screen._cursor_style.normal.blinkon)
+ end)
+ end)
+
+ it("empty 'guicursor' sets cursor_shape=block in all modes", function()
+ meths.set_option('guicursor', '')
+ screen:expect(function()
+ -- Empty 'guicursor' sets enabled=false.
+ eq(false, screen._cursor_style_enabled)
+ for _, m in ipairs({ 'cmdline_insert', 'cmdline_normal', 'cmdline_replace', 'insert',
+ 'showmatch', 'normal', 'replace', 'visual',
+ 'visual_select', }) do
+ eq('block', screen._cursor_style[m].cursor_shape)
+ eq(0, screen._cursor_style[m].blinkon)
+ end
+ end)
+ end)
+
+end)
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index 945b16ef92..05cf3231ea 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -200,58 +200,31 @@ describe('Default highlight groups', function()
it('insert mode text', function()
feed('i')
+ screen:try_resize(53, 4)
screen:expect([[
^ |
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
{1:-- INSERT --} |
]], {[0] = {bold=true, foreground=Screen.colors.Blue},
[1] = {bold = true}})
end)
it('end of file markers', function()
+ screen:try_resize(53, 4)
screen:expect([[
^ |
{1:~ }|
{1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
|
]], {[1] = {bold = true, foreground = Screen.colors.Blue}})
end)
it('"wait return" text', function()
+ screen:try_resize(53, 4)
feed(':ls<cr>')
screen:expect([[
{0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
:ls |
1 %a "[No Name]" line 1 |
{1:Press ENTER or type command to continue}^ |
@@ -259,23 +232,15 @@ describe('Default highlight groups', function()
[1] = {bold = true, foreground = Screen.colors.SeaGreen}})
feed('<cr>') -- skip the "Press ENTER..." state or tests will hang
end)
+
it('can be cleared and linked to other highlight groups', function()
+ screen:try_resize(53, 4)
execute('highlight clear ModeMsg')
feed('i')
screen:expect([[
^ |
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
-- INSERT -- |
]], {[0] = {bold=true, foreground=Screen.colors.Blue},
[1] = {bold=true}})
@@ -287,21 +252,13 @@ describe('Default highlight groups', function()
^ |
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
{1:-- INSERT --} |
]], {[0] = {bold=true, foreground=Screen.colors.Blue},
[1] = {foreground = Screen.colors.Red, background = Screen.colors.Green}})
end)
+
it('can be cleared by assigning NONE', function()
+ screen:try_resize(53, 4)
execute('syn keyword TmpKeyword neovim')
execute('hi link TmpKeyword ErrorMsg')
insert('neovim')
@@ -309,16 +266,6 @@ describe('Default highlight groups', function()
{1:neovi^m} |
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
|
]], {
[0] = {bold=true, foreground=Screen.colors.Blue},
@@ -330,18 +277,34 @@ describe('Default highlight groups', function()
neovi^m |
{0:~ }|
{0:~ }|
+ |
+ ]], {[0] = {bold=true, foreground=Screen.colors.Blue}})
+ end)
+
+ it('Whitespace highlight', function()
+ screen:try_resize(53, 4)
+ execute('highlight NonText gui=NONE guifg=#FF0000')
+ execute('set listchars=space:.,tab:>-,trail:*,eol:¬ list')
+ insert(' ne \t o\tv im ')
+ screen:expect([[
+ ne{0:.>----.}o{0:>-----}v{0:..}im{0:*^*¬} |
{0:~ }|
{0:~ }|
+ |
+ ]], {
+ [0] = {foreground=Screen.colors.Red},
+ [1] = {foreground=Screen.colors.Blue},
+ })
+ execute('highlight Whitespace gui=NONE guifg=#0000FF')
+ screen:expect([[
+ ne{1:.>----.}o{1:>-----}v{1:..}im{1:*^*}{0:¬} |
{0:~ }|
{0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- |
- ]], {[0] = {bold=true, foreground=Screen.colors.Blue}})
+ :highlight Whitespace gui=NONE guifg=#0000FF |
+ ]], {
+ [0] = {foreground=Screen.colors.Red},
+ [1] = {foreground=Screen.colors.Blue},
+ })
end)
end)
@@ -401,7 +364,7 @@ describe('guisp (special/undercurl)', function()
end)
end)
-describe("'cursorline' with 'listchars'", function()
+describe("'listchars' highlight", function()
local screen
before_each(function()
@@ -510,7 +473,7 @@ describe("'cursorline' with 'listchars'", function()
},
})
execute('highlight clear ModeMsg')
- execute('highlight SpecialKey guifg=#FF0000')
+ execute('highlight Whitespace guifg=#FF0000')
execute('set cursorline')
execute('set tabstop=8')
execute('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list')
@@ -606,7 +569,7 @@ describe("'cursorline' with 'listchars'", function()
},
})
execute('highlight clear ModeMsg')
- execute('highlight SpecialKey guifg=#FF0000')
+ execute('highlight Whitespace guifg=#FF0000')
execute('set cursorline')
execute('set tabstop=8')
execute('set nowrap')
@@ -644,4 +607,41 @@ describe("'cursorline' with 'listchars'", function()
|
]])
end)
+
+ it("'cursorline' with :match", function()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ [1] = {background=Screen.colors.Grey90},
+ [2] = {foreground=Screen.colors.Red},
+ [3] = {foreground=Screen.colors.Green1},
+ })
+ execute('highlight clear ModeMsg')
+ execute('highlight Whitespace guifg=#FF0000')
+ execute('highlight Error guifg=#00FF00')
+ execute('set nowrap')
+ feed('ia \t bc \t <esc>')
+ screen:expect([[
+ a bc ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ execute('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list')
+ screen:expect([[
+ a{2:.>-----.}bc{2:*>---*^*}{0:¬} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ execute('match Error /\\s\\+$/')
+ screen:expect([[
+ a{2:.>-----.}bc{3:*>---*^*}{0:¬} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index b2fbedfb5e..ecbd5642d1 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -6,7 +6,7 @@ local eq, funcs = helpers.eq, helpers.funcs
if helpers.pending_win32(pending) then return end
-describe('Mouse input', function()
+describe('ui/mouse/input', function()
local screen
before_each(function()
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 54f43387dc..2f2cc85dab 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -181,6 +181,7 @@ end
-- expected: Expected screen state (string). Each line represents a screen
-- row. Last character of each row (typically "|") is stripped.
-- Common indentation is stripped.
+-- Used as `condition` if NOT a string; must be the ONLY arg then.
-- attr_ids: Expected text attributes. Screen rows are transformed according
-- to this table, as follows: each substring S composed of
-- characters having the same attributes will be substituted by
@@ -191,18 +192,23 @@ end
-- any: true: Succeed if `expected` matches ANY screen line(s).
-- false (default): `expected` must match screen exactly.
function Screen:expect(expected, attr_ids, attr_ignore, condition, any)
- -- remove the last line and dedent
- expected = dedent(expected:gsub('\n[ ]+$', ''))
local expected_rows = {}
- for row in expected:gmatch('[^\n]+') do
- -- the last character should be the screen delimiter
- row = row:sub(1, #row - 1)
- table.insert(expected_rows, row)
- end
- if not any then
- assert(self._height == #expected_rows,
- "Expected screen state's row count(" .. #expected_rows
- .. ') differs from configured height(' .. self._height .. ') of Screen.')
+ if type(expected) ~= "string" then
+ assert(not (attr_ids or attr_ignore or condition or any))
+ condition = expected
+ expected = nil
+ else
+ -- Remove the last line and dedent.
+ expected = dedent(expected:gsub('\n[ ]+$', ''))
+ for row in expected:gmatch('[^\n]+') do
+ row = row:sub(1, #row - 1) -- Last char must be the screen delimiter.
+ table.insert(expected_rows, row)
+ end
+ if not any then
+ assert(self._height == #expected_rows,
+ "Expected screen state's row count(" .. #expected_rows
+ .. ') differs from configured height(' .. self._height .. ') of Screen.')
+ end
end
local ids = attr_ids or self._default_attr_ids
local ignore = attr_ignore or self._default_attr_ignore
@@ -218,7 +224,9 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any)
actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore)
end
- if any then
+ if expected == nil then
+ return
+ elseif any then
-- Search for `expected` anywhere in the screen lines.
local actual_screen_str = table.concat(actual_rows, '\n')
if nil == string.find(actual_screen_str, expected) then
@@ -313,6 +321,8 @@ function Screen:_redraw(updates)
if handler ~= nil then
handler(self, unpack(update[i]))
else
+ assert(self._on_event,
+ "Add Screen:_handle_XXX method or call Screen:set_on_event_handler")
self._on_event(method, update[i])
end
end
@@ -343,6 +353,11 @@ function Screen:_handle_resize(width, height)
}
end
+function Screen:_handle_cursor_style_set(enabled, style)
+ self._cursor_style_enabled = enabled
+ self._cursor_style = style
+end
+
function Screen:_handle_clear()
self:_clear_block(self._scroll_region.top, self._scroll_region.bot,
self._scroll_region.left, self._scroll_region.right)
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index e511234e5e..21953ba294 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -73,33 +73,29 @@ describe('Screen', function()
describe(':suspend', function()
it('is forwarded to the UI', function()
local function check()
- if not screen.suspended then
- return 'Screen was not suspended'
- end
+ eq(true, screen.suspended)
end
execute('suspend')
- screen:wait(check)
+ screen:expect(check)
screen.suspended = false
feed('<c-z>')
- screen:wait(check)
+ screen:expect(check)
end)
end)
describe('bell/visual bell', function()
it('is forwarded to the UI', function()
feed('<left>')
- screen:wait(function()
- if not screen.bell or screen.visual_bell then
- return 'Bell was not sent'
- end
+ screen:expect(function()
+ eq(true, screen.bell)
+ eq(false, screen.visual_bell)
end)
screen.bell = false
execute('set visualbell')
feed('<left>')
- screen:wait(function()
- if not screen.visual_bell or screen.bell then
- return 'Visual bell was not sent'
- end
+ screen:expect(function()
+ eq(true, screen.visual_bell)
+ eq(false, screen.bell)
end)
end)
end)
@@ -109,22 +105,16 @@ describe('Screen', function()
local expected = 'test-title'
execute('set titlestring='..expected)
execute('set title')
- screen:wait(function()
- local actual = screen.title
- if actual ~= expected then
- return 'Expected title to be "'..expected..'" but was "'..actual..'"'
- end
+ screen:expect(function()
+ eq(expected, screen.title)
end)
end)
it('has correct default title with unnamed file', function()
local expected = '[No Name] - NVIM'
execute('set title')
- screen:wait(function()
- local actual = screen.title
- if actual ~= expected then
- return 'Expected title to be "'..expected..'" but was "'..actual..'"'
- end
+ screen:expect(function()
+ eq(expected, screen.title)
end)
end)
@@ -132,11 +122,8 @@ describe('Screen', function()
local expected = 'myfile (/mydir) - NVIM'
execute('set title')
execute('file /mydir/myfile')
- screen:wait(function()
- local actual = screen.title
- if actual ~= expected then
- return 'Expected title to be "'..expected..'" but was "'..actual..'"'
- end
+ screen:expect(function()
+ eq(expected, screen.title)
end)
end)
end)
@@ -146,11 +133,8 @@ describe('Screen', function()
local expected = 'test-icon'
execute('set iconstring='..expected)
execute('set icon')
- screen:wait(function()
- local actual = screen.icon
- if actual ~= expected then
- return 'Expected title to be "'..expected..'" but was "'..actual..'"'
- end
+ screen:expect(function()
+ eq(expected, screen.icon)
end)
end)
end)
diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua
index cd5f4260e0..3c09d71eb7 100644
--- a/test/functional/viml/completion_spec.lua
+++ b/test/functional/viml/completion_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear, feed = helpers.clear, helpers.feed
local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq
local execute, source, expect = helpers.execute, helpers.source, helpers.expect
+local meths = helpers.meths
if helpers.pending_win32(pending) then return end
@@ -814,6 +815,41 @@ describe('completion', function()
end)
end)
+ describe('with numeric items', function()
+ before_each(function()
+ source([[
+ function! TestComplete() abort
+ call complete(1, g:_complist)
+ return ''
+ endfunction
+ ]])
+ meths.set_option('completeopt', 'menuone,noselect')
+ meths.set_var('_complist', {{
+ word=0,
+ abbr=1,
+ menu=2,
+ kind=3,
+ info=4,
+ icase=5,
+ dup=6,
+ empty=7,
+ }})
+ end)
+
+ it('shows correct variant as word', function()
+ feed('i<C-r>=TestComplete()<CR>')
+ screen:expect([[
+ ^ |
+ {1:1 3 2 }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- INSERT --} |
+ ]])
+ end)
+ end)
end)
describe('External completion popupmenu', function()
diff --git a/test/helpers.lua b/test/helpers.lua
index e5224349c2..3fc10e9e30 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -106,20 +106,33 @@ local uname = (function()
end)
end)()
-local function tmpname()
- local fname = os.tmpname()
- if uname() == 'Windows' and fname:sub(1, 2) == '\\s' then
- -- In Windows tmpname() returns a filename starting with
- -- special sequence \s, prepend $TEMP path
- local tmpdir = os.getenv('TEMP')
- return tmpdir..fname
- elseif fname:match('^/tmp') and uname() == 'Darwin' then
- -- In OS X /tmp links to /private/tmp
- return '/private'..fname
- else
- return fname
- end
-end
+local tmpname = (function()
+ local seq = 0
+ local tmpdir = os.getenv('TMPDIR') and os.getenv('TMPDIR') or os.getenv('TEMP')
+ -- Is $TMPDIR defined local to the project workspace?
+ local in_workspace = not not (tmpdir and string.find(tmpdir, 'Xtest'))
+ return (function()
+ if in_workspace then
+ -- Cannot control os.tmpname() dir, so hack our own tmpname() impl.
+ seq = seq + 1
+ local fname = tmpdir..'/nvim-test-lua-'..seq
+ io.open(fname, 'w'):close()
+ return fname
+ else
+ local fname = os.tmpname()
+ if uname() == 'Windows' and fname:sub(1, 2) == '\\s' then
+ -- In Windows tmpname() returns a filename starting with
+ -- special sequence \s, prepend $TEMP path
+ return tmpdir..fname
+ elseif fname:match('^/tmp') and uname() == 'Darwin' then
+ -- In OS X /tmp links to /private/tmp
+ return '/private'..fname
+ else
+ return fname
+ end
+ end
+ end)
+end)()
local function map(func, tab)
local rettab = {}
@@ -225,6 +238,41 @@ local function which(exe)
end
end
+local function concat_tables(...)
+ local ret = {}
+ for i = 1, select('#', ...) do
+ local tbl = select(i, ...)
+ if tbl then
+ for _, v in ipairs(tbl) do
+ ret[#ret + 1] = v
+ end
+ end
+ end
+ return ret
+end
+
+local function dedent(str)
+ -- find minimum common indent across lines
+ local indent = nil
+ for line in str:gmatch('[^\n]+') do
+ local line_indent = line:match('^%s+') or ''
+ if indent == nil or #line_indent < #indent then
+ indent = line_indent
+ end
+ end
+ if indent == nil or #indent == 0 then
+ -- no minimum common indent
+ return str
+ end
+ -- create a pattern for the indent
+ indent = indent:gsub('%s', '[ \t]')
+ -- strip it from the first line
+ str = str:gsub('^'..indent, '')
+ -- strip it from the remaining lines
+ str = str:gsub('[\n]'..indent, '\n')
+ return str
+end
+
return {
eq = eq,
neq = neq,
@@ -238,4 +286,6 @@ return {
check_cores = check_cores,
hasenv = hasenv,
which = which,
+ concat_tables = concat_tables,
+ dedent = dedent,
}
diff --git a/test/unit/eval/decode_spec.lua b/test/unit/eval/decode_spec.lua
index 2d7597c0f4..0b2a423cd6 100644
--- a/test/unit/eval/decode_spec.lua
+++ b/test/unit/eval/decode_spec.lua
@@ -7,7 +7,7 @@ local eq = helpers.eq
local neq = helpers.neq
local ffi = helpers.ffi
-local decode = cimport('./src/nvim/eval/decode.h', './src/nvim/eval_defs.h',
+local decode = cimport('./src/nvim/eval/decode.h', './src/nvim/eval/typval.h',
'./src/nvim/globals.h', './src/nvim/memory.h',
'./src/nvim/message.h')
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index 1377d5b501..fa76113756 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -5,13 +5,14 @@ local to_cstr = helpers.to_cstr
local ffi = helpers.ffi
local eq = helpers.eq
-local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h',
+local eval = cimport('./src/nvim/eval.h', './src/nvim/eval/typval.h',
'./src/nvim/hashtab.h')
local null_string = {[true]='NULL string'}
local null_list = {[true]='NULL list'}
local null_dict = {[true]='NULL dict'}
local type_key = {[true]='type key'}
+local locks_key = {[true]='locks key'}
local list_type = {[true]='list type'}
local dict_type = {[true]='dict type'}
local func_type = {[true]='func type'}
@@ -23,27 +24,71 @@ local nil_value = {[true]='nil'}
local lua2typvalt
local function li_alloc(nogc)
- local gcfunc = eval.listitem_free
+ local gcfunc = eval.tv_list_item_free
if nogc then gcfunc = nil end
- local li = ffi.gc(eval.listitem_alloc(), gcfunc)
+ local li = ffi.gc(eval.tv_list_item_alloc(), gcfunc)
li.li_next = nil
li.li_prev = nil
li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED}
return li
end
-local function list(...)
- local ret = ffi.gc(eval.list_alloc(), eval.list_unref)
- eq(0, ret.lv_refcount)
- ret.lv_refcount = 1
- for i = 1, select('#', ...) do
- local val = select(i, ...)
- local li_tv = ffi.gc(lua2typvalt(val), nil)
- local li = li_alloc(true)
- li.li_tv = li_tv
- eval.tv_list_append(ret, li)
+local function populate_list(l, lua_l, processed)
+ processed = processed or {}
+ eq(0, l.lv_refcount)
+ l.lv_refcount = 1
+ processed[lua_l] = l
+ for i = 1, #lua_l do
+ local item_tv = ffi.gc(lua2typvalt(lua_l[i], processed), nil)
+ local item_li = eval.tv_list_item_alloc()
+ item_li.li_tv = item_tv
+ eval.tv_list_append(l, item_li)
end
- return ret
+ return l
+end
+
+local function populate_dict(d, lua_d, processed)
+ processed = processed or {}
+ eq(0, d.dv_refcount)
+ d.dv_refcount = 1
+ processed[lua_d] = d
+ for k, v in pairs(lua_d) do
+ if type(k) == 'string' then
+ local di = eval.tv_dict_item_alloc(to_cstr(k))
+ local val_tv = ffi.gc(lua2typvalt(v, processed), nil)
+ eval.tv_copy(val_tv, di.di_tv)
+ eval.tv_clear(val_tv)
+ eval.tv_dict_add(d, di)
+ end
+ end
+ return d
+end
+
+local function populate_partial(pt, lua_pt, processed)
+ processed = processed or {}
+ eq(0, pt.pt_refcount)
+ processed[lua_pt] = pt
+ local argv = nil
+ if lua_pt.args and #lua_pt.args > 0 then
+ argv = ffi.gc(ffi.cast('typval_T*', eval.xmalloc(ffi.sizeof('typval_T') * #lua_pt.args)), nil)
+ for i, arg in ipairs(lua_pt.args) do
+ local arg_tv = ffi.gc(lua2typvalt(arg, processed), nil)
+ argv[i - 1] = arg_tv
+ end
+ end
+ local dict = nil
+ if lua_pt.dict then
+ local dict_tv = ffi.gc(lua2typvalt(lua_pt.dict, processed), nil)
+ assert(dict_tv.v_type == eval.VAR_DICT)
+ dict = dict_tv.vval.v_dict
+ end
+ pt.pt_refcount = 1
+ pt.pt_name = eval.xmemdupz(to_cstr(lua_pt.value), #lua_pt.value)
+ pt.pt_auto = not not lua_pt.auto
+ pt.pt_argc = lua_pt.args and #lua_pt.args or 0
+ pt.pt_argv = argv
+ pt.pt_dict = dict
+ return pt
end
local ptr2key = function(ptr)
@@ -54,6 +99,30 @@ local lst2tbl
local dct2tbl
local typvalt2lua
+
+local function partial2lua(pt, processed)
+ processed = processed or {}
+ local value, auto, dict, argv = nil, nil, nil, nil
+ if pt ~= nil then
+ value = ffi.string(pt.pt_name)
+ auto = pt.pt_auto and true or nil
+ argv = {}
+ for i = 1, pt.pt_argc do
+ argv[i] = typvalt2lua(pt.pt_argv[i - 1], processed)
+ end
+ if pt.pt_dict ~= nil then
+ dict = dct2tbl(pt.pt_dict)
+ end
+ end
+ return {
+ [type_key]=func_type,
+ value=value,
+ auto=auto,
+ args=argv,
+ dict=dict,
+ }
+end
+
local typvalt2lua_tab = nil
local function typvalt2lua_tab_init()
@@ -63,10 +132,10 @@ local function typvalt2lua_tab_init()
typvalt2lua_tab = {
[tonumber(eval.VAR_SPECIAL)] = function(t)
return ({
- [eval.kSpecialVarFalse] = false,
- [eval.kSpecialVarNull] = nil_value,
- [eval.kSpecialVarTrue] = true,
- })[t.vval.v_special]
+ [tonumber(eval.kSpecialVarFalse)] = false,
+ [tonumber(eval.kSpecialVarNull)] = nil_value,
+ [tonumber(eval.kSpecialVarTrue)] = true,
+ })[tonumber(t.vval.v_special)]
end,
[tonumber(eval.VAR_NUMBER)] = function(t)
return {[type_key]=int_type, value=tonumber(t.vval.v_number)}
@@ -96,26 +165,7 @@ local function typvalt2lua_tab_init()
if processed[p_key] then
return processed[p_key]
end
- local pt = t.vval.v_partial
- local value, auto, dict, argv = nil, nil, nil, nil
- if pt ~= nil then
- value = ffi.string(pt.pt_name)
- auto = pt.pt_auto and true or nil
- argv = {}
- for i = 1, pt.pt_argc do
- argv[i] = typvalt2lua(pt.pt_argv[i - 1], processed)
- end
- if pt.pt_dict ~= nil then
- dict = dct2tbl(pt.pt_dict)
- end
- end
- return {
- [type_key]=func_type,
- value=value,
- auto=auto,
- args=argv,
- dict=dict,
- }
+ return partial2lua(t.vval.v_partial, processed)
end,
}
end
@@ -241,7 +291,7 @@ local typvalt = function(typ, vval)
elseif type(typ) == 'string' then
typ = eval[typ]
end
- return ffi.gc(ffi.new('typval_T', {v_type=typ, vval=vval}), eval.clear_tv)
+ return ffi.gc(ffi.new('typval_T', {v_type=typ, vval=vval}), eval.tv_clear)
end
local lua2typvalt_type_tab = {
@@ -256,36 +306,16 @@ local lua2typvalt_type_tab = {
processed[l].lv_refcount = processed[l].lv_refcount + 1
return typvalt(eval.VAR_LIST, {v_list=processed[l]})
end
- local lst = eval.list_alloc()
- lst.lv_refcount = 1
- processed[l] = lst
- local ret = typvalt(eval.VAR_LIST, {v_list=lst})
- for i = 1, #l do
- local item_tv = ffi.gc(lua2typvalt(l[i], processed), nil)
- eval.list_append_tv(lst, item_tv)
- eval.clear_tv(item_tv)
- end
- return ret
+ local lst = populate_list(eval.tv_list_alloc(), l, processed)
+ return typvalt(eval.VAR_LIST, {v_list=lst})
end,
[dict_type] = function(l, processed)
if processed[l] then
processed[l].dv_refcount = processed[l].dv_refcount + 1
return typvalt(eval.VAR_DICT, {v_dict=processed[l]})
end
- local dct = eval.dict_alloc()
- dct.dv_refcount = 1
- processed[l] = dct
- local ret = typvalt(eval.VAR_DICT, {v_dict=dct})
- for k, v in pairs(l) do
- if type(k) == 'string' then
- local di = eval.dictitem_alloc(to_cstr(k))
- local val_tv = ffi.gc(lua2typvalt(v, processed), nil)
- eval.copy_tv(val_tv, di.di_tv)
- eval.clear_tv(val_tv)
- eval.dict_add(dct, di)
- end
- end
- return ret
+ local dct = populate_dict(eval.tv_dict_alloc(), l, processed)
+ return typvalt(eval.VAR_DICT, {v_dict=dct})
end,
[func_type] = function(l, processed)
if processed[l] then
@@ -293,29 +323,8 @@ local lua2typvalt_type_tab = {
return typvalt(eval.VAR_PARTIAL, {v_partial=processed[l]})
end
if l.args or l.dict then
- local pt = ffi.gc(ffi.cast('partial_T*', eval.xmalloc(ffi.sizeof('partial_T'))), nil)
- processed[l] = pt
- local argv = nil
- if l.args and #l.args > 0 then
- argv = ffi.gc(ffi.cast('typval_T*', eval.xmalloc(ffi.sizeof('typval_T') * #l.args)), nil)
- for i, arg in ipairs(l.args) do
- local arg_tv = ffi.gc(lua2typvalt(arg, processed), nil)
- eval.copy_tv(arg_tv, argv[i - 1])
- eval.clear_tv(arg_tv)
- end
- end
- local dict = nil
- if l.dict then
- local dict_tv = ffi.gc(lua2typvalt(l.dict, processed), nil)
- assert(dict_tv.v_type == eval.VAR_DICT)
- dict = dict_tv.vval.v_dict
- end
- pt.pt_refcount = 1
- pt.pt_name = eval.xmemdupz(to_cstr(l.value), #l.value)
- pt.pt_auto = not not l.auto
- pt.pt_argc = l.args and #l.args or 0
- pt.pt_argv = argv
- pt.pt_dict = dict
+ local pt = populate_partial(ffi.gc(ffi.cast('partial_T*',
+ eval.xcalloc(1, ffi.sizeof('partial_T'))), nil), l, processed)
return typvalt(eval.VAR_PARTIAL, {v_partial=pt})
else
return typvalt(eval.VAR_FUNC, {
@@ -368,13 +377,24 @@ lua2typvalt = function(l, processed)
return typvalt(eval.VAR_STRING, {v_string=eval.xmemdupz(to_cstr(l), #l)})
elseif type(l) == 'cdata' then
local tv = typvalt(eval.VAR_UNKNOWN)
- eval.copy_tv(l, tv)
+ eval.tv_copy(l, tv)
return tv
end
end
+local void_ptr = ffi.typeof('void *')
local function void(ptr)
- return ffi.cast('void*', ptr)
+ return ffi.cast(void_ptr, ptr)
+end
+
+local function alloc_len(len, get_ptr)
+ if type(len) == 'string' or type(len) == 'table' then
+ return #len
+ elseif len == nil then
+ return eval.strlen(get_ptr())
+ else
+ return len
+ end
end
local alloc_logging_helpers = {
@@ -382,14 +402,115 @@ local alloc_logging_helpers = {
li = function(li) return {func='malloc', args={ffi.sizeof('listitem_T')}, ret=void(li)} end,
dict = function(d) return {func='malloc', args={ffi.sizeof('dict_T')}, ret=void(d)} end,
di = function(di, size)
+ size = alloc_len(size, function() return di.di_key end)
return {func='malloc', args={ffi.offsetof('dictitem_T', 'di_key') + size + 1}, ret=void(di)}
end,
- str = function(s, size) return {func='malloc', args={size + 1}, ret=void(s)} end,
+ str = function(s, size)
+ size = alloc_len(size, function() return s end)
+ return {func='malloc', args={size + 1}, ret=void(s)}
+ end,
+
+ dwatcher = function(w) return {func='malloc', args={ffi.sizeof('DictWatcher')}, ret=void(w)} end,
- freed = function(p) return {func='free', args={p and void(p)}} end,
+ freed = function(p) return {func='free', args={type(p) == 'table' and p or void(p)}} end,
+
+ -- lua_…: allocated by this file, not by some Neovim function
+ lua_pt = function(pt) return {func='calloc', args={1, ffi.sizeof('partial_T')}, ret=void(pt)} end,
+ lua_tvs = function(argv, argc)
+ argc = alloc_len(argc)
+ return {func='malloc', args={ffi.sizeof('typval_T')*argc}, ret=void(argv)}
+ end,
}
+local function int(n)
+ return {[type_key]=int_type, value=n}
+end
+
+local function list(...)
+ return populate_list(ffi.gc(eval.tv_list_alloc(), eval.tv_list_unref),
+ {...}, {})
+end
+
+local function dict(d)
+ return populate_dict(ffi.gc(eval.tv_dict_alloc(), eval.tv_dict_free),
+ d or {}, {})
+end
+
+local callback2tbl_type_tab = nil
+
+local function init_callback2tbl_type_tab()
+ if callback2tbl_type_tab then
+ return
+ end
+ callback2tbl_type_tab = {
+ [tonumber(eval.kCallbackNone)] = function(_) return {type='none'} end,
+ [tonumber(eval.kCallbackFuncref)] = function(cb)
+ return {type='fref', fref=ffi.string(cb.data.funcref)}
+ end,
+ [tonumber(eval.kCallbackPartial)] = function(cb)
+ local lua_pt = partial2lua(cb.data.partial)
+ return {type='pt', fref=ffi.string(lua_pt.value), pt=lua_pt}
+ end
+ }
+end
+
+local function callback2tbl(cb)
+ init_callback2tbl_type_tab()
+ return callback2tbl_type_tab[tonumber(cb.type)](cb)
+end
+
+local function tbl2callback(tbl)
+ local ret = nil
+ if tbl.type == 'none' then
+ ret = ffi.new('Callback[1]', {{type=eval.kCallbackNone}})
+ elseif tbl.type == 'fref' then
+ ret = ffi.new('Callback[1]', {{type=eval.kCallbackFuncref,
+ data={funcref=eval.xstrdup(tbl.fref)}}})
+ elseif tbl.type == 'pt' then
+ local pt = ffi.gc(ffi.cast('partial_T*',
+ eval.xcalloc(1, ffi.sizeof('partial_T'))), eval.partial_unref)
+ ret = ffi.new('Callback[1]', {{type=eval.kCallbackPartial,
+ data={partial=populate_partial(pt, tbl.pt, {})}}})
+ else
+ assert(false)
+ end
+ return ffi.gc(ffi.cast('Callback*', ret), helpers.callback_free)
+end
+
+local function dict_watchers(d)
+ local ret = {}
+ local h = d.watchers
+ local q = h.next
+ local qs = {}
+ local key_patterns = {}
+ while q ~= h do
+ local qitem = ffi.cast('DictWatcher *',
+ ffi.cast('char *', q) - ffi.offsetof('DictWatcher', 'node'))
+ ret[#ret + 1] = {
+ cb=callback2tbl(qitem.callback),
+ pat=ffi.string(qitem.key_pattern, qitem.key_pattern_len),
+ busy=qitem.busy,
+ }
+ qs[#qs + 1] = qitem
+ key_patterns[#key_patterns + 1] = {qitem.key_pattern, qitem.key_pattern_len}
+ q = q.next
+ end
+ return ret, qs, key_patterns
+end
+
+local function eval0(expr)
+ local tv = ffi.gc(ffi.new('typval_T', {v_type=eval.VAR_UNKNOWN}),
+ eval.tv_clear)
+ if eval.eval0(to_cstr(expr), tv, nil, true) == 0 then
+ return nil
+ else
+ return tv
+ end
+end
+
return {
+ int=int,
+
null_string=null_string,
null_list=null_list,
null_dict=null_dict,
@@ -402,8 +523,10 @@ return {
nil_value=nil_value,
type_key=type_key,
+ locks_key=locks_key,
list=list,
+ dict=dict,
lst2tbl=lst2tbl,
dct2tbl=dct2tbl,
@@ -422,4 +545,12 @@ return {
list_items=list_items,
dict_items=dict_items,
+
+ dict_watchers=dict_watchers,
+ tbl2callback=tbl2callback,
+ callback2tbl=callback2tbl,
+
+ eval0=eval0,
+
+ empty_list = {[type_key]=list_type},
}
diff --git a/test/unit/eval/tricks_spec.lua b/test/unit/eval/tricks_spec.lua
index ec79a9cad5..7aa0f0f6e6 100644
--- a/test/unit/eval/tricks_spec.lua
+++ b/test/unit/eval/tricks_spec.lua
@@ -1,19 +1,15 @@
local helpers = require('test.unit.helpers')(after_each)
+local eval_helpers = require('test.unit.eval.helpers')
+
local itp = helpers.gen_itp(it)
local cimport = helpers.cimport
-local to_cstr = helpers.to_cstr
-local ffi = helpers.ffi
local eq = helpers.eq
-local eval = cimport('./src/nvim/eval.h', './src/nvim/memory.h')
+local eval0 = eval_helpers.eval0
-local eval_expr = function(expr)
- return ffi.gc(eval.eval_expr(to_cstr(expr), nil), function(tv)
- eval.clear_tv(tv)
- eval.xfree(tv)
- end)
-end
+local eval = cimport('./src/nvim/eval.h', './src/nvim/eval/typval.h',
+ './src/nvim/memory.h')
describe('NULL typval_T', function()
itp('is produced by $XXX_UNEXISTENT_VAR_XXX', function()
@@ -25,19 +21,19 @@ describe('NULL typval_T', function()
while os.getenv(unexistent_env) ~= nil do
unexistent_env = unexistent_env .. '_XXX'
end
- local rettv = eval_expr('$' .. unexistent_env)
+ local rettv = eval0('$' .. unexistent_env)
eq(eval.VAR_STRING, rettv.v_type)
eq(nil, rettv.vval.v_string)
end)
itp('is produced by v:_null_list', function()
- local rettv = eval_expr('v:_null_list')
+ local rettv = eval0('v:_null_list')
eq(eval.VAR_LIST, rettv.v_type)
eq(nil, rettv.vval.v_list)
end)
itp('is produced by v:_null_dict', function()
- local rettv = eval_expr('v:_null_dict')
+ local rettv = eval0('v:_null_dict')
eq(eval.VAR_DICT, rettv.v_type)
eq(nil, rettv.vval.v_dict)
end)
diff --git a/test/unit/eval/tv_clear_spec.lua b/test/unit/eval/tv_clear_spec.lua
index 47d4661ad8..ca37301b32 100644
--- a/test/unit/eval/tv_clear_spec.lua
+++ b/test/unit/eval/tv_clear_spec.lua
@@ -14,7 +14,7 @@ local list_items = eval_helpers.list_items
local dict_items = eval_helpers.dict_items
local lua2typvalt = eval_helpers.lua2typvalt
-local lib = cimport('./src/nvim/eval_defs.h', './src/nvim/eval.h')
+local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/eval.h')
local alloc_log = alloc_log_new()
@@ -26,7 +26,7 @@ after_each(function()
alloc_log:after_each()
end)
-describe('clear_tv()', function()
+describe('tv_clear()', function()
itp('successfully frees all lists in [&l [1], *l, *l]', function()
local l_inner = {1}
local list = {l_inner, l_inner, l_inner}
@@ -44,7 +44,7 @@ describe('clear_tv()', function()
a.li(lis[3]),
})
eq(3, list_inner_p.lv_refcount)
- lib.clear_tv(list_tv)
+ lib.tv_clear(list_tv)
alloc_log:check({
a.freed(lis_inner[1]),
a.freed(list_inner_p),
@@ -69,7 +69,7 @@ describe('clear_tv()', function()
a.li(lis[3]),
})
eq(3, list_inner_p.lv_refcount)
- lib.clear_tv(list_tv)
+ lib.tv_clear(list_tv)
alloc_log:check({
a.freed(list_inner_p),
a.freed(lis[1]),
@@ -92,7 +92,7 @@ describe('clear_tv()', function()
a.li(lis[2]),
})
eq(2, dict_inner_p.dv_refcount)
- lib.clear_tv(list_tv)
+ lib.tv_clear(list_tv)
alloc_log:check({
a.freed(dict_inner_p),
a.freed(lis[1]),
@@ -116,7 +116,7 @@ describe('clear_tv()', function()
a.li(lis[2]),
})
eq(2, dict_inner_p.dv_refcount)
- lib.clear_tv(list_tv)
+ lib.tv_clear(list_tv)
alloc_log:check({
a.freed(dis.a),
a.freed(dict_inner_p),
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
new file mode 100644
index 0000000000..258b5c4c1f
--- /dev/null
+++ b/test/unit/eval/typval_spec.lua
@@ -0,0 +1,2933 @@
+local bit = require('bit')
+local helpers = require('test.unit.helpers')(after_each)
+local eval_helpers = require('test.unit.eval.helpers')
+local global_helpers = require('test.helpers')
+
+local itp = helpers.gen_itp(it)
+
+local OK = helpers.OK
+local eq = helpers.eq
+local neq = helpers.neq
+local ffi = helpers.ffi
+local FAIL = helpers.FAIL
+local NULL = helpers.NULL
+local cimport = helpers.cimport
+local to_cstr = helpers.to_cstr
+local alloc_log_new = helpers.alloc_log_new
+
+local a = eval_helpers.alloc_logging_helpers
+local int = eval_helpers.int
+local list = eval_helpers.list
+local dict = eval_helpers.dict
+local eval0 = eval_helpers.eval0
+local lst2tbl = eval_helpers.lst2tbl
+local dct2tbl = eval_helpers.dct2tbl
+local typvalt = eval_helpers.typvalt
+local type_key = eval_helpers.type_key
+local li_alloc = eval_helpers.li_alloc
+local first_di = eval_helpers.first_di
+local nil_value = eval_helpers.nil_value
+local func_type = eval_helpers.func_type
+local null_list = eval_helpers.null_list
+local null_dict = eval_helpers.null_dict
+local dict_items = eval_helpers.dict_items
+local list_items = eval_helpers.list_items
+local empty_list = eval_helpers.empty_list
+local lua2typvalt = eval_helpers.lua2typvalt
+local typvalt2lua = eval_helpers.typvalt2lua
+local null_string = eval_helpers.null_string
+local callback2tbl = eval_helpers.callback2tbl
+local tbl2callback = eval_helpers.tbl2callback
+local dict_watchers = eval_helpers.dict_watchers
+
+local concat_tables = global_helpers.concat_tables
+
+local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
+ './src/nvim/mbyte.h', './src/nvim/garray.h',
+ './src/nvim/eval.h', './src/nvim/vim.h',
+ './src/nvim/globals.h')
+
+local function list_watch_alloc(li)
+ return ffi.cast('listwatch_T*', ffi.new('listwatch_T[1]', {{lw_item=li}}))
+end
+
+local function list_watch(l, li)
+ local lw = list_watch_alloc(li or l.lv_first)
+ lib.tv_list_watch_add(l, lw)
+ return lw
+end
+
+local function get_alloc_rets(exp_log, res)
+ setmetatable(res, {
+ __index={
+ freed=function(r, n) return {func='free', args={r[n]}} end
+ }
+ })
+ for i = 1,#exp_log do
+ if ({malloc=true, calloc=true})[exp_log[i].func] then
+ res[#res + 1] = exp_log[i].ret
+ end
+ end
+ return exp_log
+end
+
+local to_cstr_nofree = function(v) return lib.xstrdup(v) end
+
+local alloc_log = alloc_log_new()
+
+before_each(function()
+ alloc_log:before_each()
+end)
+
+after_each(function()
+ alloc_log:after_each()
+end)
+
+local function ga_alloc(itemsize, growsize)
+ local ga = ffi.gc(ffi.cast('garray_T*', ffi.new('garray_T[1]', {})),
+ lib.ga_clear)
+ lib.ga_init(ga, itemsize or 1, growsize or 80)
+ return ga
+end
+
+local function check_emsg(f, msg)
+ local saved_last_msg_hist = lib.last_msg_hist
+ if saved_last_msg_hist == nil then
+ saved_last_msg_hist = nil
+ end
+ local ret = {f()}
+ if msg ~= nil then
+ eq(msg, ffi.string(lib.last_msg_hist.msg))
+ neq(saved_last_msg_hist, lib.last_msg_hist)
+ else
+ if saved_last_msg_hist ~= lib.last_msg_hist then
+ eq(nil, ffi.string(lib.last_msg_hist.msg))
+ else
+ eq(saved_last_msg_hist, lib.last_msg_hist)
+ end
+ end
+ return unpack(ret)
+end
+
+describe('typval.c', function()
+ describe('list', function()
+ describe('item', function()
+ describe('alloc()/free()', function()
+ itp('works', function()
+ local li = li_alloc(true)
+ neq(nil, li)
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.freed(li),
+ })
+ end)
+ itp('also frees the value', function()
+ local li
+ local s
+ local l
+ local tv
+ li = li_alloc(true)
+ li.li_tv.v_type = lib.VAR_NUMBER
+ li.li_tv.vval.v_number = 10
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.freed(li),
+ })
+
+ li = li_alloc(true)
+ li.li_tv.v_type = lib.VAR_FLOAT
+ li.li_tv.vval.v_float = 10.5
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.freed(li),
+ })
+
+ li = li_alloc(true)
+ li.li_tv.v_type = lib.VAR_STRING
+ li.li_tv.vval.v_string = nil
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.freed(alloc_log.null),
+ a.freed(li),
+ })
+
+ li = li_alloc(true)
+ li.li_tv.v_type = lib.VAR_STRING
+ s = to_cstr_nofree('test')
+ li.li_tv.vval.v_string = s
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.str(s, #('test')),
+ a.freed(s),
+ a.freed(li),
+ })
+
+ li = li_alloc(true)
+ li.li_tv.v_type = lib.VAR_LIST
+ l = ffi.gc(list(), nil)
+ l.lv_refcount = 2
+ li.li_tv.vval.v_list = l
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.list(l),
+ a.freed(li),
+ })
+ eq(1, l.lv_refcount)
+
+ li = li_alloc(true)
+ tv = lua2typvalt({})
+ tv.vval.v_dict.dv_refcount = 2
+ li.li_tv = tv
+ lib.tv_list_item_free(li)
+ alloc_log:check({
+ a.li(li),
+ a.dict(tv.vval.v_dict),
+ a.freed(li),
+ })
+ eq(1, tv.vval.v_dict.dv_refcount)
+ end)
+ end)
+ describe('remove()', function()
+ itp('works', function()
+ local l = list(1, 2, 3, 4, 5, 6, 7)
+ neq(nil, l)
+ local lis = list_items(l)
+ alloc_log:check({
+ a.list(l),
+ a.li(lis[1]),
+ a.li(lis[2]),
+ a.li(lis[3]),
+ a.li(lis[4]),
+ a.li(lis[5]),
+ a.li(lis[6]),
+ a.li(lis[7]),
+ })
+
+ lib.tv_list_item_remove(l, lis[1])
+ alloc_log:check({
+ a.freed(table.remove(lis, 1)),
+ })
+ eq(lis, list_items(l))
+
+ lib.tv_list_item_remove(l, lis[6])
+ alloc_log:check({
+ a.freed(table.remove(lis)),
+ })
+ eq(lis, list_items(l))
+
+ lib.tv_list_item_remove(l, lis[3])
+ alloc_log:check({
+ a.freed(table.remove(lis, 3)),
+ })
+ eq(lis, list_items(l))
+ end)
+ itp('works and adjusts watchers correctly', function()
+ local l = ffi.gc(list(1, 2, 3, 4, 5, 6, 7), nil)
+ neq(nil, l)
+ local lis = list_items(l)
+ -- Three watchers: pointing to first, middle and last elements.
+ local lws = {
+ list_watch(l, lis[1]),
+ list_watch(l, lis[4]),
+ list_watch(l, lis[7]),
+ }
+
+ lib.tv_list_item_remove(l, lis[4])
+ ffi.gc(lis[4], lib.tv_list_item_free)
+ eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
+
+ lib.tv_list_item_remove(l, lis[2])
+ ffi.gc(lis[2], lib.tv_list_item_free)
+ eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
+
+ lib.tv_list_item_remove(l, lis[7])
+ ffi.gc(lis[7], lib.tv_list_item_free)
+ eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+
+ lib.tv_list_item_remove(l, lis[1])
+ ffi.gc(lis[1], lib.tv_list_item_free)
+ eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+
+ alloc_log:clear()
+ lib.tv_list_watch_remove(l, lws[2])
+ lib.tv_list_watch_remove(l, lws[3])
+ lib.tv_list_watch_remove(l, lws[1])
+ lib.tv_list_free(l)
+ alloc_log:check({
+ a.freed(lis[3]),
+ a.freed(lis[5]),
+ a.freed(lis[6]),
+ a.freed(l),
+ })
+ end)
+ end)
+ end)
+ describe('watch', function()
+ describe('remove()', function()
+ itp('works', function()
+ local l = ffi.gc(list(1, 2, 3, 4, 5, 6, 7), nil)
+ eq(nil, l.lv_watch)
+ local lw = list_watch(l)
+ neq(nil, l.lv_watch)
+ alloc_log:clear()
+ lib.tv_list_watch_remove(l, lw)
+ eq(nil, l.lv_watch)
+ alloc_log:check({
+ -- Does not free anything.
+ })
+ local lws = { list_watch(l), list_watch(l), list_watch(l) }
+ alloc_log:clear()
+ lib.tv_list_watch_remove(l, lws[2])
+ eq(lws[3], l.lv_watch)
+ eq(lws[1], l.lv_watch.lw_next)
+ lib.tv_list_watch_remove(l, lws[1])
+ eq(lws[3], l.lv_watch)
+ eq(nil, l.lv_watch.lw_next)
+ lib.tv_list_watch_remove(l, lws[3])
+ eq(nil, l.lv_watch)
+ alloc_log:check({
+ -- Does not free anything.
+ })
+ end)
+ itp('ignores not found watchers', function()
+ local l = list(1, 2, 3, 4, 5, 6, 7)
+ local lw = list_watch_alloc()
+ lib.tv_list_watch_remove(l, lw)
+ end)
+ end)
+ end)
+ -- add() and fix() were tested when testing tv_list_item_remove()
+ describe('free()', function()
+ itp('recursively frees list', function()
+ local l1 = ffi.gc(list(1, 'abc'), nil)
+ local l2 = ffi.gc(list({}), nil)
+ local l3 = ffi.gc(list(empty_list), nil)
+ local alloc_rets = {}
+ alloc_log:check(get_alloc_rets({
+ a.list(l1),
+ a.li(l1.lv_first),
+ a.str(l1.lv_last.li_tv.vval.v_string, #('abc')),
+ a.li(l1.lv_last),
+ a.list(l2),
+ a.dict(l2.lv_first.li_tv.vval.v_dict),
+ a.li(l2.lv_first),
+ a.list(l3),
+ a.list(l3.lv_first.li_tv.vval.v_list),
+ a.li(l3.lv_first),
+ }, alloc_rets))
+ lib.tv_list_free(l1)
+ alloc_log:check({
+ alloc_rets:freed(2),
+ alloc_rets:freed(3),
+ alloc_rets:freed(4),
+ alloc_rets:freed(1),
+ })
+ lib.tv_list_free(l2)
+ alloc_log:check({
+ alloc_rets:freed(6),
+ alloc_rets:freed(7),
+ alloc_rets:freed(5),
+ })
+ lib.tv_list_free(l3)
+ alloc_log:check({
+ alloc_rets:freed(9),
+ alloc_rets:freed(10),
+ alloc_rets:freed(8),
+ })
+ end)
+ end)
+ describe('free_list()', function()
+ itp('does not free list contents', function()
+ local l1 = ffi.gc(list(1, 'abc'), nil)
+ local l2 = ffi.gc(list({}), nil)
+ local l3 = ffi.gc(list(empty_list), nil)
+ local alloc_rets = {}
+ alloc_log:check(get_alloc_rets({
+ a.list(l1),
+ a.li(l1.lv_first),
+ a.str(l1.lv_last.li_tv.vval.v_string, #('abc')),
+ a.li(l1.lv_last),
+ a.list(l2),
+ a.dict(l2.lv_first.li_tv.vval.v_dict),
+ a.li(l2.lv_first),
+ a.list(l3),
+ a.list(l3.lv_first.li_tv.vval.v_list),
+ a.li(l3.lv_first),
+ }, alloc_rets))
+ lib.tv_list_free_list(l1)
+ alloc_log:check({
+ alloc_rets:freed(1),
+ })
+ lib.tv_list_free_list(l2)
+ alloc_log:check({
+ alloc_rets:freed(5),
+ })
+ lib.tv_list_free_list(l3)
+ alloc_log:check({
+ alloc_rets:freed(8),
+ })
+ end)
+ end)
+ describe('free_contents()', function()
+ itp('recursively frees list, except for the list structure itself',
+ function()
+ local l1 = ffi.gc(list(1, 'abc'), nil)
+ local l2 = ffi.gc(list({}), nil)
+ local l3 = ffi.gc(list(empty_list), nil)
+ local alloc_rets = {}
+ alloc_log:check(get_alloc_rets({
+ a.list(l1),
+ a.li(l1.lv_first),
+ a.str(l1.lv_last.li_tv.vval.v_string, #('abc')),
+ a.li(l1.lv_last),
+ a.list(l2),
+ a.dict(l2.lv_first.li_tv.vval.v_dict),
+ a.li(l2.lv_first),
+ a.list(l3),
+ a.list(l3.lv_first.li_tv.vval.v_list),
+ a.li(l3.lv_first),
+ }, alloc_rets))
+ lib.tv_list_free_contents(l1)
+ alloc_log:check({
+ alloc_rets:freed(2),
+ alloc_rets:freed(3),
+ alloc_rets:freed(4),
+ })
+ lib.tv_list_free_contents(l2)
+ alloc_log:check({
+ alloc_rets:freed(6),
+ alloc_rets:freed(7),
+ })
+ lib.tv_list_free_contents(l3)
+ alloc_log:check({
+ alloc_rets:freed(9),
+ alloc_rets:freed(10),
+ })
+ end)
+ end)
+ describe('unref()', function()
+ itp('recursively frees list when reference count goes to 0', function()
+ local l = ffi.gc(list(empty_list), nil)
+ local alloc_rets = {}
+ alloc_log:check(get_alloc_rets({
+ a.list(l),
+ a.list(l.lv_first.li_tv.vval.v_list),
+ a.li(l.lv_first),
+ }, alloc_rets))
+ l.lv_refcount = 2
+ lib.tv_list_unref(l)
+ alloc_log:check({})
+ lib.tv_list_unref(l)
+ alloc_log:check({
+ alloc_rets:freed(2),
+ alloc_rets:freed(3),
+ alloc_rets:freed(1),
+ })
+ end)
+ end)
+ describe('remove_items()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13})
+ local l = l_tv.vval.v_list
+ local lis = list_items(l)
+ -- Three watchers: pointing to first, middle and last elements.
+ local lws = {
+ list_watch(l, lis[1]),
+ list_watch(l, lis[7]),
+ list_watch(l, lis[13]),
+ }
+ alloc_log:clear()
+
+ lib.tv_list_remove_items(l, lis[1], lis[3])
+ eq({4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, typvalt2lua(l_tv))
+ eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
+
+ lib.tv_list_remove_items(l, lis[11], lis[13])
+ eq({4, 5, 6, 7, 8, 9, 10}, typvalt2lua(l_tv))
+ eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+
+ lib.tv_list_remove_items(l, lis[6], lis[8])
+ eq({4, 5, 9, 10}, typvalt2lua(l_tv))
+ eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+
+ lib.tv_list_remove_items(l, lis[4], lis[10])
+ eq(empty_list, typvalt2lua(l_tv))
+ eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
+
+ alloc_log:check({})
+ end)
+ end)
+ describe('insert', function()
+ describe('()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt({1, 2, 3, 4, 5, 6, 7})
+ local l = l_tv.vval.v_list
+ local lis = list_items(l)
+ local li
+
+ li = li_alloc(true)
+ li.li_tv = {v_type=lib.VAR_FLOAT, vval={v_float=100500}}
+ lib.tv_list_insert(l, li, nil)
+ eq(l.lv_last, li)
+ eq({1, 2, 3, 4, 5, 6, 7, 100500}, typvalt2lua(l_tv))
+
+ li = li_alloc(true)
+ li.li_tv = {v_type=lib.VAR_FLOAT, vval={v_float=0}}
+ lib.tv_list_insert(l, li, lis[1])
+ eq(l.lv_first, li)
+ eq({0, 1, 2, 3, 4, 5, 6, 7, 100500}, typvalt2lua(l_tv))
+
+ li = li_alloc(true)
+ li.li_tv = {v_type=lib.VAR_FLOAT, vval={v_float=4.5}}
+ lib.tv_list_insert(l, li, lis[5])
+ eq(list_items(l)[6], li)
+ eq({0, 1, 2, 3, 4, 4.5, 5, 6, 7, 100500}, typvalt2lua(l_tv))
+ end)
+ itp('works with an empty list', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ eq(nil, l.lv_first)
+ eq(nil, l.lv_last)
+
+ local li = li_alloc(true)
+ li.li_tv = {v_type=lib.VAR_FLOAT, vval={v_float=100500}}
+ lib.tv_list_insert(l, li, nil)
+ eq(l.lv_last, li)
+ eq({100500}, typvalt2lua(l_tv))
+ end)
+ end)
+ describe('tv()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ local l_l_tv = lua2typvalt(empty_list)
+ alloc_log:clear()
+ local l_l = l_l_tv.vval.v_list
+ eq(1, l_l.lv_refcount)
+ lib.tv_list_insert_tv(l, l_l_tv, nil)
+ eq(2, l_l.lv_refcount)
+ eq(l_l, l.lv_first.li_tv.vval.v_list)
+ alloc_log:check({
+ a.li(l.lv_first),
+ })
+
+ local l_s_tv = lua2typvalt('test')
+ alloc_log:check({
+ a.str(l_s_tv.vval.v_string, 'test'),
+ })
+ lib.tv_list_insert_tv(l, l_s_tv, l.lv_first)
+ alloc_log:check({
+ a.li(l.lv_first),
+ a.str(l.lv_first.li_tv.vval.v_string, 'test'),
+ })
+
+ eq({'test', empty_list}, typvalt2lua(l_tv))
+ end)
+ end)
+ end)
+ describe('append', function()
+ describe('list()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ local l_l = list(1)
+ alloc_log:clear()
+ eq(1, l_l.lv_refcount)
+ lib.tv_list_append_list(l, l_l)
+ eq(2, l_l.lv_refcount)
+ eq(l_l, l.lv_first.li_tv.vval.v_list)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_list(l, nil)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ eq({{1}, null_list}, typvalt2lua(l_tv))
+ end)
+ end)
+ describe('dict()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ local l_d_tv = lua2typvalt({test=1})
+ local l_d = l_d_tv.vval.v_dict
+ alloc_log:clear()
+ eq(1, l_d.dv_refcount)
+ lib.tv_list_append_dict(l, l_d)
+ eq(2, l_d.dv_refcount)
+ eq(l_d, l.lv_first.li_tv.vval.v_list)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_dict(l, nil)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ eq({{test=1}, null_dict}, typvalt2lua(l_tv))
+ end)
+ end)
+ describe('string()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ alloc_log:clear()
+ lib.tv_list_append_string(l, 'test', 3)
+ alloc_log:check({
+ a.str(l.lv_last.li_tv.vval.v_string, 'tes'),
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_string(l, nil, 0)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_string(l, nil, -1)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_string(l, 'test', -1)
+ alloc_log:check({
+ a.str(l.lv_last.li_tv.vval.v_string, 'test'),
+ a.li(l.lv_last),
+ })
+
+ eq({'tes', null_string, null_string, 'test'}, typvalt2lua(l_tv))
+ end)
+ end)
+ describe('allocated string()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ local s = lib.xstrdup('test')
+ alloc_log:clear()
+ lib.tv_list_append_allocated_string(l, s)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_allocated_string(l, nil)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_allocated_string(l, nil)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ eq({'test', null_string, null_string}, typvalt2lua(l_tv))
+ end)
+ end)
+ describe('number()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ alloc_log:clear()
+ lib.tv_list_append_number(l, -100500)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ lib.tv_list_append_number(l, 100500)
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ eq({int(-100500), int(100500)}, typvalt2lua(l_tv))
+ end)
+ end)
+ end)
+ describe('copy()', function()
+ local function tv_list_copy(...)
+ return ffi.gc(lib.tv_list_copy(...), lib.tv_list_unref)
+ end
+ itp('copies NULL correctly', function()
+ eq(nil, lib.tv_list_copy(nil, nil, true, 0))
+ eq(nil, lib.tv_list_copy(nil, nil, false, 0))
+ eq(nil, lib.tv_list_copy(nil, nil, true, 1))
+ eq(nil, lib.tv_list_copy(nil, nil, false, 1))
+ end)
+ itp('copies list correctly without converting items', function()
+ do
+ local v = {{['«']='»'}, {'„'}, 1, '“', null_string, null_list, null_dict}
+ local l_tv = lua2typvalt(v)
+ local l = l_tv.vval.v_list
+ local lis = list_items(l)
+ alloc_log:clear()
+
+ eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
+ eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
+ local l_copy1 = tv_list_copy(nil, l, false, 0)
+ eq(2, lis[1].li_tv.vval.v_dict.dv_refcount)
+ eq(2, lis[2].li_tv.vval.v_list.lv_refcount)
+ local lis_copy1 = list_items(l_copy1)
+ eq(lis[1].li_tv.vval.v_dict, lis_copy1[1].li_tv.vval.v_dict)
+ eq(lis[2].li_tv.vval.v_list, lis_copy1[2].li_tv.vval.v_list)
+ eq(v, lst2tbl(l_copy1))
+ alloc_log:check({
+ a.list(l_copy1),
+ a.li(lis_copy1[1]),
+ a.li(lis_copy1[2]),
+ a.li(lis_copy1[3]),
+ a.li(lis_copy1[4]),
+ a.str(lis_copy1[4].li_tv.vval.v_string, #v[4]),
+ a.li(lis_copy1[5]),
+ a.li(lis_copy1[6]),
+ a.li(lis_copy1[7]),
+ })
+ lib.tv_list_free(ffi.gc(l_copy1, nil))
+ alloc_log:clear()
+
+ eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
+ eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
+ local l_deepcopy1 = tv_list_copy(nil, l, true, 0)
+ neq(nil, l_deepcopy1)
+ eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
+ eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
+ local lis_deepcopy1 = list_items(l_deepcopy1)
+ neq(lis[1].li_tv.vval.v_dict, lis_deepcopy1[1].li_tv.vval.v_dict)
+ neq(lis[2].li_tv.vval.v_list, lis_deepcopy1[2].li_tv.vval.v_list)
+ eq(v, lst2tbl(l_deepcopy1))
+ local di_deepcopy1 = first_di(lis_deepcopy1[1].li_tv.vval.v_dict)
+ alloc_log:check({
+ a.list(l_deepcopy1),
+ a.li(lis_deepcopy1[1]),
+ a.dict(lis_deepcopy1[1].li_tv.vval.v_dict),
+ a.di(di_deepcopy1, #('«')),
+ a.str(di_deepcopy1.di_tv.vval.v_string, #v[1]['«']),
+ a.li(lis_deepcopy1[2]),
+ a.list(lis_deepcopy1[2].li_tv.vval.v_list),
+ a.li(lis_deepcopy1[2].li_tv.vval.v_list.lv_first),
+ a.str(lis_deepcopy1[2].li_tv.vval.v_list.lv_first.li_tv.vval.v_string, #v[2][1]),
+ a.li(lis_deepcopy1[3]),
+ a.li(lis_deepcopy1[4]),
+ a.str(lis_deepcopy1[4].li_tv.vval.v_string, #v[4]),
+ a.li(lis_deepcopy1[5]),
+ a.li(lis_deepcopy1[6]),
+ a.li(lis_deepcopy1[7]),
+ })
+ end
+ collectgarbage()
+ end)
+ itp('copies list correctly and converts items', function()
+ local vc = ffi.gc(ffi.new('vimconv_T[1]'), function(vc)
+ lib.convert_setup(vc, nil, nil)
+ end)
+ -- UTF-8 ↔ latin1 conversions need no iconv
+ eq(OK, lib.convert_setup(vc, to_cstr('utf-8'), to_cstr('latin1')))
+
+ local v = {{['«']='»'}, {'„'}, 1, '“', null_string, null_list, null_dict}
+ local l_tv = lua2typvalt(v)
+ local l = l_tv.vval.v_list
+ local lis = list_items(l)
+ alloc_log:clear()
+
+ eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
+ eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
+ local l_deepcopy1 = tv_list_copy(vc, l, true, 0)
+ neq(nil, l_deepcopy1)
+ eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
+ eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
+ local lis_deepcopy1 = list_items(l_deepcopy1)
+ neq(lis[1].li_tv.vval.v_dict, lis_deepcopy1[1].li_tv.vval.v_dict)
+ neq(lis[2].li_tv.vval.v_list, lis_deepcopy1[2].li_tv.vval.v_list)
+ eq({{['\171']='\187'}, {'\191'}, 1, '\191', null_string, null_list, null_dict},
+ lst2tbl(l_deepcopy1))
+ local di_deepcopy1 = first_di(lis_deepcopy1[1].li_tv.vval.v_dict)
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({
+ a.list(l_deepcopy1),
+ a.li(lis_deepcopy1[1]),
+ a.dict(lis_deepcopy1[1].li_tv.vval.v_dict),
+ a.di(di_deepcopy1, 1),
+ a.str(di_deepcopy1.di_tv.vval.v_string, 2),
+ a.li(lis_deepcopy1[2]),
+ a.list(lis_deepcopy1[2].li_tv.vval.v_list),
+ a.li(lis_deepcopy1[2].li_tv.vval.v_list.lv_first),
+ a.str(lis_deepcopy1[2].li_tv.vval.v_list.lv_first.li_tv.vval.v_string, #v[2][1]),
+ a.li(lis_deepcopy1[3]),
+ a.li(lis_deepcopy1[4]),
+ a.str(lis_deepcopy1[4].li_tv.vval.v_string, #v[4]),
+ a.li(lis_deepcopy1[5]),
+ a.li(lis_deepcopy1[6]),
+ a.li(lis_deepcopy1[7]),
+ })
+ end)
+ itp('returns different/same containers with(out) copyID', function()
+ local l_inner_tv = lua2typvalt(empty_list)
+ local l_tv = lua2typvalt({l_inner_tv, l_inner_tv})
+ eq(3, l_inner_tv.vval.v_list.lv_refcount)
+ local l = l_tv.vval.v_list
+ eq(l.lv_first.li_tv.vval.v_list, l.lv_last.li_tv.vval.v_list)
+
+ local l_copy1 = tv_list_copy(nil, l, true, 0)
+ neq(l_copy1.lv_first.li_tv.vval.v_list, l_copy1.lv_last.li_tv.vval.v_list)
+ eq({empty_list, empty_list}, lst2tbl(l_copy1))
+
+ local l_copy2 = tv_list_copy(nil, l, true, 2)
+ eq(l_copy2.lv_first.li_tv.vval.v_list, l_copy2.lv_last.li_tv.vval.v_list)
+ eq({empty_list, empty_list}, lst2tbl(l_copy2))
+
+ eq(3, l_inner_tv.vval.v_list.lv_refcount)
+ end)
+ itp('works with self-referencing list with copyID', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+ eq(1, l.lv_refcount)
+ lib.tv_list_append_list(l, l)
+ eq(2, l.lv_refcount)
+
+ local l_copy1 = tv_list_copy(nil, l, true, 2)
+ eq(2, l_copy1.lv_refcount)
+ local v = {}
+ v[1] = v
+ eq(v, lst2tbl(l_copy1))
+
+ local lis = list_items(l)
+ lib.tv_list_item_remove(l, lis[1])
+ eq(1, l.lv_refcount)
+
+ local lis_copy1 = list_items(l_copy1)
+ lib.tv_list_item_remove(l_copy1, lis_copy1[1])
+ eq(1, l_copy1.lv_refcount)
+ end)
+ end)
+ describe('extend()', function()
+ itp('can extend list with itself', function()
+ local l
+
+ l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l, nil)
+ alloc_log:check({
+ a.li(l.lv_last.li_prev),
+ a.li(l.lv_last),
+ })
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq({1, {}, 1, {}}, lst2tbl(l))
+
+ l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l, l.lv_last)
+ alloc_log:check({
+ a.li(l.lv_last.li_prev.li_prev),
+ a.li(l.lv_last.li_prev),
+ })
+ eq({1, 1, {}, {}}, lst2tbl(l))
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l, l.lv_first)
+ alloc_log:check({
+ a.li(l.lv_first),
+ a.li(l.lv_first.li_next),
+ })
+ eq({1, {}, 1, {}}, lst2tbl(l))
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ end)
+ itp('can extend list with an empty list', function()
+ local l = list(1, {})
+ local el = list()
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+
+ lib.tv_list_extend(l, el, nil)
+ alloc_log:check({
+ })
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+ eq({1, {}}, lst2tbl(l))
+
+ lib.tv_list_extend(l, el, l.lv_first)
+ alloc_log:check({
+ })
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+ eq({1, {}}, lst2tbl(l))
+
+ lib.tv_list_extend(l, el, l.lv_last)
+ alloc_log:check({
+ })
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+ eq({1, {}}, lst2tbl(l))
+ end)
+ itp('can extend list with another non-empty list', function()
+ local l
+ local l2 = list(42, empty_list)
+ eq(1, l2.lv_refcount)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+
+ l = ffi.gc(list(1, {}), nil)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l2, nil)
+ alloc_log:check({
+ a.li(l.lv_last.li_prev),
+ a.li(l.lv_last),
+ })
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ eq({1, {}, 42, empty_list}, lst2tbl(l))
+ lib.tv_list_free(l)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+
+ l = ffi.gc(list(1, {}), nil)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l2, l.lv_first)
+ alloc_log:check({
+ a.li(l.lv_first),
+ a.li(l.lv_first.li_next),
+ })
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ eq({42, empty_list, 1, {}}, lst2tbl(l))
+ lib.tv_list_free(l)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+
+ l = ffi.gc(list(1, {}), nil)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l2, l.lv_last)
+ alloc_log:check({
+ a.li(l.lv_first.li_next),
+ a.li(l.lv_first.li_next.li_next),
+ })
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ eq({1, 42, empty_list, {}}, lst2tbl(l))
+ lib.tv_list_free(l)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ end)
+ end)
+ describe('concat()', function()
+ itp('works with NULL lists', function()
+ local l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ local rettv1 = typvalt()
+ eq(OK, lib.tv_list_concat(nil, l, rettv1))
+ eq(1, l.lv_refcount)
+ eq(tonumber(lib.VAR_LIST), tonumber(rettv1.v_type))
+ eq({1, {}}, typvalt2lua(rettv1))
+ eq(1, rettv1.vval.v_list.lv_refcount)
+ alloc_log:check({
+ a.list(rettv1.vval.v_list),
+ a.li(rettv1.vval.v_list.lv_first),
+ a.li(rettv1.vval.v_list.lv_last),
+ })
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ local rettv2 = typvalt()
+ eq(OK, lib.tv_list_concat(l, nil, rettv2))
+ eq(1, l.lv_refcount)
+ eq(tonumber(lib.VAR_LIST), tonumber(rettv2.v_type))
+ eq({1, {}}, typvalt2lua(rettv2))
+ eq(1, rettv2.vval.v_list.lv_refcount)
+ alloc_log:check({
+ a.list(rettv2.vval.v_list),
+ a.li(rettv2.vval.v_list.lv_first),
+ a.li(rettv2.vval.v_list.lv_last),
+ })
+ eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ local rettv3 = typvalt()
+ eq(OK, lib.tv_list_concat(nil, nil, rettv3))
+ eq(tonumber(lib.VAR_LIST), tonumber(rettv3.v_type))
+ eq(null_list, typvalt2lua(rettv3))
+ alloc_log:check({})
+ end)
+ itp('works with two different lists', function()
+ local l1 = list(1, {})
+ local l2 = list(3, empty_list)
+ eq(1, l1.lv_refcount)
+ eq(1, l1.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, l2.lv_refcount)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ alloc_log:clear()
+
+ local rettv = typvalt()
+ eq(OK, lib.tv_list_concat(l1, l2, rettv))
+ eq(1, l1.lv_refcount)
+ eq(2, l1.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ alloc_log:check({
+ a.list(rettv.vval.v_list),
+ a.li(rettv.vval.v_list.lv_first),
+ a.li(rettv.vval.v_list.lv_first.li_next),
+ a.li(rettv.vval.v_list.lv_last.li_prev),
+ a.li(rettv.vval.v_list.lv_last),
+ })
+ eq({1, {}, 3, empty_list}, typvalt2lua(rettv))
+ end)
+ itp('can concatenate list with itself', function()
+ local l = list(1, {})
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ alloc_log:clear()
+
+ local rettv = typvalt()
+ eq(OK, lib.tv_list_concat(l, l, rettv))
+ eq(1, l.lv_refcount)
+ eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ alloc_log:check({
+ a.list(rettv.vval.v_list),
+ a.li(rettv.vval.v_list.lv_first),
+ a.li(rettv.vval.v_list.lv_first.li_next),
+ a.li(rettv.vval.v_list.lv_last.li_prev),
+ a.li(rettv.vval.v_list.lv_last),
+ })
+ eq({1, {}, 1, {}}, typvalt2lua(rettv))
+ end)
+ itp('can concatenate empty non-NULL lists', function()
+ local l = list(1, {})
+ local le = list()
+ local le2 = list()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, le.lv_refcount)
+ eq(1, le2.lv_refcount)
+ alloc_log:clear()
+
+ local rettv1 = typvalt()
+ eq(OK, lib.tv_list_concat(l, le, rettv1))
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, le.lv_refcount)
+ eq(1, le2.lv_refcount)
+ alloc_log:check({
+ a.list(rettv1.vval.v_list),
+ a.li(rettv1.vval.v_list.lv_first),
+ a.li(rettv1.vval.v_list.lv_last),
+ })
+ eq({1, {}}, typvalt2lua(rettv1))
+
+ local rettv2 = typvalt()
+ eq(OK, lib.tv_list_concat(le, l, rettv2))
+ eq(1, l.lv_refcount)
+ eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, le.lv_refcount)
+ eq(1, le2.lv_refcount)
+ alloc_log:check({
+ a.list(rettv2.vval.v_list),
+ a.li(rettv2.vval.v_list.lv_first),
+ a.li(rettv2.vval.v_list.lv_last),
+ })
+ eq({1, {}}, typvalt2lua(rettv2))
+
+ local rettv3 = typvalt()
+ eq(OK, lib.tv_list_concat(le, le, rettv3))
+ eq(1, l.lv_refcount)
+ eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, le.lv_refcount)
+ eq(1, le2.lv_refcount)
+ alloc_log:check({
+ a.list(rettv3.vval.v_list),
+ })
+ eq(empty_list, typvalt2lua(rettv3))
+
+ local rettv4 = typvalt()
+ eq(OK, lib.tv_list_concat(le, le2, rettv4))
+ eq(1, l.lv_refcount)
+ eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, le.lv_refcount)
+ eq(1, le2.lv_refcount)
+ alloc_log:check({
+ a.list(rettv4.vval.v_list),
+ })
+ eq(empty_list, typvalt2lua(rettv4))
+ end)
+ end)
+ describe('join()', function()
+ local function list_join(l, sep, ret)
+ local ga = ga_alloc()
+ eq(ret or OK, lib.tv_list_join(ga, l, sep))
+ if ga.ga_data == nil then return ''
+ else return ffi.string(ga.ga_data)
+ end
+ end
+ itp('works', function()
+ local l
+ l = list('boo', 'far')
+ eq('boo far', list_join(l, ' '))
+ eq('boofar', list_join(l, ''))
+
+ l = list('boo')
+ eq('boo', list_join(l, ' '))
+
+ l = list()
+ eq('', list_join(l, ' '))
+
+ l = list({}, 'far')
+ eq('{} far', list_join(l, ' '))
+
+ local recursive_list = {}
+ recursive_list[1] = recursive_list
+ l = ffi.gc(list(recursive_list, 'far'), nil)
+ eq('[[...@0]] far', list_join(l, ' '))
+
+ local recursive_l = l.lv_first.li_tv.vval.v_list
+ local recursive_li = recursive_l.lv_first
+ lib.tv_list_item_remove(recursive_l, recursive_li)
+ lib.tv_list_free(l)
+ end)
+ end)
+ describe('equal()', function()
+ itp('compares empty and NULL lists correctly', function()
+ local l = list()
+ local l2 = list()
+
+ -- NULL lists are not equal to empty lists
+ eq(false, lib.tv_list_equal(l, nil, true, false))
+ eq(false, lib.tv_list_equal(nil, l, false, false))
+ eq(false, lib.tv_list_equal(nil, l, false, true))
+ eq(false, lib.tv_list_equal(l, nil, true, true))
+
+ -- Yet NULL lists are equal themselves
+ eq(true, lib.tv_list_equal(nil, nil, true, false))
+ eq(true, lib.tv_list_equal(nil, nil, false, false))
+ eq(true, lib.tv_list_equal(nil, nil, false, true))
+ eq(true, lib.tv_list_equal(nil, nil, true, true))
+
+ -- As well as empty lists
+ eq(true, lib.tv_list_equal(l, l, true, false))
+ eq(true, lib.tv_list_equal(l, l2, false, false))
+ eq(true, lib.tv_list_equal(l2, l, false, true))
+ eq(true, lib.tv_list_equal(l2, l2, true, true))
+ end)
+ -- Must not use recursive=true argument in the following tests because it
+ -- indicates that tv_equal_recurse_limit and recursive_cnt were set which
+ -- is essential. This argument will be set when comparing inner lists.
+ itp('compares lists correctly when case is not ignored', function()
+ local l1 = list('abc', {1, 2, 'Abc'}, 'def')
+ local l2 = list('abc', {1, 2, 'Abc'})
+ local l3 = list('abc', {1, 2, 'Abc'}, 'Def')
+ local l4 = list('abc', {1, 2, 'Abc', 4}, 'def')
+ local l5 = list('Abc', {1, 2, 'Abc'}, 'def')
+ local l6 = list('abc', {1, 2, 'Abc'}, 'def')
+ local l7 = list('abc', {1, 2, 'abc'}, 'def')
+ local l8 = list('abc', nil, 'def')
+ local l9 = list('abc', {1, 2, nil}, 'def')
+
+ eq(true, lib.tv_list_equal(l1, l1, false, false))
+ eq(false, lib.tv_list_equal(l1, l2, false, false))
+ eq(false, lib.tv_list_equal(l1, l3, false, false))
+ eq(false, lib.tv_list_equal(l1, l4, false, false))
+ eq(false, lib.tv_list_equal(l1, l5, false, false))
+ eq(true, lib.tv_list_equal(l1, l6, false, false))
+ eq(false, lib.tv_list_equal(l1, l7, false, false))
+ eq(false, lib.tv_list_equal(l1, l8, false, false))
+ eq(false, lib.tv_list_equal(l1, l9, false, false))
+ end)
+ itp('compares lists correctly when case is ignored', function()
+ local l1 = list('abc', {1, 2, 'Abc'}, 'def')
+ local l2 = list('abc', {1, 2, 'Abc'})
+ local l3 = list('abc', {1, 2, 'Abc'}, 'Def')
+ local l4 = list('abc', {1, 2, 'Abc', 4}, 'def')
+ local l5 = list('Abc', {1, 2, 'Abc'}, 'def')
+ local l6 = list('abc', {1, 2, 'Abc'}, 'def')
+ local l7 = list('abc', {1, 2, 'abc'}, 'def')
+ local l8 = list('abc', nil, 'def')
+ local l9 = list('abc', {1, 2, nil}, 'def')
+
+ eq(true, lib.tv_list_equal(l1, l1, true, false))
+ eq(false, lib.tv_list_equal(l1, l2, true, false))
+ eq(true, lib.tv_list_equal(l1, l3, true, false))
+ eq(false, lib.tv_list_equal(l1, l4, true, false))
+ eq(true, lib.tv_list_equal(l1, l5, true, false))
+ eq(true, lib.tv_list_equal(l1, l6, true, false))
+ eq(true, lib.tv_list_equal(l1, l7, true, false))
+ eq(false, lib.tv_list_equal(l1, l8, true, false))
+ eq(false, lib.tv_list_equal(l1, l9, true, false))
+ end)
+ end)
+ describe('find', function()
+ describe('()', function()
+ itp('correctly indexes list', function()
+ local l = list(1, 2, 3, 4, 5)
+ local lis = list_items(l)
+ alloc_log:clear()
+
+ eq(nil, lib.tv_list_find(nil, -1))
+ eq(nil, lib.tv_list_find(nil, 0))
+ eq(nil, lib.tv_list_find(nil, 1))
+
+ eq(nil, lib.tv_list_find(l, 5))
+ eq(nil, lib.tv_list_find(l, -6))
+ eq(lis[1], lib.tv_list_find(l, -5))
+ eq(lis[5], lib.tv_list_find(l, 4))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, -3))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, -3))
+
+ l.lv_idx_item = nil
+ eq(lis[1], lib.tv_list_find(l, -5))
+ l.lv_idx_item = nil
+ eq(lis[5], lib.tv_list_find(l, 4))
+ l.lv_idx_item = nil
+ eq(lis[3], lib.tv_list_find(l, 2))
+ l.lv_idx_item = nil
+ eq(lis[3], lib.tv_list_find(l, -3))
+ l.lv_idx_item = nil
+ eq(lis[3], lib.tv_list_find(l, 2))
+ l.lv_idx_item = nil
+ eq(lis[3], lib.tv_list_find(l, 2))
+ l.lv_idx_item = nil
+ eq(lis[3], lib.tv_list_find(l, -3))
+
+ l.lv_idx_item = nil
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[1], lib.tv_list_find(l, -5))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[5], lib.tv_list_find(l, 4))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, -3))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, 2))
+ eq(lis[3], lib.tv_list_find(l, -3))
+
+ alloc_log:check({})
+ end)
+ end)
+ describe('nr()', function()
+ local function tv_list_find_nr(l, n, msg)
+ return check_emsg(function()
+ local err = ffi.new('bool[1]', {false})
+ local ret = lib.tv_list_find_nr(l, n, err)
+ return (err[0] == true), ret
+ end, msg)
+ end
+ itp('returns correct number', function()
+ local l = list(int(1), int(2), int(3), int(4), int(5))
+ alloc_log:clear()
+
+ eq({false, 1}, {tv_list_find_nr(l, -5)})
+ eq({false, 5}, {tv_list_find_nr(l, 4)})
+ eq({false, 3}, {tv_list_find_nr(l, 2)})
+ eq({false, 3}, {tv_list_find_nr(l, -3)})
+
+ alloc_log:check({})
+ end)
+ itp('returns correct number when given a string', function()
+ local l = list('1', '2', '3', '4', '5')
+ alloc_log:clear()
+
+ eq({false, 1}, {tv_list_find_nr(l, -5)})
+ eq({false, 5}, {tv_list_find_nr(l, 4)})
+ eq({false, 3}, {tv_list_find_nr(l, 2)})
+ eq({false, 3}, {tv_list_find_nr(l, -3)})
+
+ alloc_log:check({})
+ end)
+ itp('returns zero when given a NULL string', function()
+ local l = list(null_string)
+ alloc_log:clear()
+
+ eq({false, 0}, {tv_list_find_nr(l, 0)})
+
+ alloc_log:check({})
+ end)
+ itp('errors out on NULL lists', function()
+ eq({true, -1}, {tv_list_find_nr(nil, -5)})
+ eq({true, -1}, {tv_list_find_nr(nil, 4)})
+ eq({true, -1}, {tv_list_find_nr(nil, 2)})
+ eq({true, -1}, {tv_list_find_nr(nil, -3)})
+
+ alloc_log:check({})
+ end)
+ itp('errors out on out-of-range indexes', function()
+ local l = list(int(1), int(2), int(3), int(4), int(5))
+ alloc_log:clear()
+
+ eq({true, -1}, {tv_list_find_nr(l, -6)})
+ eq({true, -1}, {tv_list_find_nr(l, 5)})
+
+ alloc_log:check({})
+ end)
+ itp('errors out on invalid types', function()
+ local l = list(1, empty_list, {})
+
+ eq({true, 0}, {tv_list_find_nr(l, 0, 'E805: Using a Float as a Number')})
+ eq({true, 0}, {tv_list_find_nr(l, 1, 'E745: Using a List as a Number')})
+ eq({true, 0}, {tv_list_find_nr(l, 2, 'E728: Using a Dictionary as a Number')})
+ eq({true, 0}, {tv_list_find_nr(l, -1, 'E728: Using a Dictionary as a Number')})
+ eq({true, 0}, {tv_list_find_nr(l, -2, 'E745: Using a List as a Number')})
+ eq({true, 0}, {tv_list_find_nr(l, -3, 'E805: Using a Float as a Number')})
+ end)
+ end)
+ local function tv_list_find_str(l, n, msg)
+ return check_emsg(function()
+ local ret = lib.tv_list_find_str(l, n)
+ local s = nil
+ if ret ~= nil then
+ s = ffi.string(ret)
+ end
+ return s
+ end, msg)
+ end
+ describe('str()', function()
+ itp('returns correct string', function()
+ local l = list(int(1), int(2), int(3), int(4), int(5))
+ alloc_log:clear()
+
+ eq('1', tv_list_find_str(l, -5))
+ eq('5', tv_list_find_str(l, 4))
+ eq('3', tv_list_find_str(l, 2))
+ eq('3', tv_list_find_str(l, -3))
+
+ alloc_log:check({})
+ end)
+ itp('returns string when used with VAR_STRING items', function()
+ local l = list('1', '2', '3', '4', '5')
+ alloc_log:clear()
+
+ eq('1', tv_list_find_str(l, -5))
+ eq('5', tv_list_find_str(l, 4))
+ eq('3', tv_list_find_str(l, 2))
+ eq('3', tv_list_find_str(l, -3))
+
+ alloc_log:check({})
+ end)
+ itp('returns empty when used with NULL string', function()
+ local l = list(null_string)
+ alloc_log:clear()
+
+ eq('', tv_list_find_str(l, 0))
+
+ alloc_log:check({})
+ end)
+ itp('fails with error message when index is out of range', function()
+ local l = list(int(1), int(2), int(3), int(4), int(5))
+
+ eq(nil, tv_list_find_str(l, -6, 'E684: list index out of range: -6'))
+ eq(nil, tv_list_find_str(l, 5, 'E684: list index out of range: 5'))
+ end)
+ itp('fails with error message on invalid types', function()
+ local l = list(1, empty_list, {})
+
+ eq('', tv_list_find_str(l, 0, 'E806: using Float as a String'))
+ eq('', tv_list_find_str(l, 1, 'E730: using List as a String'))
+ eq('', tv_list_find_str(l, 2, 'E731: using Dictionary as a String'))
+ eq('', tv_list_find_str(l, -1, 'E731: using Dictionary as a String'))
+ eq('', tv_list_find_str(l, -2, 'E730: using List as a String'))
+ eq('', tv_list_find_str(l, -3, 'E806: using Float as a String'))
+ end)
+ end)
+ end)
+ describe('idx_of_item()', function()
+ itp('works', function()
+ local l = list(1, 2, 3, 4, 5)
+ local l2 = list(42, empty_list)
+ local lis = list_items(l)
+ local lis2 = list_items(l2)
+
+ for i, li in ipairs(lis) do
+ eq(i - 1, lib.tv_list_idx_of_item(l, li))
+ end
+ eq(-1, lib.tv_list_idx_of_item(l, lis2[1]))
+ eq(-1, lib.tv_list_idx_of_item(l, nil))
+ eq(-1, lib.tv_list_idx_of_item(nil, nil))
+ eq(-1, lib.tv_list_idx_of_item(nil, lis[1]))
+ end)
+ end)
+ end)
+ describe('dict', function()
+ describe('watcher', function()
+ describe('add()/remove()', function()
+ itp('works with an empty key', function()
+ local d = dict({})
+ eq({}, dict_watchers(d))
+ local cb = ffi.gc(tbl2callback({type='none'}), nil)
+ alloc_log:clear()
+ lib.tv_dict_watcher_add(d, '*', 0, cb[0])
+ local ws, qs = dict_watchers(d)
+ local key_p = qs[1].key_pattern
+ alloc_log:check({
+ a.dwatcher(qs[1]),
+ a.str(key_p, 0),
+ })
+ eq({{busy=false, cb={type='none'}, pat=''}}, ws)
+ eq(true, lib.tv_dict_watcher_remove(d, 'x', 0, cb[0]))
+ alloc_log:check({
+ a.freed(key_p),
+ a.freed(qs[1]),
+ })
+ eq({}, dict_watchers(d))
+ end)
+ itp('works with multiple callbacks', function()
+ local d = dict({})
+ eq({}, dict_watchers(d))
+ alloc_log:check({a.dict(d)})
+ local cbs = {}
+ cbs[1] = {'te', ffi.gc(tbl2callback({type='none'}), nil)}
+ alloc_log:check({})
+ cbs[2] = {'foo', ffi.gc(tbl2callback({type='fref', fref='tr'}), nil)}
+ alloc_log:check({
+ a.str(cbs[2][2].data.funcref, #('tr')),
+ })
+ cbs[3] = {'te', ffi.gc(tbl2callback({type='pt', fref='tr', pt={
+ value='tr',
+ args={'test'},
+ dict={},
+ }}), nil)}
+ local pt3 = cbs[3][2].data.partial
+ local pt3_argv = pt3.pt_argv
+ local pt3_dict = pt3.pt_dict
+ local pt3_name = pt3.pt_name
+ local pt3_str_arg = pt3.pt_argv[0].vval.v_string
+ alloc_log:check({
+ a.lua_pt(pt3),
+ a.lua_tvs(pt3_argv, pt3.pt_argc),
+ a.str(pt3_str_arg, #('test')),
+ a.dict(pt3_dict),
+ a.str(pt3_name, #('tr')),
+ })
+ for _, v in ipairs(cbs) do
+ lib.tv_dict_watcher_add(d, v[1], #(v[1]), v[2][0])
+ end
+ local ws, qs, kps = dict_watchers(d)
+ eq({{busy=false, pat=cbs[1][1], cb={type='none'}},
+ {busy=false, pat=cbs[2][1], cb={type='fref', fref='tr'}},
+ {busy=false, pat=cbs[3][1], cb={type='pt', fref='tr', pt={
+ [type_key]=func_type,
+ value='tr',
+ args={'test'},
+ dict={},
+ }}}}, ws)
+ alloc_log:check({
+ a.dwatcher(qs[1]),
+ a.str(kps[1][1], kps[1][2]),
+ a.dwatcher(qs[2]),
+ a.str(kps[2][1], kps[2][2]),
+ a.dwatcher(qs[3]),
+ a.str(kps[3][1], kps[3][2]),
+ })
+ eq(true, lib.tv_dict_watcher_remove(d, cbs[2][1], #cbs[2][1], cbs[2][2][0]))
+ alloc_log:check({
+ a.freed(cbs[2][2].data.funcref),
+ a.freed(kps[2][1]),
+ a.freed(qs[2]),
+ })
+ eq(false, lib.tv_dict_watcher_remove(d, cbs[2][1], #cbs[2][1], cbs[2][2][0]))
+ eq({{busy=false, pat=cbs[1][1], cb={type='none'}},
+ {busy=false, pat=cbs[3][1], cb={type='pt', fref='tr', pt={
+ [type_key]=func_type,
+ value='tr',
+ args={'test'},
+ dict={},
+ }}}}, dict_watchers(d))
+ eq(true, lib.tv_dict_watcher_remove(d, cbs[3][1], #cbs[3][1], cbs[3][2][0]))
+ alloc_log:check({
+ a.freed(pt3_str_arg),
+ a.freed(pt3_argv),
+ a.freed(pt3_dict),
+ a.freed(pt3_name),
+ a.freed(pt3),
+ a.freed(kps[3][1]),
+ a.freed(qs[3]),
+ })
+ eq(false, lib.tv_dict_watcher_remove(d, cbs[3][1], #cbs[3][1], cbs[3][2][0]))
+ eq({{busy=false, pat=cbs[1][1], cb={type='none'}}}, dict_watchers(d))
+ eq(true, lib.tv_dict_watcher_remove(d, cbs[1][1], #cbs[1][1], cbs[1][2][0]))
+ alloc_log:check({
+ a.freed(kps[1][1]),
+ a.freed(qs[1]),
+ })
+ eq(false, lib.tv_dict_watcher_remove(d, cbs[1][1], #cbs[1][1], cbs[1][2][0]))
+ eq({}, dict_watchers(d))
+ end)
+ end)
+ describe('notify', function()
+ -- Way too hard to test it here, functional tests in
+ -- dict_notifications_spec.lua.
+ end)
+ end)
+ describe('item', function()
+ describe('alloc()/free()', function()
+ local function check_tv_dict_item_alloc_len(s, len, tv, more_frees)
+ local di
+ if len == nil then
+ di = ffi.gc(lib.tv_dict_item_alloc(s), nil)
+ len = #s
+ else
+ di = ffi.gc(lib.tv_dict_item_alloc_len(s, len or #s), nil)
+ end
+ eq(s:sub(1, len), ffi.string(di.di_key))
+ alloc_log:check({a.di(di, len)})
+ if tv then
+ di.di_tv = tv
+ else
+ di.di_tv.v_type = lib.VAR_UNKNOWN
+ end
+ lib.tv_dict_item_free(di)
+ alloc_log:check(concat_tables(more_frees, {a.freed(di)}))
+ end
+ local function check_tv_dict_item_alloc(s, tv, more_frees)
+ return check_tv_dict_item_alloc_len(s, nil, tv, more_frees)
+ end
+ itp('works', function()
+ check_tv_dict_item_alloc('')
+ check_tv_dict_item_alloc('t')
+ check_tv_dict_item_alloc('TEST')
+ check_tv_dict_item_alloc_len('', 0)
+ check_tv_dict_item_alloc_len('TEST', 2)
+ local tv = lua2typvalt('test')
+ alloc_log:check({a.str(tv.vval.v_string, #('test'))})
+ check_tv_dict_item_alloc('', tv, {a.freed(tv.vval.v_string)})
+ tv = lua2typvalt('test')
+ alloc_log:check({a.str(tv.vval.v_string, #('test'))})
+ check_tv_dict_item_alloc_len('', 0, tv, {a.freed(tv.vval.v_string)})
+ end)
+ end)
+ describe('add()/remove()', function()
+ itp('works', function()
+ local d = dict()
+ eq({}, dct2tbl(d))
+ alloc_log:check({a.dict(d)})
+ local di = ffi.gc(lib.tv_dict_item_alloc(''), nil)
+ local tv = lua2typvalt('test')
+ di.di_tv = tv
+ alloc_log:check({a.di(di, ''), a.str(tv.vval.v_string, 'test')})
+ eq(OK, lib.tv_dict_add(d, di))
+ alloc_log:check({})
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add(d, di) end,
+ 'E685: Internal error: hash_add()'))
+ alloc_log:clear()
+ lib.tv_dict_item_remove(d, di)
+ alloc_log:check({
+ a.freed(tv.vval.v_string),
+ a.freed(di),
+ })
+ end)
+ end)
+ end)
+ describe('indexing', function()
+ describe('find()', function()
+ local function tv_dict_find(d, key, key_len)
+ local di = lib.tv_dict_find(d, key, key_len or #key)
+ if di == nil then
+ return nil, nil, nil
+ end
+ return typvalt2lua(di.di_tv), ffi.string(di.di_key), di
+ end
+ itp('works with NULL dict', function()
+ eq(nil, lib.tv_dict_find(nil, '', 0))
+ eq(nil, lib.tv_dict_find(nil, 'test', -1))
+ eq(nil, lib.tv_dict_find(nil, nil, 0))
+ end)
+ itp('works with NULL key', function()
+ local lua_d = {
+ ['']=0,
+ t=1,
+ te=2,
+ tes=3,
+ test=4,
+ testt=5,
+ }
+ local d = dict(lua_d)
+ alloc_log:clear()
+ eq(lua_d, dct2tbl(d))
+ alloc_log:check({})
+ local dis = dict_items(d)
+ eq({0, '', dis['']}, {tv_dict_find(d, '', 0)})
+ eq({0, '', dis['']}, {tv_dict_find(d, nil, 0)})
+ end)
+ itp('works with len properly', function()
+ local lua_d = {
+ ['']=0,
+ t=1,
+ te=2,
+ tes=3,
+ test=4,
+ testt=5,
+ }
+ local d = dict(lua_d)
+ alloc_log:clear()
+ eq(lua_d, dct2tbl(d))
+ alloc_log:check({})
+ for i = 0, 5 do
+ local v, k = tv_dict_find(d, 'testt', i)
+ eq({i, ('testt'):sub(1, i)}, {v, k})
+ end
+ eq(nil, tv_dict_find(d, 'testt', 6)) -- Should take NUL byte
+ eq(5, tv_dict_find(d, 'testt', -1))
+ alloc_log:check({})
+ end)
+ end)
+ describe('get_number()', function()
+ itp('works with NULL dict', function()
+ eq(0, check_emsg(function() return lib.tv_dict_get_number(nil, 'test') end,
+ nil))
+ end)
+ itp('works', function()
+ local d = ffi.gc(dict({test={}}), nil)
+ eq(0, check_emsg(function() return lib.tv_dict_get_number(d, 'test') end,
+ 'E728: Using a Dictionary as a Number'))
+ d = ffi.gc(dict({tes=int(42), t=44, te='43'}), nil)
+ alloc_log:clear()
+ eq(0, check_emsg(function() return lib.tv_dict_get_number(d, 'test') end,
+ nil))
+ eq(42, check_emsg(function() return lib.tv_dict_get_number(d, 'tes') end,
+ nil))
+ eq(43, check_emsg(function() return lib.tv_dict_get_number(d, 'te') end,
+ nil))
+ alloc_log:check({})
+ eq(0, check_emsg(function() return lib.tv_dict_get_number(d, 't') end,
+ 'E805: Using a Float as a Number'))
+ end)
+ end)
+ describe('get_string()', function()
+ itp('works with NULL dict', function()
+ eq(nil, check_emsg(function() return lib.tv_dict_get_string(nil, 'test', false) end,
+ nil))
+ end)
+ itp('works', function()
+ local d = ffi.gc(dict({test={}}), nil)
+ eq('', ffi.string(check_emsg(function() return lib.tv_dict_get_string(d, 'test', false) end,
+ 'E731: using Dictionary as a String')))
+ d = ffi.gc(dict({tes=int(42), t=44, te='43', xx=int(45)}), nil)
+ alloc_log:clear()
+ local dis = dict_items(d)
+ eq(nil, check_emsg(function() return lib.tv_dict_get_string(d, 'test', false) end,
+ nil))
+ local s42 = check_emsg(function() return lib.tv_dict_get_string(d, 'tes', false) end,
+ nil)
+ eq('42', ffi.string(s42))
+ local s45 = check_emsg(function() return lib.tv_dict_get_string(d, 'xx', false) end,
+ nil)
+ eq(s42, s45)
+ eq('45', ffi.string(s45))
+ eq('45', ffi.string(s42))
+ local s43 = check_emsg(function() return lib.tv_dict_get_string(d, 'te', false) end,
+ nil)
+ eq('43', ffi.string(s43))
+ neq(s42, s43)
+ eq(s43, dis.te.di_tv.vval.v_string)
+ alloc_log:check({})
+ eq('', ffi.string(check_emsg(function() return lib.tv_dict_get_string(d, 't', false) end,
+ 'E806: using Float as a String')))
+ end)
+ itp('allocates a string copy when requested', function()
+ local function tv_dict_get_string_alloc(d, key, emsg)
+ alloc_log:clear()
+ local ret = check_emsg(function() return lib.tv_dict_get_string(d, key, true) end,
+ emsg)
+ local s_ret = (ret ~= nil) and ffi.string(ret) or nil
+ if not emsg then
+ if s_ret then
+ alloc_log:check({a.str(ret, s_ret)})
+ else
+ alloc_log:check({})
+ end
+ end
+ lib.xfree(ret)
+ return s_ret
+ end
+ local d = ffi.gc(dict({test={}}), nil)
+ eq('', tv_dict_get_string_alloc(d, 'test', 'E731: using Dictionary as a String'))
+ d = ffi.gc(dict({tes=int(42), t=44, te='43', xx=int(45)}), nil)
+ alloc_log:clear()
+ eq(nil, tv_dict_get_string_alloc(d, 'test'))
+ eq('42', tv_dict_get_string_alloc(d, 'tes'))
+ eq('45', tv_dict_get_string_alloc(d, 'xx'))
+ eq('43', tv_dict_get_string_alloc(d, 'te'))
+ eq('', tv_dict_get_string_alloc(d, 't', 'E806: using Float as a String'))
+ end)
+ end)
+ describe('get_string_buf()', function()
+ local function tv_dict_get_string_buf(d, key, buf, emsg)
+ buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree)
+ alloc_log:clear()
+ local ret = check_emsg(function() return lib.tv_dict_get_string_buf(d, key, buf) end,
+ emsg)
+ local s_ret = (ret ~= nil) and ffi.string(ret) or nil
+ if not emsg then
+ alloc_log:check({})
+ end
+ return s_ret, ret, buf
+ end
+ itp('works with NULL dict', function()
+ eq(nil, tv_dict_get_string_buf(nil, 'test'))
+ end)
+ itp('works', function()
+ local lua_d = {
+ ['']={},
+ t=1,
+ te=int(2),
+ tes=empty_list,
+ test='tset',
+ testt=5,
+ }
+ local d = dict(lua_d)
+ alloc_log:clear()
+ eq(lua_d, dct2tbl(d))
+ alloc_log:check({})
+ local s, r, b
+ s, r, b = tv_dict_get_string_buf(d, 'test')
+ neq(r, b)
+ eq('tset', s)
+ s, r, b = tv_dict_get_string_buf(d, 't', nil, 'E806: using Float as a String')
+ neq(r, b)
+ eq('', s)
+ s, r, b = tv_dict_get_string_buf(d, 'te')
+ eq(r, b)
+ eq('2', s)
+ end)
+ end)
+ describe('get_callback()', function()
+ local function tv_dict_get_callback(d, key, key_len, emsg)
+ key_len = key_len or #key
+ local cb = ffi.gc(ffi.cast('Callback*', lib.xmalloc(ffi.sizeof('Callback'))), lib.callback_free)
+ alloc_log:clear()
+ local ret = check_emsg(function()
+ return lib.tv_dict_get_callback(d, key, key_len, cb)
+ end, emsg)
+ local cb_lua = callback2tbl(cb[0])
+ return cb_lua, ret
+ end
+ itp('works with NULL dict', function()
+ eq({{type='none'}, true}, {tv_dict_get_callback(nil, '')})
+ end)
+ itp('works', function()
+ local lua_d = {
+ ['']='tr',
+ t=int(1),
+ te={[type_key]=func_type, value='tr'},
+ tes={[type_key]=func_type, value='tr', args={'a', 'b'}},
+ test={[type_key]=func_type, value='Test', dict={test=1}, args={}},
+ testt={[type_key]=func_type, value='Test', dict={test=1}, args={1}},
+ }
+ local d = dict(lua_d)
+ eq(lua_d, dct2tbl(d))
+ eq({{type='fref', fref='tr'}, true},
+ {tv_dict_get_callback(d, nil, 0)})
+ eq({{type='fref', fref='tr'}, true},
+ {tv_dict_get_callback(d, '', -1)})
+ eq({{type='none'}, true},
+ {tv_dict_get_callback(d, 'x', -1)})
+ eq({{type='fref', fref='tr'}, true},
+ {tv_dict_get_callback(d, 'testt', 0)})
+ eq({{type='none'}, false},
+ {tv_dict_get_callback(d, 'test', 1, 'E6000: Argument is not a function or function name')})
+ eq({{type='fref', fref='tr'}, true},
+ {tv_dict_get_callback(d, 'testt', 2)})
+ eq({{ type='pt', fref='tr', pt={ [type_key]=func_type, value='tr', args={ 'a', 'b' } } }, true},
+ {tv_dict_get_callback(d, 'testt', 3)})
+ eq({{ type='pt', fref='Test', pt={ [type_key]=func_type, value='Test', dict={ test=1 }, args={} } }, true},
+ {tv_dict_get_callback(d, 'testt', 4)})
+ eq({{ type='pt', fref='Test', pt={ [type_key]=func_type, value='Test', dict={ test=1 }, args={1} } }, true},
+ {tv_dict_get_callback(d, 'testt', 5)})
+ end)
+ end)
+ end)
+ describe('add', function()
+ describe('()', function()
+ itp('works', function()
+ local di = lib.tv_dict_item_alloc_len('t-est', 5)
+ alloc_log:check({a.di(di, 't-est')})
+ di.di_tv.v_type = lib.VAR_NUMBER
+ di.di_tv.vval.v_number = 42
+ local d = dict({test=10})
+ local dis = dict_items(d)
+ alloc_log:check({
+ a.dict(d),
+ a.di(dis.test, 'test')
+ })
+ eq({test=10}, dct2tbl(d))
+ alloc_log:clear()
+ eq(OK, lib.tv_dict_add(d, di))
+ alloc_log:check({})
+ eq({test=10, ['t-est']=int(42)}, dct2tbl(d))
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add(d, di) end,
+ 'E685: Internal error: hash_add()'))
+ end)
+ end)
+ describe('list()', function()
+ itp('works', function()
+ local l = list(1, 2, 3)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_list(d, 'testt', 3, l))
+ local dis = dict_items(d)
+ alloc_log:check({a.di(dis.tes, 'tes')})
+ eq({test=10, tes={1, 2, 3}}, dct2tbl(d))
+ eq(2, l.lv_refcount)
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_list(d, 'testt', 3, l) end,
+ 'E685: Internal error: hash_add()'))
+ eq(2, l.lv_refcount)
+ alloc_log:clear()
+ lib.emsg_skip = lib.emsg_skip + 1
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_list(d, 'testt', 3, l) end,
+ nil))
+ eq(2, l.lv_refcount)
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
+ describe('dict()', function()
+ itp('works', function()
+ local d2 = dict({foo=42})
+ alloc_log:clear()
+ eq(1, d2.dv_refcount)
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_dict(d, 'testt', 3, d2))
+ local dis = dict_items(d)
+ alloc_log:check({a.di(dis.tes, 'tes')})
+ eq({test=10, tes={foo=42}}, dct2tbl(d))
+ eq(2, d2.dv_refcount)
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_dict(d, 'testt', 3, d2) end,
+ 'E685: Internal error: hash_add()'))
+ eq(2, d2.dv_refcount)
+ alloc_log:clear()
+ lib.emsg_skip = lib.emsg_skip + 1
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_dict(d, 'testt', 3, d2) end,
+ nil))
+ eq(2, d2.dv_refcount)
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
+ describe('nr()', function()
+ itp('works', function()
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_nr(d, 'testt', 3, 2))
+ local dis = dict_items(d)
+ alloc_log:check({a.di(dis.tes, 'tes')})
+ eq({test=10, tes=int(2)}, dct2tbl(d))
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_nr(d, 'testt', 3, 2) end,
+ 'E685: Internal error: hash_add()'))
+ alloc_log:clear()
+ lib.emsg_skip = lib.emsg_skip + 1
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_nr(d, 'testt', 3, 2) end,
+ nil))
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
+ describe('str()', function()
+ itp('works', function()
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_str(d, 'testt', 3, 'TEST'))
+ local dis = dict_items(d)
+ alloc_log:check({
+ a.di(dis.tes, 'tes'),
+ a.str(dis.tes.di_tv.vval.v_string, 'TEST')
+ })
+ eq({test=10, tes='TEST'}, dct2tbl(d))
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_str(d, 'testt', 3, 'TEST') end,
+ 'E685: Internal error: hash_add()'))
+ alloc_log:clear()
+ lib.emsg_skip = lib.emsg_skip + 1
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_str(d, 'testt', 3, 'TEST') end,
+ nil))
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
+ end)
+ describe('clear()', function()
+ itp('works', function()
+ local d = dict()
+ alloc_log:check({a.dict(d)})
+ eq({}, dct2tbl(d))
+ lib.tv_dict_clear(d)
+ eq({}, dct2tbl(d))
+ lib.tv_dict_add_str(d, 'TEST', 3, 'tEsT')
+ local dis = dict_items(d)
+ local di = dis.TES
+ local di_s = di.di_tv.vval.v_string
+ alloc_log:check({a.di(di), a.str(di_s)})
+ eq({TES='tEsT'}, dct2tbl(d))
+ lib.tv_dict_clear(d)
+ alloc_log:check({a.freed(di_s), a.freed(di)})
+ eq({}, dct2tbl(d))
+ end)
+ end)
+ describe('extend()', function()
+ local function tv_dict_extend(d1, d2, action, emsg)
+ action = action or "force"
+ check_emsg(function() return lib.tv_dict_extend(d1, d2, action) end, emsg)
+ end
+ itp('works', function()
+ local d1 = dict()
+ alloc_log:check({a.dict(d1)})
+ eq({}, dct2tbl(d1))
+ local d2 = dict()
+ alloc_log:check({a.dict(d2)})
+ eq({}, dct2tbl(d2))
+ tv_dict_extend(d1, d2, 'error')
+ tv_dict_extend(d1, d2, 'keep')
+ tv_dict_extend(d1, d2, 'force')
+ alloc_log:check({})
+
+ d1 = dict({a='TEST'})
+ eq({a='TEST'}, dct2tbl(d1))
+ local dis1 = dict_items(d1)
+ local a1_s = dis1.a.di_tv.vval.v_string
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({
+ a.dict(d1),
+ a.di(dis1.a),
+ a.str(a1_s),
+ })
+ d2 = dict({a='TSET'})
+ eq({a='TSET'}, dct2tbl(d2))
+ local dis2 = dict_items(d2)
+ local a2_s = dis2.a.di_tv.vval.v_string
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({
+ a.dict(d2),
+ a.di(dis2.a),
+ a.str(a2_s),
+ })
+
+ tv_dict_extend(d1, d2, 'error', 'E737: Key already exists: a')
+ eq({a='TEST'}, dct2tbl(d1))
+ eq({a='TSET'}, dct2tbl(d2))
+ alloc_log:clear()
+
+ tv_dict_extend(d1, d2, 'keep')
+ alloc_log:check({})
+ eq({a='TEST'}, dct2tbl(d1))
+ eq({a='TSET'}, dct2tbl(d2))
+
+ tv_dict_extend(d1, d2, 'force')
+ alloc_log:check({
+ a.freed(a1_s),
+ a.str(dis1.a.di_tv.vval.v_string),
+ })
+ eq({a='TSET'}, dct2tbl(d1))
+ eq({a='TSET'}, dct2tbl(d2))
+ end)
+ itp('disallows overriding builtin or user functions', function()
+ local d = dict()
+ d.dv_scope = lib.VAR_DEF_SCOPE
+ local f_lua = {
+ [type_key]=func_type,
+ value='tr',
+ }
+ local f_tv = lua2typvalt(f_lua)
+ local p_lua = {
+ [type_key]=func_type,
+ value='tr',
+ args={1},
+ }
+ local p_tv = lua2typvalt(p_lua)
+ eq(lib.VAR_PARTIAL, p_tv.v_type)
+ local d2 = dict({tr=f_tv})
+ local d3 = dict({tr=p_tv})
+ local d4 = dict({['TEST:THIS']=p_tv})
+ local d5 = dict({Test=f_tv})
+ local d6 = dict({Test=p_tv})
+ eval0([[execute("function Test()\nendfunction")]])
+ tv_dict_extend(d, d2, 'force',
+ 'E704: Funcref variable name must start with a capital: tr')
+ tv_dict_extend(d, d3, 'force',
+ 'E704: Funcref variable name must start with a capital: tr')
+ tv_dict_extend(d, d4, 'force',
+ 'E461: Illegal variable name: TEST:THIS')
+ tv_dict_extend(d, d5, 'force',
+ 'E705: Variable name conflicts with existing function: Test')
+ tv_dict_extend(d, d6, 'force',
+ 'E705: Variable name conflicts with existing function: Test')
+ eq({}, dct2tbl(d))
+ d.dv_scope = lib.VAR_SCOPE
+ tv_dict_extend(d, d4, 'force',
+ 'E461: Illegal variable name: TEST:THIS')
+ eq({}, dct2tbl(d))
+ tv_dict_extend(d, d2, 'force')
+ eq({tr=f_lua}, dct2tbl(d))
+ tv_dict_extend(d, d3, 'force')
+ eq({tr=p_lua}, dct2tbl(d))
+ tv_dict_extend(d, d5, 'force')
+ eq({tr=p_lua, Test=f_lua}, dct2tbl(d))
+ tv_dict_extend(d, d6, 'force')
+ eq({tr=p_lua, Test=p_lua}, dct2tbl(d))
+ end)
+ itp('cares about locks and read-only items', function()
+ local d_lua = {tv_locked=1, tv_fixed=2, di_ro=3, di_ro_sbx=4}
+ local d = dict(d_lua)
+ local dis = dict_items(d)
+ dis.tv_locked.di_tv.v_lock = lib.VAR_LOCKED
+ dis.tv_fixed.di_tv.v_lock = lib.VAR_FIXED
+ dis.di_ro.di_flags = bit.bor(dis.di_ro.di_flags, lib.DI_FLAGS_RO)
+ dis.di_ro_sbx.di_flags = bit.bor(dis.di_ro_sbx.di_flags, lib.DI_FLAGS_RO_SBX)
+ lib.sandbox = true
+ local d1 = dict({tv_locked=41})
+ local d2 = dict({tv_fixed=42})
+ local d3 = dict({di_ro=43})
+ local d4 = dict({di_ro_sbx=44})
+ tv_dict_extend(d, d1, 'force', 'E741: Value is locked: extend() argument')
+ tv_dict_extend(d, d2, 'force', 'E742: Cannot change value of extend() argument')
+ tv_dict_extend(d, d3, 'force', 'E46: Cannot change read-only variable "extend() argument"')
+ tv_dict_extend(d, d4, 'force', 'E794: Cannot set variable in the sandbox: "extend() argument"')
+ eq(d_lua, dct2tbl(d))
+ lib.sandbox = false
+ tv_dict_extend(d, d4, 'force')
+ d_lua.di_ro_sbx = 44
+ eq(d_lua, dct2tbl(d))
+ end)
+ end)
+ describe('equal()', function()
+ local function tv_dict_equal(d1, d2, ic, recursive)
+ return lib.tv_dict_equal(d1, d2, ic or false, recursive or false)
+ end
+ itp('works', function()
+ eq(true, tv_dict_equal(nil, nil))
+ local d1 = dict()
+ alloc_log:check({a.dict(d1)})
+ eq(1, d1.dv_refcount)
+ eq(false, tv_dict_equal(nil, d1))
+ eq(false, tv_dict_equal(d1, nil))
+ eq(true, tv_dict_equal(d1, d1))
+ eq(1, d1.dv_refcount)
+ alloc_log:check({})
+ local d_upper = dict({a='TEST'})
+ local dis_upper = dict_items(d_upper)
+ local d_lower = dict({a='test'})
+ local dis_lower = dict_items(d_lower)
+ local d_kupper_upper = dict({A='TEST'})
+ local dis_kupper_upper = dict_items(d_kupper_upper)
+ local d_kupper_lower = dict({A='test'})
+ local dis_kupper_lower = dict_items(d_kupper_lower)
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({
+ a.dict(d_upper),
+ a.di(dis_upper.a),
+ a.str(dis_upper.a.di_tv.vval.v_string),
+
+ a.dict(d_lower),
+ a.di(dis_lower.a),
+ a.str(dis_lower.a.di_tv.vval.v_string),
+
+ a.dict(d_kupper_upper),
+ a.di(dis_kupper_upper.A),
+ a.str(dis_kupper_upper.A.di_tv.vval.v_string),
+
+ a.dict(d_kupper_lower),
+ a.di(dis_kupper_lower.A),
+ a.str(dis_kupper_lower.A.di_tv.vval.v_string),
+ })
+ eq(true, tv_dict_equal(d_upper, d_upper))
+ eq(true, tv_dict_equal(d_upper, d_upper, true))
+ eq(false, tv_dict_equal(d_upper, d_lower, false))
+ eq(true, tv_dict_equal(d_upper, d_lower, true))
+ eq(true, tv_dict_equal(d_kupper_upper, d_kupper_lower, true))
+ eq(false, tv_dict_equal(d_kupper_upper, d_lower, true))
+ eq(false, tv_dict_equal(d_kupper_upper, d_upper, true))
+ eq(true, tv_dict_equal(d_upper, d_upper, true, true))
+ alloc_log:check({})
+ end)
+ end)
+ describe('copy()', function()
+ local function tv_dict_copy(...)
+ return ffi.gc(lib.tv_dict_copy(...), lib.tv_dict_unref)
+ end
+ itp('copies NULL correctly', function()
+ eq(nil, lib.tv_dict_copy(nil, nil, true, 0))
+ eq(nil, lib.tv_dict_copy(nil, nil, false, 0))
+ eq(nil, lib.tv_dict_copy(nil, nil, true, 1))
+ eq(nil, lib.tv_dict_copy(nil, nil, false, 1))
+ end)
+ itp('copies dict correctly without converting items', function()
+ do
+ local v = {a={['«']='»'}, b={'„'}, ['1']=1, ['«»']='“', ns=null_string, nl=null_list, nd=null_dict}
+ local d_tv = lua2typvalt(v)
+ local d = d_tv.vval.v_dict
+ local dis = dict_items(d)
+ alloc_log:clear()
+
+ eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
+ eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
+ local d_copy1 = tv_dict_copy(nil, d, false, 0)
+ eq(2, dis.a.di_tv.vval.v_dict.dv_refcount)
+ eq(2, dis.b.di_tv.vval.v_list.lv_refcount)
+ local dis_copy1 = dict_items(d_copy1)
+ eq(dis.a.di_tv.vval.v_dict, dis_copy1.a.di_tv.vval.v_dict)
+ eq(dis.b.di_tv.vval.v_list, dis_copy1.b.di_tv.vval.v_list)
+ eq(v, dct2tbl(d_copy1))
+ alloc_log:clear()
+ lib.tv_dict_free(ffi.gc(d_copy1, nil))
+ alloc_log:clear()
+
+ eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
+ eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
+ local d_deepcopy1 = tv_dict_copy(nil, d, true, 0)
+ neq(nil, d_deepcopy1)
+ eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
+ eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
+ local dis_deepcopy1 = dict_items(d_deepcopy1)
+ neq(dis.a.di_tv.vval.v_dict, dis_deepcopy1.a.di_tv.vval.v_dict)
+ neq(dis.b.di_tv.vval.v_list, dis_deepcopy1.b.di_tv.vval.v_list)
+ eq(v, dct2tbl(d_deepcopy1))
+ alloc_log:clear()
+ end
+ collectgarbage()
+ end)
+ itp('copies dict correctly and converts items', function()
+ local vc = ffi.gc(ffi.new('vimconv_T[1]'), function(vc)
+ lib.convert_setup(vc, nil, nil)
+ end)
+ -- UTF-8 ↔ latin1 conversions need no iconv
+ eq(OK, lib.convert_setup(vc, to_cstr('utf-8'), to_cstr('latin1')))
+
+ local v = {a={['«']='»'}, b={'„'}, ['1']=1, ['«»']='“', ns=null_string, nl=null_list, nd=null_dict}
+ local d_tv = lua2typvalt(v)
+ local d = d_tv.vval.v_dict
+ local dis = dict_items(d)
+ alloc_log:clear()
+
+ eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
+ eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
+ local d_deepcopy1 = tv_dict_copy(vc, d, true, 0)
+ neq(nil, d_deepcopy1)
+ eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
+ eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
+ local dis_deepcopy1 = dict_items(d_deepcopy1)
+ neq(dis.a.di_tv.vval.v_dict, dis_deepcopy1.a.di_tv.vval.v_dict)
+ neq(dis.b.di_tv.vval.v_list, dis_deepcopy1.b.di_tv.vval.v_list)
+ eq({a={['\171']='\187'}, b={'\191'}, ['1']=1, ['\171\187']='\191', ns=null_string, nl=null_list, nd=null_dict},
+ dct2tbl(d_deepcopy1))
+ alloc_log:clear_tmp_allocs()
+ alloc_log:clear()
+ end)
+ itp('returns different/same containers with(out) copyID', function()
+ local d_inner_tv = lua2typvalt({})
+ local d_tv = lua2typvalt({a=d_inner_tv, b=d_inner_tv})
+ eq(3, d_inner_tv.vval.v_dict.dv_refcount)
+ local d = d_tv.vval.v_dict
+ local dis = dict_items(d)
+ eq(dis.a.di_tv.vval.v_dict, dis.b.di_tv.vval.v_dict)
+
+ local d_copy1 = tv_dict_copy(nil, d, true, 0)
+ local dis_copy1 = dict_items(d_copy1)
+ neq(dis_copy1.a.di_tv.vval.v_dict, dis_copy1.b.di_tv.vval.v_dict)
+ eq({a={}, b={}}, dct2tbl(d_copy1))
+
+ local d_copy2 = tv_dict_copy(nil, d, true, 2)
+ local dis_copy2 = dict_items(d_copy2)
+ eq(dis_copy2.a.di_tv.vval.v_dict, dis_copy2.b.di_tv.vval.v_dict)
+ eq({a={}, b={}}, dct2tbl(d_copy2))
+
+ eq(3, d_inner_tv.vval.v_dict.dv_refcount)
+ end)
+ itp('works with self-referencing dict with copyID', function()
+ local d_tv = lua2typvalt({})
+ local d = d_tv.vval.v_dict
+ eq(1, d.dv_refcount)
+ lib.tv_dict_add_dict(d, 'test', 4, d)
+ eq(2, d.dv_refcount)
+
+ local d_copy1 = tv_dict_copy(nil, d, true, 2)
+ eq(2, d_copy1.dv_refcount)
+ local v = {}
+ v.test = v
+ eq(v, dct2tbl(d_copy1))
+
+ lib.tv_dict_clear(d)
+ eq(1, d.dv_refcount)
+
+ lib.tv_dict_clear(d_copy1)
+ eq(1, d_copy1.dv_refcount)
+ end)
+ end)
+ describe('set_keys_readonly()', function()
+ itp('works', function()
+ local d = dict({a=true})
+ local dis = dict_items(d)
+ alloc_log:check({a.dict(d), a.di(dis.a)})
+ eq(0, bit.band(dis.a.di_flags, lib.DI_FLAGS_RO))
+ eq(0, bit.band(dis.a.di_flags, lib.DI_FLAGS_FIX))
+ lib.tv_dict_set_keys_readonly(d)
+ alloc_log:check({})
+ eq(lib.DI_FLAGS_RO, bit.band(dis.a.di_flags, lib.DI_FLAGS_RO))
+ eq(lib.DI_FLAGS_FIX, bit.band(dis.a.di_flags, lib.DI_FLAGS_FIX))
+ end)
+ end)
+ end)
+ describe('tv', function()
+ describe('alloc', function()
+ describe('list ret()', function()
+ itp('works', function()
+ local rettv = typvalt(lib.VAR_UNKNOWN)
+ local l = lib.tv_list_alloc_ret(rettv)
+ eq(empty_list, typvalt2lua(rettv))
+ eq(rettv.vval.v_list, l)
+ end)
+ end)
+ describe('dict ret()', function()
+ itp('works', function()
+ local rettv = typvalt(lib.VAR_UNKNOWN)
+ lib.tv_dict_alloc_ret(rettv)
+ eq({}, typvalt2lua(rettv))
+ end)
+ end)
+ end)
+ local function defalloc()
+ return {}
+ end
+ describe('clear()', function()
+ itp('works', function()
+ local function deffrees(alloc_rets)
+ local ret = {}
+ for i = #alloc_rets, 1, -1 do
+ ret[#alloc_rets - i + 1] = alloc_rets:freed(i)
+ end
+ return ret
+ end
+ alloc_log:check({})
+ lib.tv_clear(nil)
+ alloc_log:check({})
+ local ll = {}
+ local ll_l = nil
+ ll[1] = ll
+ local dd = {}
+ local dd_d = nil
+ dd.dd = dd
+ for _, v in ipairs({
+ {nil_value},
+ {null_string, nil, function() return {a.freed(alloc_log.null)} end},
+ {0},
+ {int(0)},
+ {true},
+ {false},
+ {'true', function(tv) return {a.str(tv.vval.v_string)} end},
+ {{}, function(tv) return {a.dict(tv.vval.v_dict)} end},
+ {empty_list, function(tv) return {a.list(tv.vval.v_list)} end},
+ {ll, function(tv)
+ ll_l = tv.vval.v_list
+ return {a.list(tv.vval.v_list), a.li(tv.vval.v_list.lv_first)}
+ end, defalloc},
+ {dd, function(tv)
+ dd_d = tv.vval.v_dict
+ return {a.dict(tv.vval.v_dict), a.di(first_di(tv.vval.v_dict))}
+ end, defalloc},
+ }) do
+ local tv = lua2typvalt(v[1])
+ local alloc_rets = {}
+ alloc_log:check(get_alloc_rets((v[2] or defalloc)(tv), alloc_rets))
+ lib.tv_clear(tv)
+ alloc_log:check((v[3] or deffrees)(alloc_rets))
+ end
+ eq(1, ll_l.lv_refcount)
+ eq(1, dd_d.dv_refcount)
+ end)
+ end)
+ describe('copy()', function()
+ itp('works', function()
+ local function strallocs(tv)
+ return {a.str(tv.vval.v_string)}
+ end
+ for _, v in ipairs({
+ {nil_value},
+ {null_string},
+ {0},
+ {int(0)},
+ {true},
+ {false},
+ {{}, function(tv) return {a.dict(tv.vval.v_dict)} end, nil, function(from, to)
+ eq(2, to.vval.v_dict.dv_refcount)
+ eq(to.vval.v_dict, from.vval.v_dict)
+ end},
+ {empty_list, function(tv) return {a.list(tv.vval.v_list)} end, nil, function(from, to)
+ eq(2, to.vval.v_list.lv_refcount)
+ eq(to.vval.v_list, from.vval.v_list)
+ end},
+ {'test', strallocs, strallocs, function(from, to)
+ neq(to.vval.v_string, from.vval.v_string)
+ end},
+ }) do
+ local from = lua2typvalt(v[1])
+ alloc_log:check((v[2] or defalloc)(from))
+ local to = typvalt(lib.VAR_UNKNOWN)
+ lib.tv_copy(from, to)
+ local res = v[1]
+ eq(res, typvalt2lua(to))
+ alloc_log:check((v[3] or defalloc)(to))
+ if v[4] then
+ v[4](from, to)
+ end
+ end
+ end)
+ end)
+ describe('item_lock()', function()
+ itp('does not alter VAR_PARTIAL', function()
+ local p_tv = lua2typvalt({
+ [type_key]=func_type,
+ value='tr',
+ dict={},
+ })
+ lib.tv_item_lock(p_tv, -1, true)
+ eq(lib.VAR_UNLOCKED, p_tv.vval.v_partial.pt_dict.dv_lock)
+ end)
+ itp('does not change VAR_FIXED values', function()
+ local d_tv = lua2typvalt({})
+ local l_tv = lua2typvalt(empty_list)
+ alloc_log:clear()
+ d_tv.v_lock = lib.VAR_FIXED
+ d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED
+ l_tv.v_lock = lib.VAR_FIXED
+ l_tv.vval.v_list.lv_lock = lib.VAR_FIXED
+ lib.tv_item_lock(d_tv, 1, true)
+ lib.tv_item_lock(l_tv, 1, true)
+ eq(lib.VAR_FIXED, d_tv.v_lock)
+ eq(lib.VAR_FIXED, l_tv.v_lock)
+ eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock)
+ eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
+ lib.tv_item_lock(d_tv, 1, false)
+ lib.tv_item_lock(l_tv, 1, false)
+ eq(lib.VAR_FIXED, d_tv.v_lock)
+ eq(lib.VAR_FIXED, l_tv.v_lock)
+ eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock)
+ eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
+ alloc_log:check({})
+ end)
+ itp('works with NULL values', function()
+ local l_tv = lua2typvalt(null_list)
+ local d_tv = lua2typvalt(null_dict)
+ local s_tv = lua2typvalt(null_string)
+ alloc_log:clear()
+ lib.tv_item_lock(l_tv, 1, true)
+ lib.tv_item_lock(d_tv, 1, true)
+ lib.tv_item_lock(s_tv, 1, true)
+ eq(null_list, typvalt2lua(l_tv))
+ eq(null_dict, typvalt2lua(d_tv))
+ eq(null_string, typvalt2lua(s_tv))
+ eq(lib.VAR_LOCKED, d_tv.v_lock)
+ eq(lib.VAR_LOCKED, l_tv.v_lock)
+ eq(lib.VAR_LOCKED, s_tv.v_lock)
+ alloc_log:check({})
+ end)
+ end)
+ describe('islocked()', function()
+ itp('works with NULL values', function()
+ local l_tv = lua2typvalt(null_list)
+ local d_tv = lua2typvalt(null_dict)
+ eq(false, lib.tv_islocked(l_tv))
+ eq(false, lib.tv_islocked(d_tv))
+ end)
+ itp('works', function()
+ local tv = lua2typvalt()
+ local d_tv = lua2typvalt({})
+ local l_tv = lua2typvalt(empty_list)
+ alloc_log:clear()
+ eq(false, lib.tv_islocked(tv))
+ eq(false, lib.tv_islocked(l_tv))
+ eq(false, lib.tv_islocked(d_tv))
+ d_tv.vval.v_dict.dv_lock = lib.VAR_LOCKED
+ l_tv.vval.v_list.lv_lock = lib.VAR_LOCKED
+ eq(true, lib.tv_islocked(l_tv))
+ eq(true, lib.tv_islocked(d_tv))
+ tv.v_lock = lib.VAR_LOCKED
+ d_tv.v_lock = lib.VAR_LOCKED
+ l_tv.v_lock = lib.VAR_LOCKED
+ eq(true, lib.tv_islocked(tv))
+ eq(true, lib.tv_islocked(l_tv))
+ eq(true, lib.tv_islocked(d_tv))
+ d_tv.vval.v_dict.dv_lock = lib.VAR_UNLOCKED
+ l_tv.vval.v_list.lv_lock = lib.VAR_UNLOCKED
+ eq(true, lib.tv_islocked(tv))
+ eq(true, lib.tv_islocked(l_tv))
+ eq(true, lib.tv_islocked(d_tv))
+ tv.v_lock = lib.VAR_FIXED
+ d_tv.v_lock = lib.VAR_FIXED
+ l_tv.v_lock = lib.VAR_FIXED
+ eq(false, lib.tv_islocked(tv))
+ eq(false, lib.tv_islocked(l_tv))
+ eq(false, lib.tv_islocked(d_tv))
+ d_tv.vval.v_dict.dv_lock = lib.VAR_LOCKED
+ l_tv.vval.v_list.lv_lock = lib.VAR_LOCKED
+ eq(true, lib.tv_islocked(l_tv))
+ eq(true, lib.tv_islocked(d_tv))
+ d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED
+ l_tv.vval.v_list.lv_lock = lib.VAR_FIXED
+ eq(false, lib.tv_islocked(l_tv))
+ eq(false, lib.tv_islocked(d_tv))
+ alloc_log:check({})
+ end)
+ end)
+ describe('check_lock()', function()
+ local function tv_check_lock(lock, name, name_len, emsg)
+ return check_emsg(function()
+ return lib.tv_check_lock(lock, name, name_len)
+ end, emsg)
+ end
+ itp('works', function()
+ eq(false, tv_check_lock(lib.VAR_UNLOCKED, 'test', 3))
+ eq(true, tv_check_lock(lib.VAR_LOCKED, 'test', 3,
+ 'E741: Value is locked: tes'))
+ eq(true, tv_check_lock(lib.VAR_FIXED, 'test', 3,
+ 'E742: Cannot change value of tes'))
+ eq(true, tv_check_lock(lib.VAR_LOCKED, nil, 0,
+ 'E741: Value is locked: Unknown'))
+ eq(true, tv_check_lock(lib.VAR_FIXED, nil, 0,
+ 'E742: Cannot change value of Unknown'))
+ end)
+ end)
+ describe('equal()', function()
+ itp('compares empty and NULL lists correctly', function()
+ local l = lua2typvalt(empty_list)
+ local l2 = lua2typvalt(empty_list)
+ local nl = lua2typvalt(null_list)
+
+ -- NULL lists are not equal to empty lists
+ eq(false, lib.tv_equal(l, nl, true, false))
+ eq(false, lib.tv_equal(nl, l, false, false))
+ eq(false, lib.tv_equal(nl, l, false, true))
+ eq(false, lib.tv_equal(l, nl, true, true))
+
+ -- Yet NULL lists are equal themselves
+ eq(true, lib.tv_equal(nl, nl, true, false))
+ eq(true, lib.tv_equal(nl, nl, false, false))
+ eq(true, lib.tv_equal(nl, nl, false, true))
+ eq(true, lib.tv_equal(nl, nl, true, true))
+
+ -- As well as empty lists
+ eq(true, lib.tv_equal(l, l, true, false))
+ eq(true, lib.tv_equal(l, l2, false, false))
+ eq(true, lib.tv_equal(l2, l, false, true))
+ eq(true, lib.tv_equal(l2, l2, true, true))
+ end)
+ -- Must not use recursive=true argument in the following tests because it
+ -- indicates that tv_equal_recurse_limit and recursive_cnt were set which
+ -- is essential. This argument will be set when comparing inner lists.
+ itp('compares lists correctly when case is not ignored', function()
+ local l1 = lua2typvalt({'abc', {1, 2, 'Abc'}, 'def'})
+ local l2 = lua2typvalt({'abc', {1, 2, 'Abc'}})
+ local l3 = lua2typvalt({'abc', {1, 2, 'Abc'}, 'Def'})
+ local l4 = lua2typvalt({'abc', {1, 2, 'Abc', 4}, 'def'})
+ local l5 = lua2typvalt({'Abc', {1, 2, 'Abc'}, 'def'})
+ local l6 = lua2typvalt({'abc', {1, 2, 'Abc'}, 'def'})
+ local l7 = lua2typvalt({'abc', {1, 2, 'abc'}, 'def'})
+ local l8 = lua2typvalt({'abc', nil, 'def'})
+ local l9 = lua2typvalt({'abc', {1, 2, nil}, 'def'})
+
+ eq(true, lib.tv_equal(l1, l1, false, false))
+ eq(false, lib.tv_equal(l1, l2, false, false))
+ eq(false, lib.tv_equal(l1, l3, false, false))
+ eq(false, lib.tv_equal(l1, l4, false, false))
+ eq(false, lib.tv_equal(l1, l5, false, false))
+ eq(true, lib.tv_equal(l1, l6, false, false))
+ eq(false, lib.tv_equal(l1, l7, false, false))
+ eq(false, lib.tv_equal(l1, l8, false, false))
+ eq(false, lib.tv_equal(l1, l9, false, false))
+ end)
+ itp('compares lists correctly when case is ignored', function()
+ local l1 = lua2typvalt({'abc', {1, 2, 'Abc'}, 'def'})
+ local l2 = lua2typvalt({'abc', {1, 2, 'Abc'}})
+ local l3 = lua2typvalt({'abc', {1, 2, 'Abc'}, 'Def'})
+ local l4 = lua2typvalt({'abc', {1, 2, 'Abc', 4}, 'def'})
+ local l5 = lua2typvalt({'Abc', {1, 2, 'Abc'}, 'def'})
+ local l6 = lua2typvalt({'abc', {1, 2, 'Abc'}, 'def'})
+ local l7 = lua2typvalt({'abc', {1, 2, 'abc'}, 'def'})
+ local l8 = lua2typvalt({'abc', nil, 'def'})
+ local l9 = lua2typvalt({'abc', {1, 2, nil}, 'def'})
+
+ eq(true, lib.tv_equal(l1, l1, true, false))
+ eq(false, lib.tv_equal(l1, l2, true, false))
+ eq(true, lib.tv_equal(l1, l3, true, false))
+ eq(false, lib.tv_equal(l1, l4, true, false))
+ eq(true, lib.tv_equal(l1, l5, true, false))
+ eq(true, lib.tv_equal(l1, l6, true, false))
+ eq(true, lib.tv_equal(l1, l7, true, false))
+ eq(false, lib.tv_equal(l1, l8, true, false))
+ eq(false, lib.tv_equal(l1, l9, true, false))
+ end)
+ local function tv_equal(d1, d2, ic, recursive)
+ return lib.tv_equal(d1, d2, ic or false, recursive or false)
+ end
+ itp('works with dictionaries', function()
+ local nd = lua2typvalt(null_dict)
+ eq(true, tv_equal(nd, nd))
+ alloc_log:check({})
+ local d1 = lua2typvalt({})
+ alloc_log:check({a.dict(d1.vval.v_dict)})
+ eq(1, d1.vval.v_dict.dv_refcount)
+ eq(false, tv_equal(nd, d1))
+ eq(false, tv_equal(d1, nd))
+ eq(true, tv_equal(d1, d1))
+ eq(1, d1.vval.v_dict.dv_refcount)
+ alloc_log:check({})
+ local d_upper = lua2typvalt({a='TEST'})
+ local dis_upper = dict_items(d_upper.vval.v_dict)
+ local d_lower = lua2typvalt({a='test'})
+ local dis_lower = dict_items(d_lower.vval.v_dict)
+ local d_kupper_upper = lua2typvalt({A='TEST'})
+ local dis_kupper_upper = dict_items(d_kupper_upper.vval.v_dict)
+ local d_kupper_lower = lua2typvalt({A='test'})
+ local dis_kupper_lower = dict_items(d_kupper_lower.vval.v_dict)
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({
+ a.dict(d_upper.vval.v_dict),
+ a.di(dis_upper.a),
+ a.str(dis_upper.a.di_tv.vval.v_string),
+
+ a.dict(d_lower.vval.v_dict),
+ a.di(dis_lower.a),
+ a.str(dis_lower.a.di_tv.vval.v_string),
+
+ a.dict(d_kupper_upper.vval.v_dict),
+ a.di(dis_kupper_upper.A),
+ a.str(dis_kupper_upper.A.di_tv.vval.v_string),
+
+ a.dict(d_kupper_lower.vval.v_dict),
+ a.di(dis_kupper_lower.A),
+ a.str(dis_kupper_lower.A.di_tv.vval.v_string),
+ })
+ eq(true, tv_equal(d_upper, d_upper))
+ eq(true, tv_equal(d_upper, d_upper, true))
+ eq(false, tv_equal(d_upper, d_lower, false))
+ eq(true, tv_equal(d_upper, d_lower, true))
+ eq(true, tv_equal(d_kupper_upper, d_kupper_lower, true))
+ eq(false, tv_equal(d_kupper_upper, d_lower, true))
+ eq(false, tv_equal(d_kupper_upper, d_upper, true))
+ eq(true, tv_equal(d_upper, d_upper, true, true))
+ alloc_log:check({})
+ end)
+ end)
+ describe('check', function()
+ describe('str_or_nr()', function()
+ itp('works', function()
+ local tv = typvalt()
+ local mem = lib.xmalloc(1)
+ tv.vval.v_list = mem -- Should crash when actually accessed
+ alloc_log:clear()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, nil},
+ {lib.VAR_FLOAT, 'E805: Expected a Number or a String, Float found'},
+ {lib.VAR_PARTIAL, 'E703: Expected a Number or a String, Funcref found'},
+ {lib.VAR_FUNC, 'E703: Expected a Number or a String, Funcref found'},
+ {lib.VAR_LIST, 'E745: Expected a Number or a String, List found'},
+ {lib.VAR_DICT, 'E728: Expected a Number or a String, Dictionary found'},
+ {lib.VAR_SPECIAL, 'E5300: Expected a Number or a String'},
+ {lib.VAR_UNKNOWN, 'E685: Internal error: tv_check_str_or_nr(UNKNOWN)'},
+ }) do
+ local typ = v[1]
+ local emsg = v[2]
+ local ret = true
+ if emsg then ret = false end
+ tv.v_type = typ
+ eq(ret, check_emsg(function() return lib.tv_check_str_or_nr(tv) end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('num()', function()
+ itp('works', function()
+ local tv = typvalt()
+ local mem = lib.xmalloc(1)
+ tv.vval.v_list = mem -- Should crash when actually accessed
+ alloc_log:clear()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, nil},
+ {lib.VAR_FLOAT, 'E805: Using a Float as a Number'},
+ {lib.VAR_PARTIAL, 'E703: Using a Funcref as a Number'},
+ {lib.VAR_FUNC, 'E703: Using a Funcref as a Number'},
+ {lib.VAR_LIST, 'E745: Using a List as a Number'},
+ {lib.VAR_DICT, 'E728: Using a Dictionary as a Number'},
+ {lib.VAR_SPECIAL, nil},
+ {lib.VAR_UNKNOWN, 'E685: using an invalid value as a Number'},
+ }) do
+ local typ = v[1]
+ local emsg = v[2]
+ local ret = true
+ if emsg then ret = false end
+ tv.v_type = typ
+ eq(ret, check_emsg(function() return lib.tv_check_num(tv) end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('str()', function()
+ itp('works', function()
+ local tv = typvalt()
+ local mem = lib.xmalloc(1)
+ tv.vval.v_list = mem -- Should crash when actually accessed
+ alloc_log:clear()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, nil},
+ {lib.VAR_FLOAT, 'E806: using Float as a String'},
+ {lib.VAR_PARTIAL, 'E729: using Funcref as a String'},
+ {lib.VAR_FUNC, 'E729: using Funcref as a String'},
+ {lib.VAR_LIST, 'E730: using List as a String'},
+ {lib.VAR_DICT, 'E731: using Dictionary as a String'},
+ {lib.VAR_SPECIAL, nil},
+ {lib.VAR_UNKNOWN, 'E908: using an invalid value as a String'},
+ }) do
+ local typ = v[1]
+ local emsg = v[2]
+ local ret = true
+ if emsg then ret = false end
+ tv.v_type = typ
+ eq(ret, check_emsg(function() return lib.tv_check_str(tv) end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ end)
+ describe('get', function()
+ describe('number()', function()
+ itp('works', function()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, 42},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, 100500},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E805: Using a Float as a Number', 0},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E703: Using a Funcref as a Number', 0},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E703: Using a Funcref as a Number', 0},
+ {lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function() return lib.tv_get_number(tv) end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('number_chk()', function()
+ itp('works', function()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, 42},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, 100500},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E805: Using a Float as a Number', 0},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E703: Using a Funcref as a Number', 0},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E703: Using a Funcref as a Number', 0},
+ {lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = {v[4], not not emsg}
+ eq(ret, check_emsg(function()
+ local err = ffi.new('bool[1]', {false})
+ local res = lib.tv_get_number_chk(tv, err)
+ return {res, err[0]}
+ end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('lnum()', function()
+ itp('works', function()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, 42},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, 100500},
+ {lib.VAR_STRING, {v_string=to_cstr('.')}, nil, 46},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E805: Using a Float as a Number', -1},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E703: Using a Funcref as a Number', -1},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E703: Using a Funcref as a Number', -1},
+ {lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', -1},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', -1},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', -1},
+ }) do
+ lib.curwin.w_cursor.lnum = 46
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function() return lib.tv_get_lnum(tv) end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('float()', function()
+ itp('works', function()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, 42},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, 'E892: Using a String as a Float', 0},
+ {lib.VAR_FLOAT, {v_float=42.53}, nil, 42.53},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E891: Using a Funcref as a Float', 0},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E891: Using a Funcref as a Float', 0},
+ {lib.VAR_LIST, {v_list=NULL}, 'E893: Using a List as a Float', 0},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E894: Using a Dictionary as a Float', 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, 'E907: Using a special value as a Float', 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, 'E907: Using a special value as a Float', 0},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, 'E907: Using a special value as a Float', 0},
+ {lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_float(UNKNOWN)', 0},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function() return lib.tv_get_float(tv) end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('string()', function()
+ itp('works', function()
+ local buf = lib.tv_get_string(lua2typvalt(int(1)))
+ local buf_chk = lib.tv_get_string_chk(lua2typvalt(int(1)))
+ neq(buf, buf_chk)
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, '42'},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', ''},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', ''},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function()
+ local res = lib.tv_get_string(tv)
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ eq(buf, res)
+ else
+ neq(buf, res)
+ end
+ if res ~= nil then
+ return ffi.string(res)
+ else
+ return nil
+ end
+ end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('string_chk()', function()
+ itp('works', function()
+ local buf = lib.tv_get_string_chk(lua2typvalt(int(1)))
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, '42'},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', nil},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', nil},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function()
+ local res = lib.tv_get_string_chk(tv)
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ eq(buf, res)
+ else
+ neq(buf, res)
+ end
+ if res ~= nil then
+ return ffi.string(res)
+ else
+ return nil
+ end
+ end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('string_buf()', function()
+ itp('works', function()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, '42'},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', ''},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', ''},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function()
+ local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
+ local res = lib.tv_get_string_buf(tv, buf)
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ eq(buf, res)
+ else
+ neq(buf, res)
+ end
+ if res ~= nil then
+ return ffi.string(res)
+ else
+ return nil
+ end
+ end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ describe('string_buf_chk()', function()
+ itp('works', function()
+ for _, v in ipairs({
+ {lib.VAR_NUMBER, {v_number=42}, nil, '42'},
+ {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
+ {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', nil},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', nil},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
+ {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
+ }) do
+ local tv = typvalt(v[1], v[2])
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function()
+ local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
+ local res = lib.tv_get_string_buf_chk(tv, buf)
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ eq(buf, res)
+ else
+ neq(buf, res)
+ end
+ if res ~= nil then
+ return ffi.string(res)
+ else
+ return nil
+ end
+ end, emsg))
+ if emsg then
+ alloc_log:clear()
+ else
+ alloc_log:check({})
+ end
+ end
+ end)
+ end)
+ end)
+ end)
+end)
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 612b337ee7..74f214a231 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -11,6 +11,7 @@ local posix = nil
local syscall = nil
local check_cores = global_helpers.check_cores
+local dedent = global_helpers.dedent
local neq = global_helpers.neq
local map = global_helpers.map
local eq = global_helpers.eq
@@ -314,6 +315,29 @@ local function alloc_log_new()
eq(exp, self.log)
self:clear()
end
+ function log:clear_tmp_allocs()
+ local toremove = {}
+ local allocs = {}
+ for i, v in ipairs(self.log) do
+ if v.func == 'malloc' or v.func == 'calloc' then
+ allocs[tostring(v.ret)] = i
+ elseif v.func == 'realloc' or v.func == 'free' then
+ if allocs[tostring(v.args[1])] then
+ toremove[#toremove + 1] = allocs[tostring(v.args[1])]
+ if v.func == 'free' then
+ toremove[#toremove + 1] = i
+ end
+ end
+ if v.func == 'realloc' then
+ allocs[tostring(v.ret)] = i
+ end
+ end
+ end
+ table.sort(toremove)
+ for i = #toremove,1,-1 do
+ table.remove(self.log, toremove[i])
+ end
+ end
function log:restore_original_functions()
-- Do nothing: set mocks live in a separate process
return
@@ -488,6 +512,202 @@ if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
end
end
+local function just_fail(_)
+ return false
+end
+say:set('assertion.just_fail.positive', '%s')
+say:set('assertion.just_fail.negative', '%s')
+assert:register('assertion', 'just_fail', just_fail,
+ 'assertion.just_fail.positive',
+ 'assertion.just_fail.negative')
+
+local hook_fnamelen = 30
+local hook_sfnamelen = 30
+local hook_numlen = 5
+local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1
+
+local tracehelp = dedent([[
+ ┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed,
+ │ _t_ail return, _C_ount (should not actually appear),
+ │ _s_aved from previous run for reference.
+ │┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk,
+ │┃ function that did _t_ail call.
+ │┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue,
+ │┃│ space for unknown.
+ │┃│ ┏ Source file name ┌ Function name ┏ Line
+ │┃│ ┃ (trunc to 30 bytes, no .lua) │ (truncated to last 30 bytes) ┃ number
+ CWN SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:LLLLL\n
+]])
+
+local function child_sethook(wr)
+ local trace_level = os.getenv('NVIM_TEST_TRACE_LEVEL')
+ if not trace_level or trace_level == '' then
+ trace_level = 1
+ else
+ trace_level = tonumber(trace_level)
+ end
+ if trace_level <= 0 then
+ return
+ end
+ local trace_only_c = trace_level <= 1
+ local prev_info, prev_reason, prev_lnum
+ local function hook(reason, lnum, use_prev)
+ local info = nil
+ if use_prev then
+ info = prev_info
+ elseif reason ~= 'tail return' then -- tail return
+ info = debug.getinfo(2, 'nSl')
+ end
+
+ if trace_only_c and (not info or info.what ~= 'C') and not use_prev then
+ if info.source:sub(-9) == '_spec.lua' then
+ prev_info = info
+ prev_reason = 'saved'
+ prev_lnum = lnum
+ end
+ return
+ end
+ if trace_only_c and not use_prev and prev_reason then
+ hook(prev_reason, prev_lnum, true)
+ prev_reason = nil
+ end
+
+ local whatchar = ' '
+ local namewhatchar = ' '
+ local funcname = ''
+ local source = ''
+ local msgchar = reason:sub(1, 1)
+
+ if reason == 'count' then
+ msgchar = 'C'
+ end
+
+ if info then
+ funcname = (info.name or ''):sub(1, hook_fnamelen)
+ whatchar = info.what:sub(1, 1)
+ namewhatchar = info.namewhat:sub(1, 1)
+ if namewhatchar == '' then
+ namewhatchar = ' '
+ end
+ source = info.source
+ if source:sub(1, 1) == '@' then
+ if source:sub(-4, -1) == '.lua' then
+ source = source:sub(1, -5)
+ end
+ source = source:sub(-hook_sfnamelen, -1)
+ end
+ lnum = lnum or info.currentline
+ end
+
+ -- assert(-1 <= lnum and lnum <= 99999)
+ local lnum_s
+ if lnum == -1 then
+ lnum_s = 'nknwn'
+ else
+ lnum_s = ('%u'):format(lnum)
+ end
+ local msg = ( -- lua does not support %*
+ ''
+ .. msgchar
+ .. whatchar
+ .. namewhatchar
+ .. ' '
+ .. source .. (' '):rep(hook_sfnamelen - #source)
+ .. ':'
+ .. funcname .. (' '):rep(hook_fnamelen - #funcname)
+ .. ':'
+ .. ('0'):rep(hook_numlen - #lnum_s) .. lnum_s
+ .. '\n'
+ )
+ -- eq(hook_msglen, #msg)
+ sc.write(wr, msg)
+ end
+ debug.sethook(hook, 'crl')
+end
+
+local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
+
+local function itp_child(wr, func)
+ init()
+ collectgarbage('stop')
+ child_sethook(wr)
+ local err, emsg = pcall(func)
+ debug.sethook()
+ collectgarbage('restart')
+ emsg = tostring(emsg)
+ sc.write(wr, trace_end_msg)
+ if not err then
+ if #emsg > 99999 then
+ emsg = emsg:sub(1, 99999)
+ end
+ sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg))
+ deinit()
+ sc.close(wr)
+ sc.exit(1)
+ else
+ sc.write(wr, '+\n')
+ deinit()
+ sc.close(wr)
+ sc.exit(0)
+ end
+end
+
+local function check_child_err(rd)
+ local trace = {}
+ while true do
+ local traceline = sc.read(rd, hook_msglen)
+ if #traceline ~= hook_msglen then
+ if #traceline == 0 then
+ break
+ else
+ trace[#trace + 1] = 'Partial read: <' .. trace .. '>\n'
+ end
+ end
+ if traceline == trace_end_msg then
+ break
+ end
+ trace[#trace + 1] = traceline
+ end
+ local res = sc.read(rd, 2)
+ if #res ~= 2 then
+ local error
+ if #trace == 0 then
+ error = '\nTest crashed, no trace available\n'
+ else
+ error = '\nTest crashed, trace:\n' .. tracehelp
+ for i = 1, #trace do
+ error = error .. trace[i]
+ end
+ end
+ assert.just_fail(error)
+ end
+ if res == '+\n' then
+ return
+ end
+ eq('-\n', res)
+ local len_s = sc.read(rd, 5)
+ local len = tonumber(len_s)
+ neq(0, len)
+ local err = sc.read(rd, len + 1)
+ assert.just_fail(err)
+end
+
+local function itp_parent(rd, pid, allow_failure)
+ local err, emsg = pcall(check_child_err, rd)
+ sc.wait(pid)
+ sc.close(rd)
+ if not err then
+ if allow_failure then
+ io.stderr:write('Errorred out:\n' .. tostring(emsg) .. '\n')
+ os.execute([[
+ sh -c "source ci/common/test.sh
+ check_core_dumps --delete \"]] .. Paths.test_luajit_prg .. [[\""]])
+ else
+ error(emsg)
+ end
+ end
+end
+
local function gen_itp(it)
child_calls_mod = {}
child_calls_mod_once = {}
@@ -495,14 +715,6 @@ local function gen_itp(it)
preprocess_cache_mod = map(function(v) return v end, preprocess_cache_init)
previous_defines_mod = previous_defines_init
cdefs_mod = cdefs_init:copy()
- local function just_fail(_)
- return false
- end
- say:set('assertion.just_fail.positive', '%s')
- say:set('assertion.just_fail.negative', '%s')
- assert:register('assertion', 'just_fail', just_fail,
- 'assertion.just_fail.positive',
- 'assertion.just_fail.negative')
local function itp(name, func, allow_failure)
if allow_failure and os.getenv('NVIM_TEST_RUN_FAILING_TESTS') ~= '1' then
-- FIXME Fix tests with this true
@@ -512,50 +724,13 @@ local function gen_itp(it)
local rd, wr = sc.pipe()
child_pid = sc.fork()
if child_pid == 0 then
- init()
sc.close(rd)
- collectgarbage('stop')
- local err, emsg = pcall(func)
- collectgarbage('restart')
- emsg = tostring(emsg)
- if not err then
- sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg))
- deinit()
- sc.close(wr)
- sc.exit(1)
- else
- sc.write(wr, '+\n')
- deinit()
- sc.close(wr)
- sc.exit(0)
- end
+ itp_child(wr, func)
else
sc.close(wr)
- sc.wait(child_pid)
+ local saved_child_pid = child_pid
child_pid = nil
- local function check()
- local res = sc.read(rd, 2)
- eq(2, #res)
- if res == '+\n' then
- return
- end
- eq('-\n', res)
- local len_s = sc.read(rd, 5)
- local len = tonumber(len_s)
- neq(0, len)
- local err = sc.read(rd, len + 1)
- assert.just_fail(err)
- end
- local err, emsg = pcall(check)
- sc.close(rd)
- if not err then
- if allow_failure then
- io.stderr:write('Errorred out:\n' .. tostring(emsg) .. '\n')
- os.execute([[sh -c "source .ci/common/test.sh ; check_core_dumps --delete \"]] .. Paths.test_luajit_prg .. [[\""]])
- else
- error(emsg)
- end
- end
+ itp_parent(rd, saved_child_pid, allow_failure)
end
end)
end
@@ -587,6 +762,7 @@ local module = {
only_separate = only_separate,
child_call_once = child_call_once,
child_cleanup_once = child_cleanup_once,
+ sc = sc,
}
return function(after_each)
if after_each then
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index 1ffed784ff..823b6d6a85 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -35,17 +35,17 @@ describe('env function', function()
local OK = 0
itp('sets an env variable and returns OK', function()
- local name = 'NEOVIM_UNIT_TEST_SETENV_1N'
- local value = 'NEOVIM_UNIT_TEST_SETENV_1V'
+ local name = 'NVIM_UNIT_TEST_SETENV_1N'
+ local value = 'NVIM_UNIT_TEST_SETENV_1V'
eq(nil, os.getenv(name))
eq(OK, (os_setenv(name, value, 1)))
eq(value, os.getenv(name))
end)
itp("dosn't overwrite an env variable if overwrite is 0", function()
- local name = 'NEOVIM_UNIT_TEST_SETENV_2N'
- local value = 'NEOVIM_UNIT_TEST_SETENV_2V'
- local value_updated = 'NEOVIM_UNIT_TEST_SETENV_2V_UPDATED'
+ local name = 'NVIM_UNIT_TEST_SETENV_2N'
+ local value = 'NVIM_UNIT_TEST_SETENV_2V'
+ local value_updated = 'NVIM_UNIT_TEST_SETENV_2V_UPDATED'
eq(OK, (os_setenv(name, value, 0)))
eq(value, os.getenv(name))
eq(OK, (os_setenv(name, value_updated, 0)))
@@ -69,8 +69,8 @@ describe('env function', function()
describe('os_getenv', function()
itp('reads an env variable', function()
- local name = 'NEOVIM_UNIT_TEST_GETENV_1N'
- local value = 'NEOVIM_UNIT_TEST_GETENV_1V'
+ local name = 'NVIM_UNIT_TEST_GETENV_1N'
+ local value = 'NVIM_UNIT_TEST_GETENV_1V'
eq(NULL, os_getenv(name))
-- need to use os_setenv, because lua dosn't have a setenv function
os_setenv(name, value, 1)
@@ -78,7 +78,7 @@ describe('env function', function()
end)
itp('returns NULL if the env variable is not found', function()
- local name = 'NEOVIM_UNIT_TEST_GETENV_NOTFOUND'
+ local name = 'NVIM_UNIT_TEST_GETENV_NOTFOUND'
return eq(NULL, os_getenv(name))
end)
end)
@@ -97,8 +97,8 @@ describe('env function', function()
describe('os_getenvname_at_index', function()
itp('returns names of environment variables', function()
- local test_name = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N'
- local test_value = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V'
+ local test_name = 'NVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N'
+ local test_value = 'NVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V'
os_setenv(test_name, test_value, 1)
local i = 0
local names = { }
@@ -160,16 +160,16 @@ describe('env function', function()
describe('expand_env_esc', function()
itp('expands environment variables', function()
- local name = 'NEOVIM_UNIT_TEST_EXPAND_ENV_ESCN'
- local value = 'NEOVIM_UNIT_TEST_EXPAND_ENV_ESCV'
+ local name = 'NVIM_UNIT_TEST_EXPAND_ENV_ESCN'
+ local value = 'NVIM_UNIT_TEST_EXPAND_ENV_ESCV'
os_setenv(name, value, 1)
-- TODO(bobtwinkles) This only tests Unix expansions. There should be a
-- test for Windows as well
- local input1 = to_cstr('$NEOVIM_UNIT_TEST_EXPAND_ENV_ESCN/test')
- local input2 = to_cstr('${NEOVIM_UNIT_TEST_EXPAND_ENV_ESCN}/test')
+ local input1 = to_cstr('$NVIM_UNIT_TEST_EXPAND_ENV_ESCN/test')
+ local input2 = to_cstr('${NVIM_UNIT_TEST_EXPAND_ENV_ESCN}/test')
local output_buff1 = cstr(255, '')
local output_buff2 = cstr(255, '')
- local output_expected = 'NEOVIM_UNIT_TEST_EXPAND_ENV_ESCV/test'
+ local output_expected = 'NVIM_UNIT_TEST_EXPAND_ENV_ESCV/test'
cimp.expand_env_esc(input1, output_buff1, 255, false, true, NULL)
cimp.expand_env_esc(input2, output_buff2, 255, false, true, NULL)
eq(output_expected, ffi.string(output_buff1))
diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua
index 7a738ce85c..e3c8e616ce 100644
--- a/test/unit/os/fileio_spec.lua
+++ b/test/unit/os/fileio_spec.lua
@@ -80,6 +80,10 @@ local function file_read(fp, size)
return ret1, ret2
end
+local function file_flush(fp)
+ return m.file_flush(fp)
+end
+
local function file_fsync(fp)
return m.file_fsync(fp)
end
@@ -94,7 +98,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rwx------', attrs.permissions)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can create a rw------- file with kFileCreate', function()
@@ -102,7 +106,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rw-------', attrs.permissions)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can create a rwx------ file with kFileCreateOnly', function()
@@ -110,7 +114,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rwx------', attrs.permissions)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can create a rw------- file with kFileCreateOnly', function()
@@ -118,7 +122,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rw-------', attrs.permissions)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('fails to open an existing file with kFileCreateOnly', function()
@@ -137,35 +141,35 @@ describe('file_open', function()
local err, fp = file_open(file1, m.kFileCreate, 384)
eq(0, err)
eq(true, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can open an existing file read-only with zero', function()
local err, fp = file_open(file1, 0, 384)
eq(0, err)
eq(false, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can open an existing file read-only with kFileReadOnly', function()
local err, fp = file_open(file1, m.kFileReadOnly, 384)
eq(0, err)
eq(false, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can open an existing file read-only with kFileNoSymlink', function()
local err, fp = file_open(file1, m.kFileNoSymlink, 384)
eq(0, err)
eq(false, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can truncate an existing file with kFileTruncate', function()
local err, fp = file_open(file1, m.kFileTruncate, 384)
eq(0, err)
eq(true, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
local attrs = lfs.attributes(file1)
eq(0, attrs.size)
end)
@@ -174,7 +178,7 @@ describe('file_open', function()
local err, fp = file_open(file1, m.kFileWriteOnly, 384)
eq(0, err)
eq(true, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
local attrs = lfs.attributes(file1)
eq(4096, attrs.size)
end)
@@ -191,7 +195,7 @@ describe('file_open', function()
local err, fp = file_open(linkf, m.kFileTruncate, 384)
eq(0, err)
eq(true, fp.wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
local attrs = lfs.attributes(file1)
eq(0, attrs.size)
end)
@@ -217,7 +221,7 @@ describe('file_open_new', function()
local err, fp = file_open_new(file1, 0, 384)
eq(0, err)
eq(false, fp.wr)
- eq(0, m.file_free(fp))
+ eq(0, m.file_free(fp, false))
end)
itp('fails to open an existing file with kFileCreateOnly', function()
@@ -227,7 +231,29 @@ describe('file_open_new', function()
end)
end)
--- file_close is called above, so it is not tested directly
+describe('file_close', function()
+ itp('can flush writes to disk also with true argument', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, err)
+ local wsize = file_write(fp, 'test')
+ eq(4, wsize)
+ eq(0, lfs.attributes(filec).size)
+ eq(0, m.file_close(fp, true))
+ eq(wsize, lfs.attributes(filec).size)
+ end)
+end)
+
+describe('file_free', function()
+ itp('can flush writes to disk also with true argument', function()
+ local err, fp = file_open_new(filec, m.kFileCreateOnly, 384)
+ eq(0, err)
+ local wsize = file_write(fp, 'test')
+ eq(4, wsize)
+ eq(0, lfs.attributes(filec).size)
+ eq(0, m.file_free(fp, true))
+ eq(wsize, lfs.attributes(filec).size)
+ end)
+end)
describe('file_fsync', function()
itp('can flush writes to disk', function()
@@ -240,7 +266,22 @@ describe('file_fsync', function()
eq(0, lfs.attributes(filec).size)
eq(0, file_fsync(fp))
eq(wsize, lfs.attributes(filec).size)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
+ end)
+end)
+
+describe('file_flush', function()
+ itp('can flush writes to disk', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, file_flush(fp))
+ eq(0, err)
+ eq(0, lfs.attributes(filec).size)
+ local wsize = file_write(fp, 'test')
+ eq(4, wsize)
+ eq(0, lfs.attributes(filec).size)
+ eq(0, file_flush(fp))
+ eq(wsize, lfs.attributes(filec).size)
+ eq(0, m.file_close(fp, false))
end)
end)
@@ -262,7 +303,7 @@ describe('file_read', function()
eq({exp_err, exp_s}, {file_read(fp, size)})
shift = shift + size
end
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can read the whole file at once', function()
@@ -271,7 +312,7 @@ describe('file_read', function()
eq(false, fp.wr)
eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
eq({0, ('\0'):rep(#fcontents)}, {file_read(fp, #fcontents)})
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can read more then 1024 bytes after reading a small chunk', function()
@@ -281,7 +322,7 @@ describe('file_read', function()
eq({5, fcontents:sub(1, 5)}, {file_read(fp, 5)})
eq({#fcontents - 5, fcontents:sub(6) .. (('\0'):rep(5))},
{file_read(fp, #fcontents)})
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
itp('can read file by 768-byte-chunks', function()
@@ -301,7 +342,7 @@ describe('file_read', function()
eq({exp_err, exp_s}, {file_read(fp, size)})
shift = shift + size
end
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
end)
@@ -312,7 +353,7 @@ describe('file_write', function()
eq(true, fp.wr)
local wr = file_write(fp, fcontents)
eq(#fcontents, wr)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
eq(wr, lfs.attributes(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@@ -329,7 +370,7 @@ describe('file_write', function()
eq(wr, #s)
shift = shift + size
end
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
eq(#fcontents, lfs.attributes(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@@ -346,7 +387,7 @@ describe('file_write', function()
eq(wr, #s)
shift = shift + size
end
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
eq(#fcontents, lfs.attributes(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@@ -361,6 +402,6 @@ describe('file_skip', function()
local rd, s = file_read(fp, 3)
eq(3, rd)
eq(fcontents:sub(4, 6), s)
- eq(0, m.file_close(fp))
+ eq(0, m.file_close(fp, false))
end)
end)
diff --git a/test/unit/testtest_spec.lua b/test/unit/testtest_spec.lua
new file mode 100644
index 0000000000..d2f3632b6f
--- /dev/null
+++ b/test/unit/testtest_spec.lua
@@ -0,0 +1,19 @@
+local helpers = require('test.unit.helpers')(after_each)
+local assert = require('luassert')
+
+local itp = helpers.gen_itp(it)
+
+local sc = helpers.sc
+
+-- All of the below tests must fail. Check how exactly they fail.
+if os.getenv('NVIM_TEST_RUN_TESTTEST') ~= '1' then
+ return
+end
+describe('test code', function()
+ itp('does not hang when working with lengthy errors', function()
+ assert.just_fail(('x'):rep(65536))
+ end)
+ itp('shows trace after exiting abnormally', function()
+ sc.exit(0)
+ end)
+end)