diff options
Diffstat (limited to 'test')
34 files changed, 766 insertions, 359 deletions
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index a4bd574a56..a6e9f9a42b 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -575,4 +575,25 @@ describe('API: get highlight', function() meths.set_hl(0, 'Foo', hl) eq(hl, meths.get_hl(0, { name = 'Foo', link = true })) end) + + it("doesn't contain unset groups", function() + local id = meths.get_hl_id_by_name "@foobar.hubbabubba" + ok(id > 0) + + local data = meths.get_hl(0, {}) + eq(nil, data["@foobar.hubbabubba"]) + eq(nil, data["@foobar"]) + + command 'hi @foobar.hubbabubba gui=bold' + data = meths.get_hl(0, {}) + eq({bold = true}, data["@foobar.hubbabubba"]) + eq(nil, data["@foobar"]) + + -- @foobar.hubbabubba was explicitly cleared and thus shows up + -- but @foobar was never touched, and thus doesn't + command 'hi clear @foobar.hubbabubba' + data = meths.get_hl(0, {}) + eq({}, data["@foobar.hubbabubba"]) + eq(nil, data["@foobar"]) + end) end) diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua index 29d589a296..29a6171269 100644 --- a/test/functional/autocmd/autocmd_oldtest_spec.lua +++ b/test/functional/autocmd/autocmd_oldtest_spec.lua @@ -50,6 +50,7 @@ describe('oldtests', function() it('should fire on unload buf', function() funcs.writefile({'Test file Xxx1'}, 'Xxx1') funcs.writefile({'Test file Xxx2'}, 'Xxx2') + local fname = 'Xtest_functional_autocmd_unload' local content = [[ func UnloadAllBufs() @@ -69,15 +70,15 @@ describe('oldtests', function() q ]] - funcs.writefile(funcs.split(content, "\n"), 'Xtest') + funcs.writefile(funcs.split(content, "\n"), fname) funcs.delete('Xout') - funcs.system(meths.get_vvar('progpath') .. ' -u NORC -i NONE -N -S Xtest') + funcs.system(string.format('%s -u NORC -i NONE -N -S %s', meths.get_vvar('progpath'), fname)) eq(1, funcs.filereadable('Xout')) funcs.delete('Xxx1') funcs.delete('Xxx2') - funcs.delete('Xtest') + funcs.delete(fname) funcs.delete('Xout') end) end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index cc94623df3..b8a3c1dcd5 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -28,6 +28,9 @@ local meths = helpers.meths local alter_slashes = helpers.alter_slashes local is_os = helpers.is_os local dedent = helpers.dedent +local tbl_map = helpers.tbl_map +local tbl_filter = helpers.tbl_filter +local endswith = helpers.endswith local testfile = 'Xtest_startuptime' after_each(function() @@ -202,12 +205,12 @@ describe('startup', function() end) it('disables swapfile/shada/config/plugins', function() - assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scriptnames=1', + assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scripts=1', nil, nil, '-', - [[print(('updatecount=%d shadafile=%s loadplugins=%s scriptnames=%d'):format( - vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.split(vim.fn.execute('scriptnames'),'\n'))))]]) + [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]]) end) end) @@ -398,13 +401,13 @@ describe('startup', function() for _,arg in ipairs({'-es', '-Es'}) do local out = funcs.system({nvim_prog, arg, '+set swapfile? updatecount? shadafile?', - "+put =execute('scriptnames')", '+%print'}) + "+put =map(getscriptinfo(), {-> v:val.name})", '+%print'}) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. eq(" swapfile updatecount=0 shadafile=\n", line1) -- Standard plugins were loaded, but not user config. - eq('health.vim', string.match(out, 'health.vim')) - eq(nil, string.match(out, 'init.vim')) + ok(string.find(out, 'man.lua') ~= nil) + ok(string.find(out, 'init.vim') == nil) end end) @@ -865,6 +868,10 @@ describe('runtime:', function() local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep) local profiler_file = 'test_startuptime.log' + finally(function() + os.remove(profiler_file) + rmdir(plugin_path) + end) mkdir_p(plugin_folder_path) write_file(plugin_file_path, [[vim.g.lua_plugin = 2]]) @@ -872,18 +879,15 @@ describe('runtime:', function() clear{ args_rm={'-u'}, args={'--startuptime', profiler_file}, env=xenv } eq(2, eval('g:lua_plugin')) - -- Check if plugin_file_path is listed in :scriptname - local scripts = exec_capture('scriptnames') - assert(scripts:find(plugin_file_path)) + -- Check if plugin_file_path is listed in getscriptinfo() + local scripts = tbl_map(function(s) return s.name end, funcs.getscriptinfo()) + ok(#tbl_filter(function(s) return endswith(s, plugin_file_path) end, scripts) > 0) -- Check if plugin_file_path is listed in startup profile local profile_reader = io.open(profiler_file, 'r') local profile_log = profile_reader:read('*a') profile_reader:close() - assert(profile_log:find(plugin_file_path)) - - os.remove(profiler_file) - rmdir(plugin_path) + ok(profile_log:find(plugin_file_path) ~= nil) end) it('loads plugin/*.lua from site packages', function() diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 365f8527a0..a6e4b0c5eb 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -417,4 +417,48 @@ describe('named marks view', function() | ]]) end) + + it('fallback to standard behavior when mark is loaded from shada', function() + local screen = Screen.new(10, 6) + screen:attach() + command('edit ' .. file1) + feed('G') + feed('mA') + screen:expect([[ + 26 line | + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + command('set shadafile=Xtestfile-functional-editor-marks-shada') + finally(function() + command('set shadafile=NONE') + os.remove('Xtestfile-functional-editor-marks-shada') + end) + command('wshada!') + command('bwipe!') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + command('rshada!') + command('edit ' .. file1) + feed('`"') + screen:expect([[ + 26 line | + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + feed('`A') + screen:expect_unchanged() + end) end) diff --git a/test/functional/legacy/061_undo_tree_spec.lua b/test/functional/legacy/061_undo_tree_spec.lua index 1a8ef067d0..b5af8f7d52 100644 --- a/test/functional/legacy/061_undo_tree_spec.lua +++ b/test/functional/legacy/061_undo_tree_spec.lua @@ -22,29 +22,30 @@ end describe('undo tree:', function() before_each(clear) + local fname = 'Xtest_functional_legacy_undotree' teardown(function() - os.remove('Xtest.source') + os.remove(fname .. '.source') end) describe(':earlier and :later', function() before_each(function() - os.remove('Xtest') + os.remove(fname) end) teardown(function() - os.remove('Xtest') + os.remove(fname) end) it('time specifications, g- g+', function() -- We write the test text to a file in order to prevent nvim to record -- the inserting of the text into the undo history. - write_file('Xtest', '\n123456789\n') + write_file(fname, '\n123456789\n') -- `:earlier` and `:later` are (obviously) time-sensitive, so this test -- sometimes fails if the system is under load. It is wrapped in a local -- function to allow multiple attempts. local function test_earlier_later() clear() - feed_command('e Xtest') + feed_command('e ' .. fname) -- Assert that no undo history is present. eq({}, eval('undotree().entries')) -- Delete three characters and undo. @@ -103,7 +104,7 @@ describe('undo tree:', function() it('file-write specifications', function() feed('ione one one<esc>') - feed_command('w Xtest') + feed_command('w ' .. fname) feed('otwo<esc>') feed('otwo<esc>') feed_command('w') @@ -187,7 +188,7 @@ describe('undo tree:', function() it('undo an expression-register', function() local normal_commands = 'o1\027a2\018=string(123)\n\027' - write_file('Xtest.source', normal_commands) + write_file(fname .. '.source', normal_commands) feed('oa<esc>') feed('ob<esc>') @@ -221,7 +222,7 @@ describe('undo tree:', function() c 12]]) feed('od<esc>') - feed_command('so! Xtest.source') + feed_command('so! ' .. fname .. '.source') expect([[ a diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 6c72cde855..5c3f8a6f8c 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -247,6 +247,7 @@ describe('prompt buffer', function() func DoAppend() call appendbufline('prompt', '$', 'Test') + return '' endfunc ]]) feed('asomething<CR>') @@ -254,7 +255,12 @@ describe('prompt buffer', function() neq(prev_win, meths.get_current_win()) feed('exit<CR>') eq(prev_win, meths.get_current_win()) + eq({ mode = 'n', blocking = false }, meths.get_mode()) command('call DoAppend()') eq({ mode = 'n', blocking = false }, meths.get_mode()) + feed('i') + eq({ mode = 'i', blocking = false }, meths.get_mode()) + command('call DoAppend()') + eq({ mode = 'i', blocking = false }, meths.get_mode()) end) end) diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua new file mode 100644 index 0000000000..e2958d1592 --- /dev/null +++ b/test/functional/lua/loader_spec.lua @@ -0,0 +1,36 @@ +-- Test suite for testing interactions with API bindings +local helpers = require('test.functional.helpers')(after_each) + +local exec_lua = helpers.exec_lua +local command = helpers.command +local eq = helpers.eq + +describe('vim.loader', function() + before_each(helpers.clear) + + it('handles changing files (#23027)', function() + exec_lua[[ + vim.loader.enable() + ]] + + local tmp = helpers.tmpname() + command('edit ' .. tmp) + + eq(1, exec_lua([[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=1'}) + vim.cmd.write() + loadfile(...)() + return _G.TEST + ]], tmp)) + + -- fs latency + helpers.sleep(10) + + eq(2, exec_lua([[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=2'}) + vim.cmd.write() + loadfile(...)() + return _G.TEST + ]], tmp)) + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 45d9263c0c..1ee1a13fd5 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -461,6 +461,22 @@ describe('lua stdlib', function() pcall_err(exec_lua, [[return vim.pesc(2)]])) end) + it('vim.list_contains', function() + eq(true, exec_lua("return vim.list_contains({'a','b','c'}, 'c')")) + eq(false, exec_lua("return vim.list_contains({'a','b','c'}, 'd')")) + end) + + it('vim.tbl_contains', function() + eq(true, exec_lua("return vim.tbl_contains({'a','b','c'}, 'c')")) + eq(false, exec_lua("return vim.tbl_contains({'a','b','c'}, 'd')")) + eq(true, exec_lua("return vim.tbl_contains({[2]='a',foo='b',[5] = 'c'}, 'c')")) + eq(true, exec_lua([[ + return vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) + return vim.deep_equal(v, { 'b', 'c' }) + end, { predicate = true }) + ]])) + end) + it('vim.tbl_keys', function() eq({}, exec_lua("return vim.tbl_keys({})")) for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do @@ -505,6 +521,19 @@ describe('lua stdlib', function() ]])) end) + it('vim.tbl_isarray', function() + eq(true, exec_lua("return vim.tbl_isarray({})")) + eq(false, exec_lua("return vim.tbl_isarray(vim.empty_dict())")) + eq(true, exec_lua("return vim.tbl_isarray({'a', 'b', 'c'})")) + eq(false, exec_lua("return vim.tbl_isarray({'a', '32', a='hello', b='baz'})")) + eq(false, exec_lua("return vim.tbl_isarray({1, a='hello', b='baz'})")) + eq(false, exec_lua("return vim.tbl_isarray({a='hello', b='baz', 1})")) + eq(false, exec_lua("return vim.tbl_isarray({1, 2, nil, a='hello'})")) + eq(true, exec_lua("return vim.tbl_isarray({1, 2, nil, 4})")) + eq(true, exec_lua("return vim.tbl_isarray({nil, 2, 3, 4})")) + eq(false, exec_lua("return vim.tbl_isarray({1, [1.5]=2, [3]=3})")) + end) + it('vim.tbl_islist', function() eq(true, exec_lua("return vim.tbl_islist({})")) eq(false, exec_lua("return vim.tbl_islist(vim.empty_dict())")) @@ -513,6 +542,9 @@ describe('lua stdlib', function() eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})")) eq(false, exec_lua("return vim.tbl_islist({a='hello', b='baz', 1})")) eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, a='hello'})")) + eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, 4})")) + eq(false, exec_lua("return vim.tbl_islist({nil, 2, 3, 4})")) + eq(false, exec_lua("return vim.tbl_islist({1, [1.5]=2, [3]=3})")) end) it('vim.tbl_isempty', function() @@ -2297,6 +2329,10 @@ describe('lua stdlib', function() insert([[αα]]) eq({0,5}, exec_lua[[ return vim.region(0,{0,0},{0,4},'3',true)[0] ]]) end) + it('getpos() input', function() + insert('getpos') + eq({0,6}, exec_lua[[ return vim.region(0,{0,0},'.','v',true)[0] ]]) + end) end) describe('vim.on_key', function() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index da05b09593..5ba0706208 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -323,15 +323,13 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) + lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") - end; on_handler = function(_, _, ctx) if ctx.method == 'test' then eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc")) @@ -352,7 +350,8 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ vim.api.nvim_command('filetype plugin on') BUFFER_1 = vim.api.nvim_create_buf(false, true) @@ -360,14 +359,16 @@ describe('LSP', function() vim.api.nvim_buf_set_option(BUFFER_1, 'filetype', 'man') vim.api.nvim_buf_set_option(BUFFER_2, 'filetype', 'xml') ]] + + -- Sanity check to ensure that some values are set after setting filetype. eq('v:lua.require\'man\'.goto_tag', get_buf_option("tagfunc", "BUFFER_1")) eq('xmlcomplete#CompleteTags', get_buf_option("omnifunc", "BUFFER_2")) eq('xmlformat#Format()', get_buf_option("formatexpr", "BUFFER_2")) - end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)") - exec_lua("lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)") + + exec_lua [[ + lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID) + lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID) + ]] end; on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -389,18 +390,16 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_option(BUFFER, 'tagfunc', 'tfu') vim.api.nvim_buf_set_option(BUFFER, 'omnifunc', 'ofu') vim.api.nvim_buf_set_option(BUFFER, 'formatexpr', 'fex') + lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") - end; on_handler = function(_, _, ctx) if ctx.method == 'test' then eq('tfu', get_buf_option("tagfunc")) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index da2fbbe029..dad7aa851d 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -1115,5 +1115,3 @@ describe('ShaDa changes support code', function() eq(found, 100) end) end) - --- vim: ts=2 sw=2 diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index f88954c16b..3203b187cc 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -432,6 +432,25 @@ describe('float window', function() assert_alive() end) + it("should re-apply 'style' when present", function() + local float_opts = {style = 'minimal', relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local float_win = meths.open_win(0, true, float_opts) + meths.win_set_option(float_win, 'number', true) + float_opts.row = 2 + meths.win_set_config(float_win, float_opts) + eq(false, meths.win_get_option(float_win, 'number')) + end) + + it("should not re-apply 'style' when missing", function() + local float_opts = {style = 'minimal', relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local float_win = meths.open_win(0, true, float_opts) + meths.win_set_option(float_win, 'number', true) + float_opts.row = 2 + float_opts.style = nil + meths.win_set_config(float_win, float_opts) + eq(true, meths.win_get_option(float_win, 'number')) + end) + it("'scroll' is computed correctly when opening float with splitkeep=screen #20684", function() meths.set_option('splitkeep', 'screen') local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10} diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index fedfaca7ba..89b503141b 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -1411,10 +1411,10 @@ describe('ColorColumn highlight', function() [3] = {foreground = Screen.colors.Brown}, -- LineNr [4] = {foreground = Screen.colors.Brown, bold = true}, -- CursorLineNr [5] = {foreground = Screen.colors.Blue, bold = true}, -- NonText - -- NonText and ColorColumn [6] = {foreground = Screen.colors.Blue, background = Screen.colors.LightRed, bold = true}, [7] = {reverse = true, bold = true}, -- StatusLine [8] = {reverse = true}, -- StatusLineNC + [9] = {background = Screen.colors.Grey90, foreground = Screen.colors.Red}, }) screen:attach() end) @@ -1500,6 +1500,25 @@ describe('ColorColumn highlight', function() | ]]) end) + + it('is combined with low-priority CursorLine highlight #23016', function() + screen:try_resize(40, 2) + command('set colorcolumn=30 cursorline') + screen:expect([[ + {2:^ }{1: }{2: }| + | + ]]) + command('hi clear ColorColumn') + screen:expect([[ + {2:^ }| + | + ]]) + command('hi ColorColumn guifg=Red') + screen:expect([[ + {2:^ }{9: }{2: }| + | + ]]) + end) end) describe("MsgSeparator highlight and msgsep fillchar", function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 5220f3fa89..1a7fe26d26 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -22,6 +22,7 @@ local skip = helpers.skip describe('ui/ext_messages', function() local screen + local fname = 'Xtest_functional_ui_messages_spec' before_each(function() clear() @@ -41,7 +42,7 @@ describe('ui/ext_messages', function() }) end) after_each(function() - os.remove('Xtest') + os.remove(fname) end) it('msg_clear follows msg_show kind of confirm', function() @@ -126,7 +127,7 @@ describe('ui/ext_messages', function() feed('nq') -- kind=wmsg (editing readonly file) - command('write Xtest') + command('write ' .. fname) command('set readonly nohls') feed('G$x') screen:expect{grid=[[ @@ -912,9 +913,9 @@ stack traceback: end) it('does not truncate messages', function() - command('write Xtest') + command('write '.. fname) screen:expect({messages={ - {content = { { '"Xtest" [New] 0L, 0B written' } }, kind = "" } + {content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = "" } }}) end) end) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 0a253455ad..a2fe875e65 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -397,6 +397,29 @@ describe('statuscolumn', function() {0:~ }| | ]]) + -- Also test virt_lines when 'cpoptions' includes "n" + exec_lua([[ + vim.opt.cpoptions:append("n") + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 14, 0, { virt_lines = {{{"virt_line1", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 14, 0, { virt_lines = {{{"virt_line2", ""}}} }) + ]]) + screen:expect([[ + {1:buffer 0 13}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaa | + {1:buffer 0 14}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaa | + {1:buffer 0 15}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaa | + {1:virtual-2 15}virt_line1 | + {1:virtual-2 15}virt_line2 | + {1:buffer 0 16}{5:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {5:aaaaaaaaa }| + {1:virtual-1 16}END | + {0:~ }| + {0:~ }| + | + ]]) end) it("works with 'statuscolumn' clicks", function() @@ -593,4 +616,17 @@ describe('statuscolumn', function() | ]]) end) + + it("is only evaluated twice, once to estimate and once to draw", function() + command([[ + let g:stcnr = 0 + func! Stc() + let g:stcnr += 1 + return '12345' + endfunc + set stc=%!Stc() + norm ggdG + ]]) + eq(2, eval('g:stcnr')) + end) end) diff --git a/test/functional/ui/winbar_spec.lua b/test/functional/ui/winbar_spec.lua index ece27ec3ff..970f9c3d76 100644 --- a/test/functional/ui/winbar_spec.lua +++ b/test/functional/ui/winbar_spec.lua @@ -114,6 +114,41 @@ describe('winbar', function() {2:[No Name] [No Name] }| | ]]) + -- 'showcmdloc' "statusline" should not interfere with winbar redrawing #23030 + command('set showcmd showcmdloc=statusline') + feed('<C-W>w') + feed('<C-W>') + screen:expect([[ + {6:Set Up The Bars }│{6:Set Up The Bars }| + │ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{5:Set Up The Bars }| + {3:~ }│^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] ^W }| + {3:~ }│{6:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) + feed('w<C-W>W') + screen:expect([[ + {6:Set Up The Bars }│{6:Set Up The Bars }| + │ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{5:Set Up The Bars }| + {3:~ }│^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] }| + {3:~ }│{6:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) end) it('works when switching value of \'winbar\'', function() diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index b995aaa5a6..b411b1e379 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -220,3 +220,38 @@ describe('listing functions using :function', function() assert_alive() end) end) + +it('no double-free in garbage collection #16287', function() + clear() + -- Don't use exec() here as using a named script reproduces the issue better. + write_file('Xgarbagecollect.vim', [[ + func Foo() abort + let s:args = [a:000] + let foo0 = "" + let foo1 = "" + let foo2 = "" + let foo3 = "" + let foo4 = "" + let foo5 = "" + let foo6 = "" + let foo7 = "" + let foo8 = "" + let foo9 = "" + let foo10 = "" + let foo11 = "" + let foo12 = "" + let foo13 = "" + let foo14 = "" + endfunc + + set updatetime=1 + call Foo() + call Foo() + ]]) + finally(function() + os.remove('Xgarbagecollect.vim') + end) + command('source Xgarbagecollect.vim') + sleep(10) + assert_alive() +end) diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index 158dfe86d7..130d5d81fa 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -393,7 +393,7 @@ describe('system()', function() end) describe('with output containing NULs', function() - local fname = 'Xtest' + local fname = 'Xtest_functional_vimscript_system_nuls' before_each(create_file_with_nuls(fname)) after_each(delete_file(fname)) @@ -549,7 +549,7 @@ describe('systemlist()', function() end) describe('with output containing NULs', function() - local fname = 'Xtest' + local fname = 'Xtest_functional_vimscript_systemlist_nuls' before_each(function() command('set ff=unix') diff --git a/test/helpers.lua b/test/helpers.lua index 94dcf86c4d..8f06311a3c 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -3,7 +3,6 @@ local shared = vim local assert = require('luassert') local busted = require('busted') local luv = require('luv') -local relpath = require('pl.path').relpath local Paths = require('test.cmakeconfig.paths') assert:set_parameter('TableFormatLevel', 100) @@ -21,6 +20,16 @@ local module = { REMOVE_THIS = {}, } +--- @param p string +--- @return string +local function relpath(p) + p = vim.fs.normalize(p) + local cwd = luv.cwd() + return p:gsub("^" .. cwd) +end + +--- @param path string +--- @return boolean function module.isdir(path) if not path then return false @@ -32,6 +41,8 @@ function module.isdir(path) return stat.type == 'directory' end +--- @param path string +--- @return boolean function module.isfile(path) if not path then return false @@ -43,6 +54,7 @@ function module.isfile(path) return stat.type == 'file' end +--- @return string function module.argss_to_cmd(...) local cmd = '' for i = 1, select('#', ...) do @@ -457,6 +469,7 @@ function module.check_cores(app, force) -- luacheck: ignore end end +--- @return string? function module.repeated_read_cmd(...) for _ = 1, 10 do local stream = module.popen_r(...) @@ -556,6 +569,9 @@ function module.concat_tables(...) return ret end +--- @param str string +--- @param leave_indent? boolean +--- @return string function module.dedent(str, leave_indent) -- find minimum common indent across lines local indent = nil diff --git a/test/old/testdir/test_alot.vim b/test/old/testdir/test_alot.vim index a3d240f27e..4a22315b9f 100644 --- a/test/old/testdir/test_alot.vim +++ b/test/old/testdir/test_alot.vim @@ -2,7 +2,6 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_backup.vim -source test_behave.vim source test_compiler.vim source test_ex_equal.vim source test_ex_undo.vim diff --git a/test/old/testdir/test_arglist.vim b/test/old/testdir/test_arglist.vim index fb8b17cd16..de4e5e33d6 100644 --- a/test/old/testdir/test_arglist.vim +++ b/test/old/testdir/test_arglist.vim @@ -183,22 +183,25 @@ func Test_argument() let save_columns = &columns let &columns = 79 - exe 'args ' .. join(range(1, 81)) - call assert_equal(join([ - \ '', - \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ', - \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ', - \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ', - \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ', - \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ', - \ ], "\n"), - \ execute('args')) - - " No trailing newline with one item per row. - let long_arg = repeat('X', 81) - exe 'args ' .. long_arg - call assert_equal("\n[".long_arg.']', execute('args')) - let &columns = save_columns + try + exe 'args ' .. join(range(1, 81)) + call assert_equal(join([ + \ '', + \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ', + \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ', + \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ', + \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ', + \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ', + \ ], "\n"), + \ execute('args')) + + " No trailing newline with one item per row. + let long_arg = repeat('X', 81) + exe 'args ' .. long_arg + call assert_equal("\n[".long_arg.']', execute('args')) + finally + let &columns = save_columns + endtry " Setting argument list should fail when the current buffer has unsaved " changes diff --git a/test/old/testdir/test_behave.vim b/test/old/testdir/test_behave.vim deleted file mode 100644 index c26bfe7ce3..0000000000 --- a/test/old/testdir/test_behave.vim +++ /dev/null @@ -1,29 +0,0 @@ -" Test the :behave command - -func Test_behave() - behave mswin - call assert_equal('mouse,key', &selectmode) - call assert_equal('popup', &mousemodel) - call assert_equal('startsel,stopsel', &keymodel) - call assert_equal('exclusive', &selection) - - behave xterm - call assert_equal('', &selectmode) - call assert_equal('extend', &mousemodel) - call assert_equal('', &keymodel) - call assert_equal('inclusive', &selection) - - set selection& - set mousemodel& - set keymodel& - set selection& -endfunc - -func Test_behave_completion() - call feedkeys(":behave \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"behave mswin xterm', @:) -endfunc - -func Test_behave_error() - call assert_fails('behave x', 'E475:') -endfunc diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 1dceb43e5d..74dd7bf3c4 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -453,10 +453,10 @@ func Test_getcompletion() let l = getcompletion('blahblah', 'augroup') call assert_equal([], l) - let l = getcompletion('', 'behave') - call assert_true(index(l, 'mswin') >= 0) - let l = getcompletion('not', 'behave') - call assert_equal([], l) + " let l = getcompletion('', 'behave') + " call assert_true(index(l, 'mswin') >= 0) + " let l = getcompletion('not', 'behave') + " call assert_equal([], l) let l = getcompletion('', 'color') call assert_true(index(l, 'default') >= 0) @@ -2770,6 +2770,7 @@ endfunc " :behave suboptions fuzzy completion func Test_fuzzy_completion_behave() + throw 'Skipped: Nvim removed :behave' set wildoptions& call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx') call assert_equal('"behave xm', @:) @@ -3532,6 +3533,14 @@ endfunc func Test_setcmdline() func SetText(text, pos) + call assert_equal(0, setcmdline(v:_null_string)) + call assert_equal('', getcmdline()) + call assert_equal(1, getcmdpos()) + + call assert_equal(0, setcmdline(''[: -1])) + call assert_equal('', getcmdline()) + call assert_equal(1, getcmdpos()) + autocmd CmdlineChanged * let g:cmdtype = expand('<afile>') call assert_equal(0, setcmdline(a:text)) call assert_equal(a:text, getcmdline()) diff --git a/test/old/testdir/test_expr.vim b/test/old/testdir/test_expr.vim index 292a504df9..1810cf6741 100644 --- a/test/old/testdir/test_expr.vim +++ b/test/old/testdir/test_expr.vim @@ -39,6 +39,38 @@ func Test_version() call assert_false(has('patch-9.9.1')) endfunc +func Test_op_trinary() + call assert_equal('yes', 1 ? 'yes' : 'no') + call assert_equal('no', 0 ? 'yes' : 'no') + call assert_equal('no', 'x' ? 'yes' : 'no') + call assert_equal('yes', '1x' ? 'yes' : 'no') + + call assert_fails('echo [1] ? "yes" : "no"', 'E745:') + call assert_fails('echo {} ? "yes" : "no"', 'E728:') +endfunc + +func Test_op_falsy() + call assert_equal(v:true, v:true ?? 456) + call assert_equal(123, 123 ?? 456) + call assert_equal('yes', 'yes' ?? 456) + call assert_equal(0z00, 0z00 ?? 456) + call assert_equal([1], [1] ?? 456) + call assert_equal(#{one: 1}, #{one: 1} ?? 456) + if has('float') + call assert_equal(0.1, 0.1 ?? 456) + endif + + call assert_equal(456, v:false ?? 456) + call assert_equal(456, 0 ?? 456) + call assert_equal(456, '' ?? 456) + call assert_equal(456, 0z ?? 456) + call assert_equal(456, [] ?? 456) + call assert_equal(456, {} ?? 456) + if has('float') + call assert_equal(456, 0.0 ?? 456) + endif +endfunc + func Test_dict() let d = {'': 'empty', 'a': 'a', 0: 'zero'} call assert_equal('empty', d['']) diff --git a/test/old/testdir/test_help_tagjump.vim b/test/old/testdir/test_help_tagjump.vim index eae1a241e3..8a58d2f13c 100644 --- a/test/old/testdir/test_help_tagjump.vim +++ b/test/old/testdir/test_help_tagjump.vim @@ -35,9 +35,7 @@ func Test_help_tagjump() help ?? call assert_equal("help", &filetype) - " *??* tag needs patch 8.2.1794 - " call assert_true(getline('.') =~ '\*??\*') - call assert_true(getline('.') =~ '\*g??\*') + call assert_true(getline('.') =~ '\*??\*') helpclose help :? diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index 35745e9c6a..fe7633cf11 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -327,7 +327,7 @@ func Test_let_heredoc_fails() endfunc END call writefile(text, 'XheredocFail') - call assert_fails('source XheredocFail', 'E126:') + call assert_fails('source XheredocFail', 'E1145:') call delete('XheredocFail') let text =<< trim CodeEnd @@ -336,7 +336,7 @@ func Test_let_heredoc_fails() endfunc CodeEnd call writefile(text, 'XheredocWrong') - call assert_fails('source XheredocWrong', 'E126:') + call assert_fails('source XheredocWrong', 'E1145:') call delete('XheredocWrong') let text =<< trim TEXTend diff --git a/test/old/testdir/test_prompt_buffer.vim b/test/old/testdir/test_prompt_buffer.vim index 43d8bb4789..3dfbbcece6 100644 --- a/test/old/testdir/test_prompt_buffer.vim +++ b/test/old/testdir/test_prompt_buffer.vim @@ -271,6 +271,7 @@ func Test_prompt_appending_while_hidden() func DoAppend() call appendbufline('prompt', '$', 'Test') + return '' endfunc END call writefile(script, 'XpromptBuffer', 'D') @@ -282,12 +283,18 @@ func Test_prompt_appending_while_hidden() call TermWait(buf) call term_sendkeys(buf, "exit\<CR>") - call TermWait(buf) + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) call term_sendkeys(buf, ":call DoAppend()\<CR>") - call TermWait(buf) - call assert_notmatch('-- INSERT --', term_getline(buf, 10)) + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "i") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + call term_sendkeys(buf, "\<C-R>=DoAppend()\<CR>") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) endfunc diff --git a/test/old/testdir/test_usercommands.vim b/test/old/testdir/test_usercommands.vim index 898f7a59f1..e22f57b6f1 100644 --- a/test/old/testdir/test_usercommands.vim +++ b/test/old/testdir/test_usercommands.vim @@ -303,7 +303,7 @@ func Test_CmdErrors() call assert_fails('com! -complete=xxx DoCmd :', 'E180:') call assert_fails('com! -complete=custom DoCmd :', 'E467:') call assert_fails('com! -complete=customlist DoCmd :', 'E467:') - call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:') + " call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:') call assert_fails('com! -complete=file DoCmd :', 'E1208:') call assert_fails('com! -nargs=0 -complete=file DoCmd :', 'E1208:') call assert_fails('com! -nargs=x DoCmd :', 'E176:') @@ -391,9 +391,9 @@ func Test_CmdCompletion() call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com DoC', @:) - com! -nargs=1 -complete=behave DoCmd : - call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"DoCmd mswin xterm', @:) + " com! -nargs=1 -complete=behave DoCmd : + " call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"DoCmd mswin xterm', @:) " Test for file name completion com! -nargs=1 -complete=file DoCmd : diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index b0c4baf7c2..5988d6ed71 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -6746,7 +6746,7 @@ func Test_script_lines() \ ]) call assert_report("Shouldn't be able to define function") catch - call assert_exception('Vim(function):E126: Missing :endfunction') + call assert_exception('Vim(function):E1145: Missing heredoc end marker: .') endtry " :change @@ -6766,7 +6766,7 @@ func Test_script_lines() \ ]) call assert_report("Shouldn't be able to define function") catch - call assert_exception('Vim(function):E126: Missing :endfunction') + call assert_exception('Vim(function):E1145: Missing heredoc end marker: .') endtry " :insert @@ -6786,7 +6786,7 @@ func Test_script_lines() \ ]) call assert_report("Shouldn't be able to define function") catch - call assert_exception('Vim(function):E126: Missing :endfunction') + call assert_exception('Vim(function):E1145: Missing heredoc end marker: .') endtry endfunc @@ -7258,6 +7258,30 @@ func Test_typed_script_var() call StopVimInTerminal(buf) endfunc +" Test for issue6776 {{{1 +func Test_trinary_expression() + try + call eval('0 ? 0') + catch + endtry + " previous failure should not cause next expression to fail + call assert_equal(v:false, eval(string(v:false))) + + try + call eval('0 ? "burp') + catch + endtry + " previous failure should not cause next expression to fail + call assert_equal(v:false, eval(string(v:false))) + + try + call eval('1 ? 0 : "burp') + catch + endtry + " previous failure should not cause next expression to fail + call assert_equal(v:false, eval(string(v:false))) +endfunction + func Test_for_over_string() let res = '' for c in 'aéc̀d' diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 350c69fe4e..d10a946200 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -485,14 +485,16 @@ endfunc func Test_visual_block_put_invalid() enew! - behave mswin + " behave mswin + set selection=exclusive norm yy norm v)Ps/^/ " this was causing the column to become negative silent norm ggv)P bwipe! - behave xterm + " behave xterm + set selection& endfunc " Visual modes (v V CTRL-V) followed by an operator; count; repeating diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 9dd6b76de2..883f01bd84 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -512,7 +512,8 @@ 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 + local evalarg = ffi.new('evalarg_T', {eval_flags = eval.EVAL_EVALUATE}) + if eval.eval0(to_cstr(expr), tv, nil, evalarg) == 0 then return nil else return tv diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index 2fd37c599a..c94f0d88f7 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -154,6 +154,8 @@ local C_keywords = set { -- luacheck: ignore -- -- The first one will have a lot of false positives (the line '{' for -- example), the second one is more unique. +--- @param string +--- @return string local function formatc(str) local toks = TokeniseC(str) local result = {} diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 708929ad9f..10b7594a88 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -14,20 +14,15 @@ local map = global_helpers.tbl_map local eq = global_helpers.eq local trim = global_helpers.trim --- C constants. -local NULL = ffi.cast('void*', 0) - -local OK = 1 -local FAIL = 0 - -local cimport - -- add some standard header locations for _, p in ipairs(Paths.include_paths) do Preprocess.add_to_include_path(p) end -local child_pid = nil +local child_pid = nil --- @type integer +--- @generic F: function +--- @param func F +--- @return F local function only_separate(func) return function(...) if child_pid ~= 0 then @@ -36,9 +31,20 @@ local function only_separate(func) return func(...) end end -local child_calls_init = {} -local child_calls_mod = nil -local child_calls_mod_once = nil + +--- @class ChildCall +--- @field func function +--- @field args any[] + +--- @class ChildCallLog +--- @field func string +--- @field args any[] +--- @field ret any? + +local child_calls_init = {} --- @type ChildCall[] +local child_calls_mod = nil --- @type ChildCall[] +local child_calls_mod_once = nil --- @type ChildCall[]? + local function child_call(func, ret) return function(...) local child_calls = child_calls_mod or child_calls_init @@ -53,16 +59,16 @@ end -- Run some code at the start of the child process, before running the test -- itself. Is supposed to be run in `before_each`. +--- @param func function local function child_call_once(func, ...) if child_pid ~= 0 then - child_calls_mod_once[#child_calls_mod_once + 1] = { - func=func, args={...}} + child_calls_mod_once[#child_calls_mod_once + 1] = { func = func, args = {...} } else func(...) end end -local child_cleanups_mod_once = nil +local child_cleanups_mod_once = nil --- @type ChildCall[]? -- Run some code at the end of the child process, before exiting. Is supposed to -- be run in `before_each` because `after_each` is run after child has exited. @@ -125,8 +131,9 @@ local pragma_pack_id = 1 -- some things are just too complex for the LuaJIT C parser to digest. We -- usually don't need them anyway. +--- @param body string local function filter_complex_blocks(body) - local result = {} + local result = {} --- @type string[] for line in body:gmatch("[^\r\n]+") do if not (string.find(line, "(^)", 1, true) ~= nil @@ -153,18 +160,20 @@ typedef struct { char bytes[16]; } __attribute__((aligned(16))) __uint128_t; typedef struct { char bytes[16]; } __attribute__((aligned(16))) __float128; ]] -local preprocess_cache_init = {} +local preprocess_cache_init = {} --- @type table<string,string> local previous_defines_mod = '' -local preprocess_cache_mod = nil +local preprocess_cache_mod = nil --- @type table<string,string> local function is_child_cdefs() - return (os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1') + return os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1' end -- use this helper to import C files, you can pass multiple paths at once, -- this helper will return the C namespace of the nvim library. -cimport = function(...) - local previous_defines, preprocess_cache, cdefs +local function cimport(...) + local previous_defines --- @type string + local preprocess_cache --- @type table<string,string> + local cdefs if is_child_cdefs() and preprocess_cache_mod then preprocess_cache = preprocess_cache_mod previous_defines = previous_defines_mod @@ -180,7 +189,7 @@ cimport = function(...) path = './' .. path end if not preprocess_cache[path] then - local body + local body --- @type string body, previous_defines = Preprocess.preprocess(previous_defines, path) -- format it (so that the lines are "unique" statements), also filter out -- Objective-C blocks @@ -202,6 +211,7 @@ cimport = function(...) -- (they are needed in the right order with the struct definitions, -- otherwise luajit has wrong memory layouts for the sturcts) if line:match("#pragma%s+pack") then + --- @type string line = line .. " // " .. pragma_pack_id pragma_pack_id = pragma_pack_id + 1 end @@ -229,20 +239,21 @@ cimport = function(...) return lib end -local cimport_immediate = function(...) +local function cimport_immediate(...) local saved_pid = child_pid child_pid = 0 local err, emsg = pcall(cimport, ...) child_pid = saved_pid if not err then - emsg = tostring(emsg) - io.stderr:write(emsg .. '\n') + io.stderr:write(tostring(emsg) .. '\n') assert(false) else return lib end end +--- @param preprocess_cache table<string,string[]> +--- @param path string local function _cimportstr(preprocess_cache, path) if imported:contains(path) then return lib @@ -265,12 +276,14 @@ end local function alloc_log_new() local log = { - log={}, - lib=cimport('./src/nvim/memory.h'), - original_functions={}, + log={}, --- @type ChildCallLog[] + lib=cimport('./src/nvim/memory.h'), --- @type table<string,function> + original_functions={}, --- @type table<string,function> null={['\0:is_null']=true}, } + local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'} + function log:save_original_functions() for _, funcname in ipairs(allocator_functions) do if not self.original_functions[funcname] then @@ -278,13 +291,16 @@ local function alloc_log_new() end end end + log.save_original_functions = child_call(log.save_original_functions) + function log:set_mocks() for _, k in ipairs(allocator_functions) do do local kk = k self.lib['mem_' .. k] = function(...) - local log_entry = {func=kk, args={...}} + --- @type ChildCallLog + local log_entry = { func = kk, args = {...} } self.log[#self.log + 1] = log_entry if kk == 'free' then self.original_functions[kk](...) @@ -305,17 +321,21 @@ local function alloc_log_new() end end end + log.set_mocks = child_call(log.set_mocks) + function log:clear() self.log = {} end + function log:check(exp) eq(exp, self.log) self:clear() end + function log:clear_tmp_allocs(clear_null_frees) - local toremove = {} - local allocs = {} + local toremove = {} --- @type integer[] + local allocs = {} --- @type table<string,integer> for i, v in ipairs(self.log) do if v.func == 'malloc' or v.func == 'calloc' then allocs[tostring(v.ret)] = i @@ -338,26 +358,20 @@ local function alloc_log_new() table.remove(self.log, toremove[i]) end end - function log:restore_original_functions() - -- Do nothing: set mocks live in a separate process - return - --[[ - [ for k, v in pairs(self.original_functions) do - [ self.lib['mem_' .. k] = v - [ end - ]] - end + function log:setup() log:save_original_functions() log:set_mocks() end + function log:before_each() - return end + function log:after_each() - log:restore_original_functions() end + log:setup() + return log end @@ -374,98 +388,109 @@ local function to_cstr(string) end cimport_immediate('./test/unit/fixtures/posix.h') -local sc = { - fork = function() - return tonumber(ffi.C.fork()) - end, - pipe = function() - local ret = ffi.new('int[2]', {-1, -1}) - ffi.errno(0) - local res = ffi.C.pipe(ret) - if (res ~= 0) then + +local sc = {} + +function sc.fork() + return tonumber(ffi.C.fork()) +end + +function sc.pipe() + local ret = ffi.new('int[2]', {-1, -1}) + ffi.errno(0) + local res = ffi.C.pipe(ret) + if (res ~= 0) then + local err = ffi.errno(0) + assert(res == 0, ("pipe() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) + end + assert(ret[0] ~= -1 and ret[1] ~= -1) + return ret[0], ret[1] +end + +--- @return string +function sc.read(rd, len) + local ret = ffi.new('char[?]', len, {0}) + local total_bytes_read = 0 + ffi.errno(0) + while total_bytes_read < len do + local bytes_read = tonumber(ffi.C.read( + rd, + ffi.cast('void*', ret + total_bytes_read), + len - total_bytes_read)) + if bytes_read == -1 then local err = ffi.errno(0) - assert(res == 0, ("pipe() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - assert(ret[0] ~= -1 and ret[1] ~= -1) - return ret[0], ret[1] - end, - read = function(rd, len) - local ret = ffi.new('char[?]', len, {0}) - local total_bytes_read = 0 - ffi.errno(0) - while total_bytes_read < len do - local bytes_read = tonumber(ffi.C.read( - rd, - ffi.cast('void*', ret + total_bytes_read), - len - total_bytes_read)) - if bytes_read == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("read() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - elseif bytes_read == 0 then - break - else - total_bytes_read = total_bytes_read + bytes_read + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("read() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + elseif bytes_read == 0 then + break + else + total_bytes_read = total_bytes_read + bytes_read end - return ffi.string(ret, total_bytes_read) - end, - write = function(wr, s) - local wbuf = to_cstr(s) - local total_bytes_written = 0 - ffi.errno(0) - while total_bytes_written < #s do - local bytes_written = tonumber(ffi.C.write( - wr, - ffi.cast('void*', wbuf + total_bytes_written), - #s - total_bytes_written)) - if bytes_written == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("write() error: %u: %s ('%s')"):format( - err, ffi.string(ffi.C.strerror(err)), s)) - end - elseif bytes_written == 0 then - break - else - total_bytes_written = total_bytes_written + bytes_written + end + return ffi.string(ret, total_bytes_read) +end + +function sc.write(wr, s) + local wbuf = to_cstr(s) + local total_bytes_written = 0 + ffi.errno(0) + while total_bytes_written < #s do + local bytes_written = tonumber(ffi.C.write( + wr, + ffi.cast('void*', wbuf + total_bytes_written), + #s - total_bytes_written)) + if bytes_written == -1 then + local err = ffi.errno(0) + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("write() error: %u: %s ('%s')"):format( + err, ffi.string(ffi.C.strerror(err)), s)) end + elseif bytes_written == 0 then + break + else + total_bytes_written = total_bytes_written + bytes_written end - return total_bytes_written - end, - close = ffi.C.close, - wait = function(pid) - ffi.errno(0) - local stat_loc = ffi.new('int[1]', {0}) - while true do - local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED) - if r == -1 then - local err = ffi.errno(0) - if err == ffi.C.kPOSIXErrnoECHILD then - break - elseif err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("waitpid() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - else - assert(r == pid) + end + return total_bytes_written +end + +sc.close = ffi.C.close + +--- @param pid integer +--- @return integer +function sc.wait(pid) + ffi.errno(0) + local stat_loc = ffi.new('int[1]', {0}) + while true do + local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED) + if r == -1 then + local err = ffi.errno(0) + if err == ffi.C.kPOSIXErrnoECHILD then + break + elseif err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("waitpid() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + else + assert(r == pid) end - return stat_loc[0] - end, - exit = ffi.C._exit, -} + end + return stat_loc[0] +end + +sc.exit = ffi.C._exit +--- @param lst string[] +--- @return string local function format_list(lst) - local ret = '' + local ret = {} --- @type string[] for _, v in ipairs(lst) do - if ret ~= '' then ret = ret .. ', ' end - ret = ret .. assert:format({v, n=1})[1] + ret[#ret+1] = assert:format({v, n=1})[1] end - return ret + return table.concat(ret, ', ') end if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then @@ -513,19 +538,26 @@ local tracehelp = dedent([[ ]]) local function child_sethook(wr) - local trace_level = os.getenv('NVIM_TEST_TRACE_LEVEL') - if not trace_level or trace_level == '' then - trace_level = 0 - else - trace_level = tonumber(trace_level) + local trace_level_str = os.getenv('NVIM_TEST_TRACE_LEVEL') + local trace_level = 0 + if trace_level_str and trace_level_str ~= '' then + --- @type number + trace_level = assert(tonumber(trace_level_str)) end + if trace_level <= 0 then return end + local trace_only_c = trace_level <= 1 + --- @type debuginfo?, string?, integer local prev_info, prev_reason, prev_lnum + + --- @param reason string + --- @param lnum integer + --- @param use_prev boolean local function hook(reason, lnum, use_prev) - local info = nil + local info = nil --- @type debuginfo? if use_prev then info = prev_info elseif reason ~= 'tail return' then -- tail return @@ -533,6 +565,7 @@ local function child_sethook(wr) end if trace_only_c and (not info or info.what ~= 'C') and not use_prev then + --- @cast info -nil if info.source:sub(-9) == '_spec.lua' then prev_info = info prev_reason = 'saved' @@ -573,12 +606,8 @@ local function child_sethook(wr) 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 lnum_s = lnum == -1 and 'nknwn' or ('%u'):format(lnum) + --- @type string local msg = ( -- lua does not support %* '' .. msgchar @@ -600,6 +629,7 @@ end local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2)) +--- @type function local _debug_log local debug_log = only_separate(function(...) @@ -607,6 +637,7 @@ local debug_log = only_separate(function(...) end) local function itp_child(wr, func) + --- @param s string _debug_log = function(s) s = s:sub(1, hook_msglen - 2) sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') @@ -638,7 +669,7 @@ local function itp_child(wr, func) end local function check_child_err(rd) - local trace = {} + local trace = {} --- @type string[] local did_traceline = false local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024 while true do @@ -668,11 +699,14 @@ local function check_child_err(rd) local len = tonumber(len_s) neq(0, len) if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then + --- @type string err = '\nTest failed, trace:\n' .. tracehelp for _, traceline in ipairs(trace) do + --- @type string err = err .. traceline end end + --- @type string err = err .. sc.read(rd, len + 1) end local eres = sc.read(rd, 2) @@ -686,10 +720,12 @@ local function check_child_err(rd) end end if not did_traceline then + --- @type string err = err .. '\nNo end of trace occurred' end local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) if not cc_err then + --- @type string err = err .. '\ncheck_cores failed: ' .. cc_emsg end end @@ -822,9 +858,9 @@ local module = { lib = lib, cstr = cstr, to_cstr = to_cstr, - NULL = NULL, - OK = OK, - FAIL = FAIL, + NULL = ffi.cast('void*', 0), + OK = 1, + FAIL = 0, alloc_log_new = alloc_log_new, gen_itp = gen_itp, only_separate = only_separate, diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 88f7a14d1f..c5fb62dce5 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -7,6 +7,9 @@ local global_helpers = require('test.helpers') local argss_to_cmd = global_helpers.argss_to_cmd local repeated_read_cmd = global_helpers.repeated_read_cmd +--- @alias Compiler {path: string[], type: string} + +--- @type Compiler[] local ccs = {} local env_cc = os.getenv("CC") @@ -27,6 +30,8 @@ table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"}) table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"}) -- parse Makefile format dependencies into a Lua table +--- @param deps string +--- @return string[] local function parse_make_deps(deps) -- remove line breaks and line concatenators deps = deps:gsub("\n", ""):gsub("\\", "") @@ -36,7 +41,7 @@ local function parse_make_deps(deps) deps = deps:gsub(" +", " ") -- split according to token (space in this case) - local headers = {} + local headers = {} --- @type string[] for token in deps:gmatch("[^%s]+") do -- headers[token] = true headers[#headers + 1] = token @@ -53,57 +58,58 @@ local function parse_make_deps(deps) return headers end --- will produce a string that represents a meta C header file that includes --- all the passed in headers. I.e.: --- --- headerize({"stdio.h", "math.h"}, true) --- produces: --- #include <stdio.h> --- #include <math.h> --- --- headerize({"vim.h", "memory.h"}, false) --- produces: --- #include "vim.h" --- #include "memory.h" +--- will produce a string that represents a meta C header file that includes +--- all the passed in headers. I.e.: +--- +--- headerize({"stdio.h", "math.h"}, true) +--- produces: +--- #include <stdio.h> +--- #include <math.h> +--- +--- headerize({"vim.h", "memory.h"}, false) +--- produces: +--- #include "vim.h" +--- #include "memory.h" +--- @param headers string[] +--- @param global? boolean +--- @return string local function headerize(headers, global) - local pre = '"' - local post = pre - if global then - pre = "<" - post = ">" - end - - local formatted = {} + local fmt = global and '#include <%s>' or '#include "%s"' + local formatted = {} --- @type string[] for _, hdr in ipairs(headers) do - formatted[#formatted + 1] = "#include " .. - tostring(pre) .. - tostring(hdr) .. - tostring(post) + formatted[#formatted + 1] = string.format(fmt, hdr) end return table.concat(formatted, "\n") end +--- @class Gcc +--- @field path string +--- @field preprocessor_extra_flags string[] +--- @field get_defines_extra_flags string[] +--- @field get_declarations_extra_flags string[] local Gcc = { preprocessor_extra_flags = {}, get_defines_extra_flags = {'-std=c99', '-dM', '-E'}, get_declarations_extra_flags = {'-std=c99', '-P', '-E'}, } +--- @param name string +--- @param args string[]? +--- @param val string? function Gcc:define(name, args, val) - local define = '-D' .. name - if args ~= nil then - define = define .. '(' .. table.concat(args, ',') .. ')' + local define = string.format('-D%s', name) + if args then + define = string.format('%s(%s)', define, table.concat(args, ',')) end - if val ~= nil then - define = define .. '=' .. val + if val then + define = string.format('%s=%s', define, val) end self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = define end function Gcc:undefine(name) - self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = ( - '-U' .. name) + self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = '-U' .. name end function Gcc:init_defines() @@ -128,6 +134,8 @@ function Gcc:init_defines() self:undefine('__BLOCKS__') end +--- @param obj? Compiler +--- @return Gcc function Gcc:new(obj) obj = obj or {} setmetatable(obj, self) @@ -136,6 +144,7 @@ function Gcc:new(obj) return obj end +--- @param ... string function Gcc:add_to_include_path(...) for i = 1, select('#', ...) do local path = select(i, ...) @@ -145,110 +154,115 @@ function Gcc:add_to_include_path(...) end -- returns a list of the headers files upon which this file relies +--- @param hdr string +--- @return string[]? function Gcc:dependencies(hdr) + --- @type string local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1' - local out = io.popen(cmd) + local out = assert(io.popen(cmd)) local deps = out:read("*a") out:close() if deps then return parse_make_deps(deps) - else - return nil end end +--- @param defines string +--- @return string function Gcc:filter_standard_defines(defines) if not self.standard_defines then local pseudoheader_fname = 'tmp_empty_pseudoheader.h' - local pseudoheader_file = io.open(pseudoheader_fname, 'w') + local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w')) pseudoheader_file:close() - local standard_defines = repeated_read_cmd(self.path, - self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local standard_defines = assert(repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname})) os.remove(pseudoheader_fname) - self.standard_defines = {} + self.standard_defines = {} --- @type table<string,true> for line in standard_defines:gmatch('[^\n]+') do self.standard_defines[line] = true end end - local ret = {} + + local ret = {} --- @type string[] for line in defines:gmatch('[^\n]+') do if not self.standard_defines[line] then ret[#ret + 1] = line end end + return table.concat(ret, "\n") end --- returns a stream representing a preprocessed form of the passed-in headers. --- Don't forget to close the stream by calling the close() method on it. +--- returns a stream representing a preprocessed form of the passed-in headers. +--- Don't forget to close the stream by calling the close() method on it. +--- @param previous_defines string +--- @param ... string +--- @return string, string function Gcc:preprocess(previous_defines, ...) -- create pseudo-header local pseudoheader = headerize({...}, false) local pseudoheader_fname = 'tmp_pseudoheader.h' - local pseudoheader_file = io.open(pseudoheader_fname, 'w') + local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w')) pseudoheader_file:write(previous_defines) pseudoheader_file:write("\n") pseudoheader_file:write(pseudoheader) pseudoheader_file:flush() pseudoheader_file:close() - local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local defines = assert(repeated_read_cmd(self.path, self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname})) defines = self:filter_standard_defines(defines) - local declarations = repeated_read_cmd(self.path, - self.preprocessor_extra_flags, - self.get_declarations_extra_flags, - {pseudoheader_fname}) + local declarations = assert(repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_declarations_extra_flags, + {pseudoheader_fname})) os.remove(pseudoheader_fname) - assert(declarations and defines) return declarations, defines end -local Clang = Gcc:new() -local Msvc = Gcc:new() - -local type_to_class = { - ["gcc"] = Gcc, - ["clang"] = Clang, - ["msvc"] = Msvc -} - -- find the best cc. If os.exec causes problems on windows (like popping up -- a console window) we might consider using something like this: -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec +--- @param compilers Compiler[] +--- @return Gcc? local function find_best_cc(compilers) for _, meta in pairs(compilers) do - local version = io.popen(tostring(meta.path) .. " -v 2>&1") + local version = assert(io.popen(tostring(meta.path) .. " -v 2>&1")) version:close() if version then - return type_to_class[meta.type]:new({path = meta.path}) + return Gcc:new({path = meta.path}) end end - return nil end -- find the best cc. If os.exec causes problems on windows (like popping up -- a console window) we might consider using something like this: -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec -local cc = nil -if cc == nil then - cc = find_best_cc(ccs) -end - -return { - includes = function(hdr) - return cc:dependencies(hdr) - end, - preprocess = function(...) - return cc:preprocess(...) - end, - add_to_include_path = function(...) - return cc:add_to_include_path(...) - end -} +local cc = assert(find_best_cc(ccs)) + +local M = {} + +--- @param hdr string +--- @return string[]? +function M.includes(hdr) + return cc:dependencies(hdr) +end + +--- @param ... string +--- @return string, string +function M.preprocess(...) + return cc:preprocess(...) +end + +--- @param ... string +function M.add_to_include_path(...) + return cc:add_to_include_path(...) +end + +return M diff --git a/test/unit/set.lua b/test/unit/set.lua index f3d68c3042..91b7e2dd70 100644 --- a/test/unit/set.lua +++ b/test/unit/set.lua @@ -4,10 +4,15 @@ -- other: -- 1) index => item -- 2) item => index +--- @class Set +--- @field nelem integer +--- @field items string[] +--- @field tbl table local Set = {} +--- @param items? string[] function Set:new(items) - local obj = {} + local obj = {} --- @ type Set setmetatable(obj, self) self.__index = self @@ -26,8 +31,9 @@ function Set:new(items) return obj end +--- @return Set function Set:copy() - local obj = {} + local obj = {} --- @ type Set obj.nelem = self.nelem obj.tbl = {} obj.items = {} @@ -43,6 +49,7 @@ function Set:copy() end -- adds the argument Set to this Set +--- @param other Set function Set:union(other) for e in other:iterator() do self:add(e) @@ -57,6 +64,7 @@ function Set:union_table(t) end -- subtracts the argument Set from this Set +--- @param other Set function Set:diff(other) if other:size() > self:size() then -- this set is smaller than the other set @@ -75,6 +83,7 @@ function Set:diff(other) end end +--- @param it string function Set:add(it) if not self:contains(it) then local idx = #self.tbl + 1 @@ -84,6 +93,7 @@ function Set:add(it) end end +--- @param it string function Set:remove(it) if self:contains(it) then local idx = self.items[it] @@ -93,10 +103,13 @@ function Set:remove(it) end end +--- @param it string +--- @return boolean function Set:contains(it) return self.items[it] or false end +--- @return integer function Set:size() return self.nelem end @@ -113,29 +126,18 @@ function Set:iterator() return pairs(self.items) end +--- @return string[] function Set:to_table() -- there might be gaps in @tbl, so we have to be careful and sort first - local keys - do - local _accum_0 = { } - local _len_0 = 1 - for idx, _ in pairs(self.tbl) do - _accum_0[_len_0] = idx - _len_0 = _len_0 + 1 - end - keys = _accum_0 + local keys = {} --- @type string[] + for idx, _ in pairs(self.tbl) do + keys[#keys+1] = idx end + table.sort(keys) - local copy - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #keys do - local idx = keys[_index_0] - _accum_0[_len_0] = self.tbl[idx] - _len_0 = _len_0 + 1 - end - copy = _accum_0 + local copy = {} --- @type string[] + for _, idx in ipairs(keys) do + copy[#copy+1] = self.tbl[idx] end return copy end |