aboutsummaryrefslogtreecommitdiff
path: root/test/functional/lua
diff options
context:
space:
mode:
authorZyX <kp-pav@yandex.ru>2017-12-03 16:49:30 +0300
committerZyX <kp-pav@yandex.ru>2017-12-03 16:49:30 +0300
commitc49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57 (patch)
treeb7e59c416d1435725c65f8952b6e55c70544d97e /test/functional/lua
parent62108c3b0be46936c83f6d4c98b44ceb5e6f77fd (diff)
parent27a577586eace687c47e7398845178208cae524a (diff)
downloadrneovim-c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57.tar.gz
rneovim-c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57.tar.bz2
rneovim-c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57.zip
Merge branch 'master' into s-dash-stdin
Diffstat (limited to 'test/functional/lua')
-rw-r--r--test/functional/lua/api_spec.lua204
-rw-r--r--test/functional/lua/commands_spec.lua164
-rw-r--r--test/functional/lua/luaeval_spec.lua255
-rw-r--r--test/functional/lua/overrides_spec.lua301
-rw-r--r--test/functional/lua/utility_functions_spec.lua108
5 files changed, 1032 insertions, 0 deletions
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua
new file mode 100644
index 0000000000..b1dc5c07fd
--- /dev/null
+++ b/test/functional/lua/api_spec.lua
@@ -0,0 +1,204 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+
+local exc_exec = helpers.exc_exec
+local funcs = helpers.funcs
+local clear = helpers.clear
+local eval = helpers.eval
+local NIL = helpers.NIL
+local eq = helpers.eq
+
+before_each(clear)
+
+describe('luaeval(vim.api.…)', function()
+ describe('with channel_id and buffer handle', function()
+ describe('nvim_buf_get_lines', function()
+ it('works', function()
+ funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
+ eq({{_TYPE={}, _VAL={'a\nb'}}},
+ funcs.luaeval('vim.api.nvim_buf_get_lines(1, 2, 3, false)'))
+ end)
+ end)
+ describe('nvim_buf_set_lines', function()
+ it('works', function()
+ funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
+ eq(NIL, funcs.luaeval('vim.api.nvim_buf_set_lines(1, 1, 2, false, {"b\\0a"})'))
+ eq({'abc', {_TYPE={}, _VAL={'b\na'}}, {_TYPE={}, _VAL={'a\nb'}}, 'ttt'},
+ funcs.luaeval('vim.api.nvim_buf_get_lines(1, 0, 4, false)'))
+ end)
+ end)
+ end)
+ describe('with errors', function()
+ it('transforms API error from nvim_buf_set_lines into lua error', function()
+ funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
+ eq({false, 'String cannot contain newlines'},
+ funcs.luaeval('{pcall(vim.api.nvim_buf_set_lines, 1, 1, 2, false, {"b\\na"})}'))
+ end)
+
+ it('transforms API error from nvim_win_set_cursor into lua error', function()
+ eq({false, 'Argument "pos" must be a [row, col] array'},
+ funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}'))
+ -- Used to produce a memory leak due to a bug in nvim_win_set_cursor
+ eq({false, 'Invalid window id'},
+ funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, -1, {1, 2, 3})}'))
+ end)
+
+ it('transforms API error from nvim_win_set_cursor + same array as in first test into lua error',
+ function()
+ eq({false, 'Argument "pos" must be a [row, col] array'},
+ funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {"b\\na"})}'))
+ end)
+ end)
+
+ it('correctly evaluates API code which calls luaeval', function()
+ local str = (([===[vim.api.nvim_eval([==[
+ luaeval('vim.api.nvim_eval([=[
+ luaeval("vim.api.nvim_eval([[
+ luaeval(1)
+ ]])")
+ ]=])')
+ ]==])]===]):gsub('\n', ' '))
+ eq(1, funcs.luaeval(str))
+ end)
+
+ it('correctly converts from API objects', function()
+ eq(1, funcs.luaeval('vim.api.nvim_eval("1")'))
+ eq('1', funcs.luaeval([[vim.api.nvim_eval('"1"')]]))
+ eq({}, funcs.luaeval('vim.api.nvim_eval("[]")'))
+ eq({}, funcs.luaeval('vim.api.nvim_eval("{}")'))
+ eq(1, funcs.luaeval('vim.api.nvim_eval("1.0")'))
+ eq(true, funcs.luaeval('vim.api.nvim_eval("v:true")'))
+ eq(false, funcs.luaeval('vim.api.nvim_eval("v:false")'))
+ eq(NIL, funcs.luaeval('vim.api.nvim_eval("v:null")'))
+
+ eq(0, eval([[type(luaeval('vim.api.nvim_eval("1")'))]]))
+ eq(1, eval([[type(luaeval('vim.api.nvim_eval("''1''")'))]]))
+ eq(3, eval([[type(luaeval('vim.api.nvim_eval("[]")'))]]))
+ eq(4, eval([[type(luaeval('vim.api.nvim_eval("{}")'))]]))
+ eq(5, eval([[type(luaeval('vim.api.nvim_eval("1.0")'))]]))
+ eq(6, eval([[type(luaeval('vim.api.nvim_eval("v:true")'))]]))
+ eq(6, eval([[type(luaeval('vim.api.nvim_eval("v:false")'))]]))
+ eq(7, eval([[type(luaeval('vim.api.nvim_eval("v:null")'))]]))
+
+ eq({foo=42}, funcs.luaeval([[vim.api.nvim_eval('{"foo": 42}')]]))
+ eq({42}, funcs.luaeval([[vim.api.nvim_eval('[42]')]]))
+
+ eq({foo={bar=42}, baz=50}, funcs.luaeval([[vim.api.nvim_eval('{"foo": {"bar": 42}, "baz": 50}')]]))
+ eq({{42}, {}}, funcs.luaeval([=[vim.api.nvim_eval('[[42], []]')]=]))
+ end)
+
+ it('correctly converts to API objects', function()
+ eq(1, funcs.luaeval('vim.api.nvim__id(1)'))
+ eq('1', funcs.luaeval('vim.api.nvim__id("1")'))
+ eq({1}, funcs.luaeval('vim.api.nvim__id({1})'))
+ eq({foo=1}, funcs.luaeval('vim.api.nvim__id({foo=1})'))
+ eq(1.5, funcs.luaeval('vim.api.nvim__id(1.5)'))
+ eq(true, funcs.luaeval('vim.api.nvim__id(true)'))
+ eq(false, funcs.luaeval('vim.api.nvim__id(false)'))
+ eq(NIL, funcs.luaeval('vim.api.nvim__id(nil)'))
+
+ eq(0, eval([[type(luaeval('vim.api.nvim__id(1)'))]]))
+ eq(1, eval([[type(luaeval('vim.api.nvim__id("1")'))]]))
+ eq(3, eval([[type(luaeval('vim.api.nvim__id({1})'))]]))
+ eq(4, eval([[type(luaeval('vim.api.nvim__id({foo=1})'))]]))
+ eq(5, eval([[type(luaeval('vim.api.nvim__id(1.5)'))]]))
+ eq(6, eval([[type(luaeval('vim.api.nvim__id(true)'))]]))
+ eq(6, eval([[type(luaeval('vim.api.nvim__id(false)'))]]))
+ eq(7, eval([[type(luaeval('vim.api.nvim__id(nil)'))]]))
+
+ eq({foo=1, bar={42, {{baz=true}, 5}}}, funcs.luaeval('vim.api.nvim__id({foo=1, bar={42, {{baz=true}, 5}}})'))
+ end)
+
+ it('correctly converts container objects with type_idx to API objects', function()
+ eq(5, eval('type(luaeval("vim.api.nvim__id({[vim.type_idx]=vim.types.float, [vim.val_idx]=0})"))'))
+ eq(4, eval([[type(luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.dictionary})'))]]))
+ eq(3, eval([[type(luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array})'))]]))
+
+ eq({}, funcs.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array})'))
+
+ -- Presence of type_idx makes Vim ignore some keys
+ eq({42}, funcs.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'))
+ eq({foo=2}, funcs.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'))
+ eq(10, funcs.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'))
+ eq({}, funcs.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2})'))
+ end)
+
+ it('correctly converts arrays with type_idx to API objects', function()
+ eq(3, eval([[type(luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array})'))]]))
+
+ eq({}, funcs.luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array})'))
+
+ eq({42}, funcs.luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'))
+ eq({{foo=2}}, funcs.luaeval('vim.api.nvim__id_array({{[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'))
+ eq({10}, funcs.luaeval('vim.api.nvim__id_array({{[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'))
+ eq({}, funcs.luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2})'))
+
+ eq({}, funcs.luaeval('vim.api.nvim__id_array({})'))
+ eq(3, eval([[type(luaeval('vim.api.nvim__id_array({})'))]]))
+ end)
+
+ it('correctly converts dictionaries with type_idx to API objects', function()
+ eq(4, eval([[type(luaeval('vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.dictionary})'))]]))
+
+ eq({}, funcs.luaeval('vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.dictionary})'))
+
+ eq({v={42}}, funcs.luaeval('vim.api.nvim__id_dictionary({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'))
+ eq({foo=2}, funcs.luaeval('vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'))
+ eq({v=10}, funcs.luaeval('vim.api.nvim__id_dictionary({v={[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'))
+ eq({v={}}, funcs.luaeval('vim.api.nvim__id_dictionary({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2}})'))
+
+ -- If API requests dictionary, then empty table will be the one. This is not
+ -- the case normally because empty table is an empty arrray.
+ eq({}, funcs.luaeval('vim.api.nvim__id_dictionary({})'))
+ eq(4, eval([[type(luaeval('vim.api.nvim__id_dictionary({})'))]]))
+ end)
+
+ it('errors out correctly when working with API', function()
+ -- Conversion errors
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua type',
+ exc_exec([[call luaeval("vim.api.nvim__id(vim.api.nvim__id)")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua table',
+ exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua type',
+ exc_exec([[call luaeval("vim.api.nvim__id({42, vim.api.nvim__id})")]]))
+ -- Errors in number of arguments
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 1 argument',
+ exc_exec([[call luaeval("vim.api.nvim__id()")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 1 argument',
+ exc_exec([[call luaeval("vim.api.nvim__id(1, 2)")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 2 arguments',
+ exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]]))
+ -- Error in argument types
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua string',
+ exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]]))
+
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua number',
+ exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Number is not integral',
+ exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]]))
+
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]]))
+
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]]))
+
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]]))
+ -- TODO: check for errors with Tabpage argument
+ -- TODO: check for errors with Window argument
+ -- TODO: check for errors with Buffer argument
+ end)
+
+ it('accepts any value as API Boolean', function()
+ eq('', funcs.luaeval('vim.api.nvim_replace_termcodes("", vim, false, nil)'))
+ eq('', funcs.luaeval('vim.api.nvim_replace_termcodes("", 0, 1.5, "test")'))
+ eq('', funcs.luaeval('vim.api.nvim_replace_termcodes("", true, {}, {[vim.type_idx]=vim.types.array})'))
+ end)
+end)
diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua
new file mode 100644
index 0000000000..017ee55729
--- /dev/null
+++ b/test/functional/lua/commands_spec.lua
@@ -0,0 +1,164 @@
+-- Test suite for checking :lua* commands
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local NIL = helpers.NIL
+local clear = helpers.clear
+local meths = helpers.meths
+local funcs = helpers.funcs
+local source = helpers.source
+local dedent = helpers.dedent
+local exc_exec = helpers.exc_exec
+local write_file = helpers.write_file
+local redir_exec = helpers.redir_exec
+local curbufmeths = helpers.curbufmeths
+
+before_each(clear)
+
+describe(':lua command', function()
+ it('works', function()
+ eq('', redir_exec(
+ 'lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TEST"})'))
+ eq({'', 'TEST'}, curbufmeths.get_lines(0, 100, false))
+ source(dedent([[
+ lua << EOF
+ vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TSET"})
+ EOF]]))
+ eq({'', 'TSET'}, curbufmeths.get_lines(0, 100, false))
+ source(dedent([[
+ lua << EOF
+ vim.api.nvim_buf_set_lines(1, 1, 2, false, {"SETT"})]]))
+ eq({'', 'SETT'}, curbufmeths.get_lines(0, 100, false))
+ source(dedent([[
+ lua << EOF
+ vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
+ vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
+ vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
+ EOF]]))
+ eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false))
+ end)
+ it('throws catchable errors', function()
+ eq([[Vim(lua):E5104: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unexpected symbol near ')']],
+ exc_exec('lua ()'))
+ eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: TEST]],
+ exc_exec('lua error("TEST")'))
+ eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: Invalid buffer id]],
+ exc_exec('lua vim.api.nvim_buf_set_lines(-10, 1, 1, false, {"TEST"})'))
+ eq({''}, curbufmeths.get_lines(0, 100, false))
+ end)
+ it('works with NULL errors', function()
+ eq([=[Vim(lua):E5105: Error while calling lua chunk: [NULL]]=],
+ exc_exec('lua error(nil)'))
+ end)
+ it('accepts embedded NLs without heredoc', function()
+ -- Such code is usually used for `:execute 'lua' {generated_string}`:
+ -- heredocs do not work in this case.
+ meths.command([[
+ lua
+ vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
+ vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
+ vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
+ ]])
+ eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false))
+ end)
+ it('preserves global and not preserves local variables', function()
+ eq('', redir_exec('lua gvar = 42'))
+ eq('', redir_exec('lua local lvar = 100500'))
+ eq(NIL, funcs.luaeval('lvar'))
+ eq(42, funcs.luaeval('gvar'))
+ end)
+ it('works with long strings', function()
+ local s = ('x'):rep(100500)
+
+ eq('\nE5104: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s)))
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+
+ eq('', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s)))
+ eq({'', s}, curbufmeths.get_lines(0, -1, false))
+ end)
+end)
+
+describe(':luado command', function()
+ it('works', function()
+ curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"})
+ eq('', redir_exec('luado lines = (lines or {}) lines[#lines + 1] = {linenr, line}'))
+ eq({'ABC', 'def', 'gHi'}, curbufmeths.get_lines(0, -1, false))
+ eq({{1, 'ABC'}, {2, 'def'}, {3, 'gHi'}}, funcs.luaeval('lines'))
+
+ -- Automatic transformation of numbers
+ eq('', redir_exec('luado return linenr'))
+ eq({'1', '2', '3'}, curbufmeths.get_lines(0, -1, false))
+
+ eq('', redir_exec('luado return ("<%02x>"):format(line:byte())'))
+ eq({'<31>', '<32>', '<33>'}, curbufmeths.get_lines(0, -1, false))
+ end)
+ it('stops processing lines when suddenly out of lines', function()
+ curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"})
+ eq('', redir_exec('2,$luado runs = ((runs or 0) + 1) vim.api.nvim_command("%d")'))
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+ eq(1, funcs.luaeval('runs'))
+ end)
+ it('works correctly when changing lines out of range', function()
+ curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"})
+ eq('\nE322: line number out of range: 1 past the end\nE320: Cannot find line 2',
+ redir_exec('2,$luado vim.api.nvim_command("%d") return linenr'))
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+ end)
+ it('fails on errors', function()
+ eq([[Vim(luado):E5109: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unexpected symbol near ')']],
+ exc_exec('luado ()'))
+ eq([[Vim(luado):E5111: Error while calling lua function: [string "<VimL compiled string>"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]],
+ exc_exec('luado return liness + 1'))
+ end)
+ it('works with NULL errors', function()
+ eq([=[Vim(luado):E5111: Error while calling lua function: [NULL]]=],
+ exc_exec('luado error(nil)'))
+ end)
+ it('fails in sandbox when needed', function()
+ curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"})
+ eq('\nE48: Not allowed in sandbox: sandbox luado runs = (runs or 0) + 1',
+ redir_exec('sandbox luado runs = (runs or 0) + 1'))
+ eq(NIL, funcs.luaeval('runs'))
+ end)
+ it('works with long strings', function()
+ local s = ('x'):rep(100500)
+
+ eq('\nE5109: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s)))
+ eq({''}, curbufmeths.get_lines(0, -1, false))
+
+ eq('', redir_exec(('luado return "%s"'):format(s)))
+ eq({s}, curbufmeths.get_lines(0, -1, false))
+ end)
+end)
+
+describe(':luafile', function()
+ local fname = 'Xtest-functional-lua-commands-luafile'
+
+ after_each(function()
+ os.remove(fname)
+ end)
+
+ it('works', function()
+ write_file(fname, [[
+ vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
+ vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
+ vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
+ ]])
+ eq('', redir_exec('luafile ' .. fname))
+ eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false))
+ end)
+
+ it('correctly errors out', function()
+ write_file(fname, '()')
+ eq(("Vim(luafile):E5112: Error while creating lua chunk: %s:1: unexpected symbol near ')'"):format(fname),
+ exc_exec('luafile ' .. fname))
+ write_file(fname, 'vimm.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})')
+ eq(("Vim(luafile):E5113: Error while calling lua chunk: %s:1: attempt to index global 'vimm' (a nil value)"):format(fname),
+ exc_exec('luafile ' .. fname))
+ end)
+ it('works with NULL errors', function()
+ write_file(fname, 'error(nil)')
+ eq([=[Vim(luafile):E5113: Error while calling lua chunk: [NULL]]=],
+ exc_exec('luafile ' .. fname))
+ end)
+end)
diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua
new file mode 100644
index 0000000000..6da0001cea
--- /dev/null
+++ b/test/functional/lua/luaeval_spec.lua
@@ -0,0 +1,255 @@
+-- Test suite for testing luaeval() function
+local helpers = require('test.functional.helpers')(after_each)
+
+local redir_exec = helpers.redir_exec
+local exc_exec = helpers.exc_exec
+local command = helpers.command
+local meths = helpers.meths
+local funcs = helpers.funcs
+local clear = helpers.clear
+local eval = helpers.eval
+local NIL = helpers.NIL
+local eq = helpers.eq
+
+before_each(clear)
+
+local function startswith(expected, actual)
+ eq(expected, actual:sub(1, #expected))
+end
+
+describe('luaeval()', function()
+ local nested_by_level = {}
+ local nested = {}
+ local nested_s = '{}'
+ for i=1,100 do
+ if i % 2 == 0 then
+ nested = {nested}
+ nested_s = '{' .. nested_s .. '}'
+ else
+ nested = {nested=nested}
+ nested_s = '{nested=' .. nested_s .. '}'
+ end
+ nested_by_level[i] = {o=nested, s=nested_s}
+ end
+
+ describe('second argument', function()
+ it('is successfully received', function()
+ local t = {t=true, f=false, --[[n=NIL,]] d={l={'string', 42, 0.42}}}
+ eq(t, funcs.luaeval("_A", t))
+ -- Not tested: nil, funcrefs, returned object identity: behaviour will
+ -- most likely change.
+ end)
+ end)
+ describe('lua values', function()
+ it('are successfully transformed', function()
+ eq({n=1, f=1.5, s='string', l={4, 2}},
+ funcs.luaeval('{n=1, f=1.5, s="string", l={4, 2}}'))
+ -- Not tested: nil inside containers: behaviour will most likely change.
+ eq(NIL, funcs.luaeval('nil'))
+ eq({['']=1}, funcs.luaeval('{[""]=1}'))
+ end)
+ end)
+ describe('recursive lua values', function()
+ it('are successfully transformed', function()
+ funcs.luaeval('rawset(_G, "d", {})')
+ funcs.luaeval('rawset(d, "d", d)')
+ eq('\n{\'d\': {...@0}}', funcs.execute('echo luaeval("d")'))
+
+ funcs.luaeval('rawset(_G, "l", {})')
+ funcs.luaeval('table.insert(l, l)')
+ eq('\n[[...@0]]', funcs.execute('echo luaeval("l")'))
+ end)
+ end)
+ describe('strings', function()
+ it('are successfully converted to special dictionaries', function()
+ command([[let s = luaeval('"\0"')]])
+ eq({_TYPE={}, _VAL={'\n'}}, meths.get_var('s'))
+ eq(1, funcs.eval('s._TYPE is v:msgpack_types.binary'))
+ end)
+ it('are successfully converted to special dictionaries in table keys',
+ function()
+ command([[let d = luaeval('{["\0"]=1}')]])
+ eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n'}}, 1}}}, meths.get_var('d'))
+ eq(1, funcs.eval('d._TYPE is v:msgpack_types.map'))
+ eq(1, funcs.eval('d._VAL[0][0]._TYPE is v:msgpack_types.string'))
+ end)
+ it('are successfully converted to special dictionaries from a list',
+ function()
+ command([[let l = luaeval('{"abc", "a\0b", "c\0d", "def"}')]])
+ eq({'abc', {_TYPE={}, _VAL={'a\nb'}}, {_TYPE={}, _VAL={'c\nd'}}, 'def'},
+ meths.get_var('l'))
+ eq(1, funcs.eval('l[1]._TYPE is v:msgpack_types.binary'))
+ eq(1, funcs.eval('l[2]._TYPE is v:msgpack_types.binary'))
+ end)
+ end)
+
+ -- Not checked: funcrefs converted to NIL. To be altered to something more
+ -- meaningful later.
+
+ it('correctly evaluates scalars', function()
+ eq(1, funcs.luaeval('1'))
+ eq(0, eval('type(luaeval("1"))'))
+
+ eq(1.5, funcs.luaeval('1.5'))
+ eq(5, eval('type(luaeval("1.5"))'))
+
+ eq("test", funcs.luaeval('"test"'))
+ eq(1, eval('type(luaeval("\'test\'"))'))
+
+ eq('', funcs.luaeval('""'))
+ eq({_TYPE={}, _VAL={'\n'}}, funcs.luaeval([['\0']]))
+ eq({_TYPE={}, _VAL={'\n', '\n'}}, funcs.luaeval([['\0\n\0']]))
+ eq(1, eval([[luaeval('"\0\n\0"')._TYPE is v:msgpack_types.binary]]))
+
+ eq(true, funcs.luaeval('true'))
+ eq(false, funcs.luaeval('false'))
+ eq(NIL, funcs.luaeval('nil'))
+ end)
+
+ it('correctly evaluates containers', function()
+ eq({}, funcs.luaeval('{}'))
+ eq(3, eval('type(luaeval("{}"))'))
+
+ eq({test=1, foo=2}, funcs.luaeval('{test=1, foo=2}'))
+ eq(4, eval('type(luaeval("{test=1, foo=2}"))'))
+
+ eq({4, 2}, funcs.luaeval('{4, 2}'))
+ eq(3, eval('type(luaeval("{4, 2}"))'))
+
+ local level = 30
+ eq(nested_by_level[level].o, funcs.luaeval(nested_by_level[level].s))
+
+ eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}},
+ funcs.luaeval([[{['\0\n\0']='\0\n\0\0'}]]))
+ eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._TYPE is v:msgpack_types.map]]))
+ eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][0]._TYPE is v:msgpack_types.string]]))
+ eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][1]._TYPE is v:msgpack_types.binary]]))
+ eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}}}},
+ funcs.luaeval([[{nested={{['\0\n\0']='\0\n\0\0'}}}]]))
+ end)
+
+ it('correctly passes scalars as argument', function()
+ eq(1, funcs.luaeval('_A', 1))
+ eq(1.5, funcs.luaeval('_A', 1.5))
+ eq('', funcs.luaeval('_A', ''))
+ eq('test', funcs.luaeval('_A', 'test'))
+ eq(NIL, funcs.luaeval('_A', NIL))
+ eq(true, funcs.luaeval('_A', true))
+ eq(false, funcs.luaeval('_A', false))
+ end)
+
+ it('correctly passes containers as argument', function()
+ eq({}, funcs.luaeval('_A', {}))
+ eq({test=1}, funcs.luaeval('_A', {test=1}))
+ eq({4, 2}, funcs.luaeval('_A', {4, 2}))
+ local level = 28
+ eq(nested_by_level[level].o, funcs.luaeval('_A', nested_by_level[level].o))
+ end)
+
+ local function sp(typ, val)
+ return ('{"_TYPE": v:msgpack_types.%s, "_VAL": %s}'):format(typ, val)
+ end
+ local function mapsp(...)
+ local val = ''
+ for i=1,(select('#', ...)/2) do
+ val = ('%s[%s,%s],'):format(val, select(i * 2 - 1, ...),
+ select(i * 2, ...))
+ end
+ return sp('map', '[' .. val .. ']')
+ end
+ local function luaevalarg(argexpr, expr)
+ return eval(([=[
+ [
+ extend(g:, {'_ret': luaeval(%s, %s)})._ret,
+ type(g:_ret)==type({})&&has_key(g:_ret, '_TYPE')
+ ? [
+ get(keys(filter(copy(v:msgpack_types), 'v:val is g:_ret._TYPE')), 0,
+ g:_ret._TYPE),
+ get(g:_ret, '_VAL', g:_ret)
+ ]
+ : [0, g:_ret]][1]
+ ]=]):format(expr or '"_A"', argexpr):gsub('\n', ''))
+ end
+
+ it('correctly passes special dictionaries', function()
+ eq({'binary', {'\n', '\n'}}, luaevalarg(sp('binary', '["\\n", "\\n"]')))
+ eq({'binary', {'\n', '\n'}}, luaevalarg(sp('string', '["\\n", "\\n"]')))
+ eq({0, true}, luaevalarg(sp('boolean', 1)))
+ eq({0, false}, luaevalarg(sp('boolean', 0)))
+ eq({0, NIL}, luaevalarg(sp('nil', 0)))
+ eq({0, {[""]=""}}, luaevalarg(mapsp(sp('binary', '[""]'), '""')))
+ eq({0, {[""]=""}}, luaevalarg(mapsp(sp('string', '[""]'), '""')))
+ end)
+
+ it('issues an error in some cases', function()
+ eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys",
+ exc_exec('call luaeval("{1, foo=2}")'))
+ eq("Vim(call):E5101: Cannot convert given lua type",
+ exc_exec('call luaeval("vim.api.nvim_buf_get_lines")'))
+ startswith("Vim(call):E5107: Error while creating lua chunk for luaeval(): ",
+ exc_exec('call luaeval("1, 2, 3")'))
+ startswith("Vim(call):E5108: Error while calling lua chunk for luaeval(): ",
+ exc_exec('call luaeval("(nil)()")'))
+ eq("Vim(call):E5101: Cannot convert given lua type",
+ exc_exec('call luaeval("{42, vim.api}")'))
+ eq("Vim(call):E5101: Cannot convert given lua type",
+ exc_exec('call luaeval("{foo=42, baz=vim.api}")'))
+
+ -- The following should not crash: conversion error happens inside
+ eq("Vim(call):E5101: Cannot convert given lua type",
+ exc_exec('call luaeval("vim.api")'))
+ -- The following should not show internal error
+ eq("\nE5101: Cannot convert given lua type\n0",
+ redir_exec('echo luaeval("vim.api")'))
+ end)
+
+ it('correctly converts containers with type_idx', function()
+ eq(5, eval('type(luaeval("{[vim.type_idx]=vim.types.float, [vim.val_idx]=0}"))'))
+ eq(4, eval([[type(luaeval('{[vim.type_idx]=vim.types.dictionary}'))]]))
+ eq(3, eval([[type(luaeval('{[vim.type_idx]=vim.types.array}'))]]))
+
+ eq({}, funcs.luaeval('{[vim.type_idx]=vim.types.array}'))
+
+ -- Presence of type_idx makes Vim ignore some keys
+ eq({42}, funcs.luaeval('{[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}'))
+ eq({foo=2}, funcs.luaeval('{[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}'))
+ eq(10, funcs.luaeval('{[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}'))
+
+ -- The following should not crash
+ eq({}, funcs.luaeval('{[vim.type_idx]=vim.types.dictionary}'))
+ end)
+
+ it('correctly converts self-containing containers', function()
+ meths.set_var('l', {})
+ eval('add(l, l)')
+ eq(true, eval('luaeval("_A == _A[1]", l)'))
+ eq(true, eval('luaeval("_A[1] == _A[1][1]", [l])'))
+ eq(true, eval('luaeval("_A.d == _A.d[1]", {"d": l})'))
+ eq(true, eval('luaeval("_A ~= _A[1]", [l])'))
+
+ meths.set_var('d', {foo=42})
+ eval('extend(d, {"d": d})')
+ eq(true, eval('luaeval("_A == _A.d", d)'))
+ eq(true, eval('luaeval("_A[1] == _A[1].d", [d])'))
+ eq(true, eval('luaeval("_A.d == _A.d.d", {"d": d})'))
+ eq(true, eval('luaeval("_A ~= _A.d", {"d": d})'))
+ end)
+
+ it('errors out correctly when doing incorrect things in lua', function()
+ -- Conversion errors
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)',
+ exc_exec([[call luaeval("vim.xxx_nonexistent_key_xxx()")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: ERROR',
+ exc_exec([[call luaeval("error('ERROR')")]]))
+ eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [NULL]',
+ exc_exec([[call luaeval("error(nil)")]]))
+ end)
+
+ it('does not leak memory when called with too long line',
+ function()
+ local s = ('x'):rep(65536)
+ eq('Vim(call):E5107: Error while creating lua chunk for luaeval(): [string "<VimL compiled string>"]:1: unexpected symbol near \')\'',
+ exc_exec([[call luaeval("(']] .. s ..[[' + )")]]))
+ eq(s, funcs.luaeval('"' .. s .. '"'))
+ end)
+end)
diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua
new file mode 100644
index 0000000000..8ca5fe57ba
--- /dev/null
+++ b/test/functional/lua/overrides_spec.lua
@@ -0,0 +1,301 @@
+-- Test for Vim overrides of lua built-ins
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local eq = helpers.eq
+local neq = helpers.neq
+local NIL = helpers.NIL
+local feed = helpers.feed
+local clear = helpers.clear
+local funcs = helpers.funcs
+local meths = helpers.meths
+local iswin = helpers.iswin
+local command = helpers.command
+local write_file = helpers.write_file
+local redir_exec = helpers.redir_exec
+local alter_slashes = helpers.alter_slashes
+
+local screen
+
+local fname = 'Xtest-functional-lua-overrides-luafile'
+
+before_each(clear)
+
+after_each(function()
+ os.remove(fname)
+end)
+
+describe('print', function()
+ it('returns nothing', function()
+ eq(NIL, funcs.luaeval('print("abc")'))
+ eq(0, funcs.luaeval('select("#", print("abc"))'))
+ end)
+ it('allows catching printed text with :execute', function()
+ eq('\nabc', funcs.execute('lua print("abc")'))
+ eq('\nabc', funcs.execute('luado print("abc")'))
+ eq('\nabc', funcs.execute('call luaeval("print(\'abc\')")'))
+ write_file(fname, 'print("abc")')
+ eq('\nabc', funcs.execute('luafile ' .. fname))
+
+ eq('\nabc', redir_exec('lua print("abc")'))
+ eq('\nabc', redir_exec('luado print("abc")'))
+ eq('\nabc', redir_exec('call luaeval("print(\'abc\')")'))
+ write_file(fname, 'print("abc")')
+ eq('\nabc', redir_exec('luafile ' .. fname))
+ end)
+ it('handles errors in __tostring', function()
+ write_file(fname, [[
+ local meta_nilerr = { __tostring = function() error(nil) end }
+ local meta_abcerr = { __tostring = function() error("abc") end }
+ local meta_tblout = { __tostring = function() return {"TEST"} end }
+ v_nilerr = setmetatable({}, meta_nilerr)
+ v_abcerr = setmetatable({}, meta_abcerr)
+ v_tblout = setmetatable({}, meta_tblout)
+ ]])
+ eq('', redir_exec('luafile ' .. fname))
+ eq('\nE5114: Error while converting print argument #2: [NULL]',
+ redir_exec('lua print("foo", v_nilerr, "bar")'))
+ eq('\nE5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
+ redir_exec('lua print("foo", v_abcerr, "bar")'))
+ eq('\nE5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
+ redir_exec('lua print("foo", v_tblout, "bar")'))
+ end)
+ it('prints strings with NULs and NLs correctly', function()
+ meths.set_option('more', true)
+ eq('\nabc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT\n',
+ redir_exec([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\n")]]))
+ eq('\nabc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT^@',
+ redir_exec([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\0")]]))
+ eq('\nT^@', redir_exec([[lua print("T\0")]]))
+ eq('\nT\n', redir_exec([[lua print("T\n")]]))
+ end)
+ it('prints empty strings correctly', function()
+ -- Regression: first test used to crash
+ eq('', redir_exec('lua print("")'))
+ eq('\n def', redir_exec('lua print("", "def")'))
+ eq('\nabc ', redir_exec('lua print("abc", "")'))
+ eq('\nabc def', redir_exec('lua print("abc", "", "def")'))
+ end)
+end)
+
+describe('debug.debug', function()
+ before_each(function()
+ screen = Screen.new()
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=255},
+ E = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ cr = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ end)
+ it('works', function()
+ command([[lua
+ function Test(a)
+ print(a)
+ debug.debug()
+ print(a * 100)
+ end
+ ]])
+ feed(':lua Test()\n')
+ screen:expect([[
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ nil |
+ lua_debug> ^ |
+ ]])
+ feed('print("TEST")\n')
+ screen:expect([[
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ nil |
+ lua_debug> print("TEST") |
+ TEST |
+ lua_debug> ^ |
+ ]])
+ feed('<C-c>')
+ screen:expect([[
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ nil |
+ lua_debug> print("TEST") |
+ TEST |
+ |
+ {E:E5105: Error while calling lua chunk: [string "<VimL }|
+ {E:compiled string>"]:5: attempt to perform arithmetic o}|
+ {E:n local 'a' (a nil value)} |
+ Interrupt: {cr:Press ENTER or type command to continue}^ |
+ ]])
+ feed('<C-l>:lua Test()\n')
+ screen:expect([[
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ nil |
+ lua_debug> ^ |
+ ]])
+ feed('\n')
+ screen:expect([[
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ nil |
+ lua_debug> |
+ {E:E5105: Error while calling lua chunk: [string "<VimL }|
+ {E:compiled string>"]:5: attempt to perform arithmetic o}|
+ {E:n local 'a' (a nil value)} |
+ {cr:Press ENTER or type command to continue}^ |
+ ]])
+ end)
+end)
+
+describe('package.path/package.cpath', function()
+ local sl = alter_slashes
+
+ local function get_new_paths(sufs, runtimepaths)
+ runtimepaths = runtimepaths or meths.list_runtime_paths()
+ local new_paths = {}
+ local sep = package.config:sub(1, 1)
+ for _, v in ipairs(runtimepaths) do
+ for _, suf in ipairs(sufs) do
+ new_paths[#new_paths + 1] = v .. sep .. 'lua' .. suf
+ end
+ end
+ return new_paths
+ end
+ local function execute_lua(cmd, ...)
+ return meths.execute_lua(cmd, {...})
+ end
+ local function eval_lua(expr, ...)
+ return meths.execute_lua('return ' .. expr, {...})
+ end
+ local function set_path(which, value)
+ return execute_lua('package[select(1, ...)] = select(2, ...)', which, value)
+ end
+
+ it('contains directories from &runtimepath on first invocation', function()
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
+
+ local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'})
+ local new_cpaths_str = table.concat(new_cpaths, ';')
+ eq(new_cpaths_str, eval_lua('package.cpath'):sub(1, #new_cpaths_str))
+ end)
+ it('puts directories from &runtimepath always at the start', function()
+ meths.set_option('runtimepath', 'a,b')
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b'})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
+
+ set_path('path', sl'foo/?.lua;foo/?/init.lua;' .. new_paths_str)
+
+ neq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
+
+ command('set runtimepath+=c')
+ new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b', 'c'})
+ new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
+ end)
+ it('understands uncommon suffixes', function()
+ set_path('cpath', './?/foo/bar/baz/x.nlua')
+ meths.set_option('runtimepath', 'a')
+ local new_paths = get_new_paths({'/?/foo/bar/baz/x.nlua'}, {'a'})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
+
+ set_path('cpath', './yyy?zzz/x')
+ meths.set_option('runtimepath', 'b')
+ new_paths = get_new_paths({'/yyy?zzz/x'}, {'b'})
+ new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
+
+ set_path('cpath', './yyy?zzz/123?ghi/x')
+ meths.set_option('runtimepath', 'b')
+ new_paths = get_new_paths({'/yyy?zzz/123?ghi/x'}, {'b'})
+ new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
+ end)
+ it('preserves empty items', function()
+ local many_empty_path = ';;;;;;'
+ local many_empty_cpath = ';;;;;;./?.luaso'
+ set_path('path', many_empty_path)
+ set_path('cpath', many_empty_cpath)
+ meths.set_option('runtimepath', 'a')
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str .. ';' .. many_empty_path, eval_lua('package.path'))
+ local new_cpaths = get_new_paths({'/?.luaso'}, {'a'})
+ local new_cpaths_str = table.concat(new_cpaths, ';')
+ eq(new_cpaths_str .. ';' .. many_empty_cpath, eval_lua('package.cpath'))
+ end)
+ it('preserves empty value', function()
+ set_path('path', '')
+ meths.set_option('runtimepath', 'a')
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str .. ';', eval_lua('package.path'))
+ end)
+ it('purges out all additions if runtimepath is set to empty', function()
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'})
+ local new_paths_str = table.concat(new_paths, ';')
+ local path = eval_lua('package.path')
+ eq(new_paths_str, path:sub(1, #new_paths_str))
+
+ local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'})
+ local new_cpaths_str = table.concat(new_cpaths, ';')
+ local cpath = eval_lua('package.cpath')
+ eq(new_cpaths_str, cpath:sub(1, #new_cpaths_str))
+
+ meths.set_option('runtimepath', '')
+ eq(path:sub(#new_paths_str + 2, -1), eval_lua('package.path'))
+ eq(cpath:sub(#new_cpaths_str + 2, -1), eval_lua('package.cpath'))
+ end)
+ it('works with paths with escaped commas', function()
+ meths.set_option('runtimepath', '\\,')
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
+ end)
+ it('ignores paths with semicolons', function()
+ meths.set_option('runtimepath', 'foo;bar,\\,')
+ local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','})
+ local new_paths_str = table.concat(new_paths, ';')
+ eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
+ end)
+end)
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
new file mode 100644
index 0000000000..d5756e134d
--- /dev/null
+++ b/test/functional/lua/utility_functions_spec.lua
@@ -0,0 +1,108 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+
+local funcs = helpers.funcs
+local clear = helpers.clear
+local eq = helpers.eq
+
+before_each(clear)
+
+describe('vim.stricmp', function()
+ -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
+ -- length 2 (in bytes).
+ -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
+ -- length 3 (in bytes).
+ --
+ -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
+ -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
+ -- only on ASCII characters.
+ it('works', function()
+ eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("", "")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
+ end)
+end)