diff options
Diffstat (limited to 'test')
28 files changed, 1140 insertions, 263 deletions
diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua new file mode 100644 index 0000000000..b028a50b02 --- /dev/null +++ b/test/functional/api/ui_spec.lua @@ -0,0 +1,37 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local expect_err = helpers.expect_err +local meths = helpers.meths +local request = helpers.request + +describe('nvim_ui_attach()', function() + before_each(function() + clear() + end) + it('handles very large width/height #2180', function() + local screen = Screen.new(999, 999) + screen:attach() + eq(999, eval('&lines')) + eq(999, eval('&columns')) + end) + it('invalid option returns error', function() + expect_err('No such UI option: foo', + meths.ui_attach, 80, 24, { foo={'foo'} }) + end) + it('validates channel arg', function() + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_try_resize', 40, 10) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_set_option', 'rgb', true) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_detach') + + local screen = Screen.new() + screen:attach({rgb=false}) + expect_err('UI already attached to channel: 1', + request, 'nvim_ui_attach', 40, 10, { rgb=false }) + end) +end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 7ac20a99af..718294d941 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -73,6 +73,8 @@ describe('api', function() it('captures command output', function() eq('this is\nspinal tap', nvim('command_output', [[echo "this is\nspinal tap"]])) + eq('no line ending!', + nvim('command_output', [[echon "no line ending!"]])) end) it('captures empty command output', function() diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index 0804579a4f..db4e5379d0 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -32,7 +32,7 @@ describe('TermClose event', function() end) it('kills job trapping SIGTERM', function() - if helpers.pending_win32(pending) then return end + if iswin() then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]] @@ -51,8 +51,8 @@ describe('TermClose event', function() ok(duration <= 4000) -- Epsilon for slow CI end) - it('kills pty job trapping SIGHUP and SIGTERM', function() - if helpers.pending_win32(pending) then return end + it('kills PTY job trapping SIGHUP and SIGTERM', function() + if iswin() then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]] diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua new file mode 100644 index 0000000000..09533e4e60 --- /dev/null +++ b/test/functional/core/fileio_spec.lua @@ -0,0 +1,68 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local feed = helpers.feed +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local request = helpers.request +local retry = helpers.retry +local rmdir = helpers.rmdir +local sleep = helpers.sleep + +describe('fileio', function() + before_each(function() + end) + after_each(function() + command(':qall!') + os.remove('Xtest_startup_shada') + os.remove('Xtest_startup_file1') + os.remove('Xtest_startup_file2') + rmdir('Xtest_startup_swapdir') + end) + + it('fsync() codepaths #8304', function() + clear({ args={ '-i', 'Xtest_startup_shada', + '--cmd', 'set directory=Xtest_startup_swapdir' } }) + + -- These cases ALWAYS force fsync (regardless of 'fsync' option): + + -- 1. Idle (CursorHold) with modified buffers (+ 'swapfile'). + command('write Xtest_startup_file1') + feed('ifoo<esc>h') + command('write') + eq(0, request('nvim__stats').fsync) -- 'nofsync' is the default. + command('set swapfile') + command('set updatetime=1') + feed('izub<esc>h') -- File is 'modified'. + sleep(3) -- Allow 'updatetime' to expire. + retry(3, nil, function() + eq(1, request('nvim__stats').fsync) + end) + command('set updatetime=9999') + + -- 2. Exit caused by deadly signal (+ 'swapfile'). + local j = funcs.jobstart({ nvim_prog, '-u', 'NONE', '-i', + 'Xtest_startup_shada', '--headless', + '-c', 'set swapfile', + '-c', 'write Xtest_startup_file2', + '-c', 'put =localtime()', }) + sleep(10) -- Let Nvim start. + funcs.jobstop(j) -- Send deadly signal. + + -- 3. SIGPWR signal. + -- ?? + + -- 4. Explicit :preserve command. + command('preserve') + eq(2, request('nvim__stats').fsync) + + -- 5. Enable 'fsync' option, write file. + command('set fsync') + feed('ibaz<esc>h') + command('write') + eq(4, request('nvim__stats').fsync) + end) +end) + diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 6d4cadbdc8..e90339b0cd 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -19,14 +19,18 @@ local expect_twostreams = helpers.expect_twostreams local expect_msg_seq = helpers.expect_msg_seq local Screen = require('test.functional.ui.screen') +-- Kill process with given pid +local function os_kill(pid) + return os.execute((iswin() + and 'taskkill /f /t /pid '..pid..' > nul' + or 'kill -9 '..pid..' > /dev/null')) +end + describe('jobs', function() local channel before_each(function() clear() - if iswin() then - helpers.set_shell_powershell() - end channel = nvim('get_api_info')[1] nvim('set_var', 'channel', channel) source([[ @@ -52,7 +56,7 @@ describe('jobs', function() it('uses &shell and &shellcmdflag if passed a string', function() nvim('command', "let $VAR = 'abc'") if iswin() then - nvim('command', "let j = jobstart('echo $env:VAR', g:job_opts)") + nvim('command', "let j = jobstart('echo %VAR%', g:job_opts)") else nvim('command', "let j = jobstart('echo $VAR', g:job_opts)") end @@ -64,7 +68,7 @@ describe('jobs', function() it('changes to given / directory', function() nvim('command', "let g:job_opts.cwd = '/'") if iswin() then - nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") + nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end @@ -79,7 +83,7 @@ describe('jobs', function() mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then - nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") + nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end @@ -103,7 +107,7 @@ describe('jobs', function() local _, err = pcall(function() nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then - nvim('command', "let j = jobstart('pwd|%{$_.Path}', g:job_opts)") + nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end @@ -134,10 +138,8 @@ describe('jobs', function() end) it('invokes callbacks when the job writes and exits', function() - -- TODO: hangs on Windows - if helpers.pending_win32(pending) then return end nvim('command', "let g:job_opts.on_stderr = function('OnEvent')") - nvim('command', [[call jobstart('echo ""', g:job_opts)]]) + nvim('command', [[call jobstart(has('win32') ? 'echo:' : 'echo', g:job_opts)]]) expect_twostreams({{'notification', 'stdout', {0, {'', ''}}}, {'notification', 'stdout', {0, {''}}}}, {{'notification', 'stderr', {0, {''}}}}) @@ -145,15 +147,28 @@ describe('jobs', function() end) it('allows interactive commands', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") neq(0, eval('j')) nvim('command', 'call jobsend(j, "abc\\n")') eq({'notification', 'stdout', {0, {'abc', ''}}}, next_msg()) nvim('command', 'call jobsend(j, "123\\nxyz\\n")') - eq({'notification', 'stdout', {0, {'123', 'xyz', ''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {0, {'123', 'xyz', ''}}} + }, + -- Alternative sequence: + { {'notification', 'stdout', {0, {'123', ''}}}, + {'notification', 'stdout', {0, {'xyz', ''}}} + } + ) nvim('command', 'call jobsend(j, [123, "xyz", ""])') - eq({'notification', 'stdout', {0, {'123', 'xyz', ''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {0, {'123', 'xyz', ''}}} + }, + -- Alternative sequence: + { {'notification', 'stdout', {0, {'123', ''}}}, + {'notification', 'stdout', {0, {'xyz', ''}}} + } + ) nvim('command', "call jobstop(j)") eq({'notification', 'stdout', {0, {''}}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg()) @@ -226,16 +241,14 @@ describe('jobs', function() end) it('closes the job streams with jobclose', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") nvim('command', 'call jobclose(j, "stdin")') eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) + eq({'notification', 'exit', {0, iswin() and 1 or 0}}, next_msg()) end) it("disallows jobsend on a job that closed stdin", function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") nvim('command', 'call jobclose(j, "stdin")') eq(false, pcall(function() nvim('command', 'call jobsend(j, ["some data"])') @@ -248,8 +261,7 @@ describe('jobs', function() end) it('disallows jobstop twice on the same job', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") neq(0, eval('j')) eq(true, pcall(eval, "jobstop(j)")) eq(false, pcall(eval, "jobstop(j)")) @@ -260,41 +272,49 @@ describe('jobs', function() end) it('can get the pid value using getpid', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. - nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") local pid = eval('jobpid(j)') - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) nvim('command', 'call jobstop(j)') eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) - neq(0,os.execute('ps -p '..pid..' > /dev/null')) + if iswin() then + expect_msg_seq( + -- win64 + { {'notification', 'exit', {0, 1}} + }, + -- win32 + { {'notification', 'exit', {0, 15}} + } + ) + else + eq({'notification', 'exit', {0, 0}}, next_msg()) + end + eq(NIL, meths.get_proc(pid)) end) it("do not survive the exit of nvim", function() - if helpers.pending_win32(pending) then return end -- use sleep, which doesn't die on stdin close - nvim('command', "let g:j = jobstart(['sleep', '1000'], g:job_opts)") + nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") local pid = eval('jobpid(g:j)') - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) clear() - neq(0,os.execute('ps -p '..pid..' > /dev/null')) + eq(NIL, meths.get_proc(pid)) end) it('can survive the exit of nvim with "detach"', function() - if helpers.pending_win32(pending) then return end nvim('command', 'let g:job_opts.detach = 1') - nvim('command', "let g:j = jobstart(['sleep', '1000'], g:job_opts)") + nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") local pid = eval('jobpid(g:j)') - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) clear() - eq(0,os.execute('ps -p '..pid..' > /dev/null')) + neq(NIL, meths.get_proc(pid)) -- clean up after ourselves - os.execute('kill -9 '..pid..' > /dev/null') + eq(0, os_kill(pid)) end) it('can pass user data to the callback', function() nvim('command', 'let g:job_opts.user = {"n": 5, "s": "str", "l": [1]}') - nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) + nvim('command', [[call jobstart('echo foo', g:job_opts)]]) local data = {n = 5, s = 'str', l = {1}} expect_msg_seq( { {'notification', 'stdout', {data, {'foo', ''}}}, @@ -312,14 +332,14 @@ describe('jobs', function() it('can omit data callbacks', function() nvim('command', 'unlet g:job_opts.on_stdout') nvim('command', 'let g:job_opts.user = 5') - nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) + nvim('command', [[call jobstart('echo foo', g:job_opts)]]) eq({'notification', 'exit', {5, 0}}, next_msg()) end) it('can omit exit callback', function() nvim('command', 'unlet g:job_opts.on_exit') nvim('command', 'let g:job_opts.user = 5') - nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) + nvim('command', [[call jobstart('echo foo', g:job_opts)]]) expect_msg_seq( { {'notification', 'stdout', {5, {'foo', ''} } }, {'notification', 'stdout', {5, {''} } }, @@ -401,15 +421,14 @@ describe('jobs', function() end) it('does not repeat output with slow output handlers', function() - if helpers.pending_win32(pending) then return end source([[ let d = {'data': []} function! d.on_stdout(job, data, event) dict - call add(self.data, a:data) + call add(self.data, Normalize(a:data)) sleep 200m endfunction if has('win32') - let cmd = '1,2,3,4,5 | foreach-object -process {echo $_; sleep 0.1}' + let cmd = 'for /L %I in (1,1,5) do @(echo %I& ping -n 2 127.0.0.1 > nul)' else let cmd = ['sh', '-c', 'for i in $(seq 1 5); do echo $i; sleep 0.1; done'] endif @@ -428,7 +447,7 @@ describe('jobs', function() endfunction let Callback = function('PrintArgs', ["foo", "bar"]) let g:job_opts = {'on_stdout': Callback} - call jobstart('echo "some text"', g:job_opts) + call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, @@ -448,7 +467,7 @@ describe('jobs', function() return {id, data, event -> rpcnotify(g:channel, '1', a1, a2, Normalize(data), event)} endfun let g:job_opts = {'on_stdout': MkFun()} - call jobstart('echo "some text"', g:job_opts) + call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, @@ -463,7 +482,7 @@ describe('jobs', function() it('jobstart() works when closure passed directly to `jobstart`', function() source([[ let g:job_opts = {'on_stdout': {id, data, event -> rpcnotify(g:channel, '1', 'foo', 'bar', Normalize(data), event)}} - call jobstart('echo "some text"', g:job_opts) + call jobstart('echo some text', g:job_opts) ]]) expect_msg_seq( { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, @@ -476,9 +495,20 @@ describe('jobs', function() end) describe('jobwait', function() + before_each(function() + if iswin() then + helpers.set_shell_powershell() + end + end) + it('returns a list of status codes', function() source([[ - call rpcnotify(g:channel, 'wait', jobwait([ + call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 100; exit 4'), + \ jobstart('Start-Sleep -Milliseconds 300; exit 5'), + \ jobstart('Start-Sleep -Milliseconds 500; exit 6'), + \ jobstart('Start-Sleep -Milliseconds 700; exit 7') + \ ] : [ \ jobstart('sleep 0.10; exit 4'), \ jobstart('sleep 0.110; exit 5'), \ jobstart('sleep 0.210; exit 6'), @@ -498,7 +528,12 @@ describe('jobs', function() endif let g:exits += 1 endfunction - call jobwait([ + call jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 100; exit 5', g:dict), + \ jobstart('Start-Sleep -Milliseconds 300; exit 5', g:dict), + \ jobstart('Start-Sleep -Milliseconds 500; exit 5', g:dict), + \ jobstart('Start-Sleep -Milliseconds 700; exit 5', g:dict) + \ ] : [ \ jobstart('sleep 0.010; exit 5', g:dict), \ jobstart('sleep 0.030; exit 5', g:dict), \ jobstart('sleep 0.050; exit 5', g:dict), @@ -511,7 +546,12 @@ describe('jobs', function() it('will return status codes in the order of passed ids', function() source([[ - call rpcnotify(g:channel, 'wait', jobwait([ + call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 700; exit 4'), + \ jobstart('Start-Sleep -Milliseconds 500; exit 5'), + \ jobstart('Start-Sleep -Milliseconds 300; exit 6'), + \ jobstart('Start-Sleep -Milliseconds 100; exit 7') + \ ] : [ \ jobstart('sleep 0.070; exit 4'), \ jobstart('sleep 0.050; exit 5'), \ jobstart('sleep 0.030; exit 6'), @@ -525,7 +565,7 @@ describe('jobs', function() source([[ call rpcnotify(g:channel, 'wait', jobwait([ \ -10, - \ jobstart('sleep 0.01; exit 5'), + \ jobstart((has('win32') ? 'Start-Sleep -Milliseconds 100' : 'sleep 0.01').'; exit 5'), \ ])) ]]) eq({'notification', 'wait', {{-3, 5}}}, next_msg()) @@ -534,7 +574,9 @@ describe('jobs', function() it('will return -2 when interrupted without timeout', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. - 'jobwait([jobstart("sleep 10; exit 55")]))') + 'jobwait([jobstart("'.. + (iswin() and 'Start-Sleep 10' or 'sleep 10').. + '; exit 55")]))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') eq({'notification', 'wait', {{-2}}}, next_msg()) @@ -543,7 +585,9 @@ describe('jobs', function() it('will return -2 when interrupted with timeout', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. - 'jobwait([jobstart("sleep 10; exit 55")], 10000))') + 'jobwait([jobstart("'.. + (iswin() and 'Start-Sleep 10' or 'sleep 10').. + '; exit 55")], 10000))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') eq({'notification', 'wait', {{-2}}}, next_msg()) @@ -598,20 +642,22 @@ describe('jobs', function() end) describe('with timeout argument', function() - if helpers.pending_win32(pending) then return end it('will return -1 if the wait timed out', function() source([[ call rpcnotify(g:channel, 'wait', jobwait([ \ jobstart('exit 4'), - \ jobstart('sleep 10; exit 5'), - \ ], 100)) + \ jobstart((has('win32') ? 'Start-Sleep 10' : 'sleep 10').'; exit 5'), + \ ], has('win32') ? 3000 : 100)) ]]) eq({'notification', 'wait', {{4, -1}}}, next_msg()) end) it('can pass 0 to check if a job exists', function() source([[ - call rpcnotify(g:channel, 'wait', jobwait([ + call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [ + \ jobstart('Start-Sleep -Milliseconds 50; exit 4'), + \ jobstart('Start-Sleep -Milliseconds 300; exit 5'), + \ ] : [ \ jobstart('sleep 0.05; exit 4'), \ jobstart('sleep 0.3; exit 5'), \ ], 0)) @@ -633,27 +679,38 @@ describe('jobs', function() end) it('cannot have both rpc and pty options', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. command("let g:job_opts.pty = v:true") command("let g:job_opts.rpc = v:true") - local _, err = pcall(command, "let j = jobstart(['cat', '-'], g:job_opts)") + local _, err = pcall(command, "let j = jobstart(has('win32') ? ['find', '/v', ''] : ['cat', '-'], g:job_opts)") ok(string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") ~= nil) end) - it('jobstop() kills entire process tree #6530', function() - command('set shell& shellcmdflag& shellquote& shellpipe& shellredir& shellxquote&') + it('does not crash when repeatedly failing to start shell', function() + source([[ + set shell=nosuchshell + func! DoIt() + call jobstart('true') + call jobstart('true') + endfunc + ]]) + -- The crash only triggered if both jobs are cleaned up on the same event + -- loop tick. This is also prevented by try-block, so feed must be used. + feed_command("call DoIt()") + feed('<cr>') -- press RETURN + eq(2,eval('1+1')) + end) + it('jobstop() kills entire process tree #6530', function() -- XXX: Using `nvim` isn't a good test, it reaps its children on exit. -- local c = 'call jobstart([v:progpath, "-u", "NONE", "-i", "NONE", "--headless"])' -- local j = eval("jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '--headless', '-c', '" -- ..c.."', '-c', '"..c.."'])") -- Create child with several descendants. - local j = (iswin() - and eval([=[jobstart('start /b cmd /c "ping 127.0.0.1 -n 1 -w 30000 > NUL"]=] - ..[=[ & start /b cmd /c "ping 127.0.0.1 -n 1 -w 40000 > NUL"]=] - ..[=[ & start /b cmd /c "ping 127.0.0.1 -n 1 -w 50000 > NUL"')]=]) - or eval("jobstart('sleep 30 | sleep 30 | sleep 30')")) + local sleep_cmd = (iswin() + and 'ping -n 31 127.0.0.1' + or 'sleep 30') + local j = eval("jobstart('"..sleep_cmd..' | '..sleep_cmd..' | '..sleep_cmd.."')") local ppid = funcs.jobpid(j) local children retry(nil, nil, function() diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua new file mode 100644 index 0000000000..cd396ef820 --- /dev/null +++ b/test/functional/core/main_spec.lua @@ -0,0 +1,131 @@ +local lfs = require('lfs') +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local feed = helpers.feed +local eval = helpers.eval +local clear = helpers.clear +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local write_file = helpers.write_file + +local function nvim_prog_abs() + -- system(['build/bin/nvim']) does not work for whatever reason. It needs to + -- either be executable searched in $PATH or something starting with / or ./. + if nvim_prog:match('[/\\]') then + return funcs.fnamemodify(nvim_prog, ':p') + else + return nvim_prog + end +end + +describe('Command-line option', function() + describe('-s', function() + local fname = 'Xtest-functional-core-main-s' + local fname_2 = fname .. '.2' + local nonexistent_fname = fname .. '.nonexistent' + local dollar_fname = '$' .. fname + before_each(function() + clear() + os.remove(fname) + os.remove(dollar_fname) + end) + after_each(function() + os.remove(fname) + os.remove(dollar_fname) + end) + it('treats - as stdin', function() + eq(nil, lfs.attributes(fname)) + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', '-', fname}, + {':call setline(1, "42")', ':wqall!', ''}) + eq(0, eval('v:shell_error')) + local attrs = lfs.attributes(fname) + eq(#('42\n'), attrs.size) + end) + it('does not expand $VAR', function() + eq(nil, lfs.attributes(fname)) + eq(true, not not dollar_fname:find('%$%w+')) + write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n') + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', dollar_fname, fname}) + eq(0, eval('v:shell_error')) + local attrs = lfs.attributes(fname) + eq(#('100500\n'), attrs.size) + end) + it('does not crash after reading from stdin in non-headless mode', function() + if helpers.pending_win32(pending) then return end + local screen = Screen.new(40, 8) + screen:attach() + funcs.termopen({ + nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', '-' + }) + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] 0,0-1 All}| + | + | + ]], { + [1] = {foreground = 4210943, special = Screen.colors.Grey0}, + [2] = {special = Screen.colors.Grey0, bold = true, reverse = true} + }) + feed('i:cq<CR>') + screen:expect([[ + | + [Process exited 1] | + | + | + | + | + | + -- TERMINAL -- | + ]]) + --[=[ Example of incorrect output: + screen:expect([[ + ^nvim: /var/tmp/portage/dev-libs/libuv-1.| + 10.2/work/libuv-1.10.2/src/unix/core.c:5| + 19: uv__close: Assertion `fd > STDERR_FI| + LENO' failed. | + | + [Process exited 6] | + | + | + ]]) + ]=] + end) + it('errors out when trying to use nonexistent file with -s', function() + eq( + 'Cannot open for reading: "'..nonexistent_fname..'": no such file or directory\n', + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', 'language C', + '-s', nonexistent_fname})) + eq(2, eval('v:shell_error')) + end) + it('errors out when trying to use -s twice', function() + write_file(fname, ':call setline(1, "1")\n:wqall!\n') + write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n') + eq( + 'Attempt to open script file again: "-s '..dollar_fname..'"\n', + funcs.system( + {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', 'language C', + '-s', fname, '-s', dollar_fname, fname_2})) + eq(2, eval('v:shell_error')) + eq(nil, lfs.attributes(fname_2)) + end) + end) +end) diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua index 393616838e..4e4aed864b 100644 --- a/test/functional/eval/server_spec.lua +++ b/test/functional/eval/server_spec.lua @@ -1,31 +1,40 @@ - local helpers = require('test.functional.helpers')(after_each) local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local command = helpers.command local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths -local os_name = helpers.os_name +local iswin = helpers.iswin +local ok = helpers.ok +local matches = helpers.matches local function clear_serverlist() - for _, server in pairs(funcs.serverlist()) do - funcs.serverstop(server) - end + for _, server in pairs(funcs.serverlist()) do + funcs.serverstop(server) + end end -describe('serverstart(), serverstop()', function() +describe('server', function() before_each(clear) - it('sets $NVIM_LISTEN_ADDRESS on first invocation', function() + it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function() -- Unset $NVIM_LISTEN_ADDRESS command('let $NVIM_LISTEN_ADDRESS = ""') local s = eval('serverstart()') assert(s ~= nil and s:len() > 0, "serverstart() returned empty") eq(s, eval('$NVIM_LISTEN_ADDRESS')) - command("call serverstop('"..s.."')") + eq(1, eval("serverstop('"..s.."')")) eq('', eval('$NVIM_LISTEN_ADDRESS')) end) - it('sets v:servername _only_ on nvim startup unless all servers are stopped', + it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() + clear({env={NVIM_LISTEN_ADDRESS='.'}}) + eq('.', eval('$NVIM_LISTEN_ADDRESS')) + local servers = funcs.serverlist() + eq(1, #servers) + ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… + end) + + it('sets v:servername at startup or if all servers were stopped', function() local initial_server = meths.get_vvar('servername') assert(initial_server ~= nil and initial_server:len() > 0, @@ -38,24 +47,23 @@ describe('serverstart(), serverstop()', function() neq(initial_server, s) -- serverstop() does _not_ modify v:servername... - funcs.serverstop(s) + eq(1, funcs.serverstop(s)) eq(initial_server, meths.get_vvar('servername')) -- ...unless we stop _all_ servers. - funcs.serverstop(funcs.serverlist()[1]) + eq(1, funcs.serverstop(funcs.serverlist()[1])) eq('', meths.get_vvar('servername')) -- v:servername will take the next available server. - local servername = (os_name() == 'windows' - and [[\\.\pipe\Xtest-functional-server-pipe]] - or 'Xtest-functional-server-socket') + local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] + or 'Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) end) - it('serverstop() ignores invalid input', function() - command("call serverstop('')") - command("call serverstop('bogus-socket-name')") + it('serverstop() returns false for invalid input', function() + eq(0, eval("serverstop('')")) + eq(0, eval("serverstop('bogus-socket-name')")) end) it('parses endpoints correctly', function() @@ -96,17 +104,13 @@ describe('serverstart(), serverstop()', function() funcs.serverstart('127.0.0.1:65536') -- invalid port eq({}, funcs.serverlist()) end) -end) - -describe('serverlist()', function() - before_each(clear) - it('returns the list of servers', function() + it('serverlist() returns the list of servers', function() -- There should already be at least one server. local n = eval('len(serverlist())') - -- Add a few - local servs = (os_name() == 'windows' + -- Add some servers. + local servs = (iswin() and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] }) for _, s in ipairs(servs) do @@ -120,9 +124,31 @@ describe('serverlist()', function() -- The new servers should be at the end of the list. for i = 1, #servs do eq(servs[i], new_servs[i + n]) - command("call serverstop('"..servs[i].."')") + eq(1, eval("serverstop('"..servs[i].."')")) end -- After serverstop() the servers should NOT be in the list. eq(n, eval('len(serverlist())')) end) end) + +describe('startup --listen', function() + it('validates', function() + clear() + + local cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen') + matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd)) + + cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen2') + matches('nvim.*: Garbage after option argument: "%-%-listen2"', funcs.system(cmd)) + end) + + it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() + local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] + or 'Xtest-listen-pipe') + clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' }, + args={ '--listen', addr } }) + eq(addr, meths.get_vvar('servername')) + end) +end) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 201426c40b..23cea4c038 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -255,10 +255,8 @@ describe('system()', function() end end) it('to backgrounded command does not crash', function() - -- cmd.exe doesn't background a command with & - if iswin() then return end -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("echo -n echoed &")') + feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "echo -n echoed &")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) @@ -272,10 +270,8 @@ describe('system()', function() eq("input", eval('system("cat -", "input")')) end) it('to backgrounded command does not crash', function() - -- cmd.exe doesn't background a command with & - if iswin() then return end -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("cat - &", "input")') + feed_command('call system(has("win32") ? "start /b /wait more" : "cat - &", "input")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index b8d912114d..655200e6f7 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -14,8 +14,10 @@ local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs local neq = global_helpers.neq local eq = global_helpers.eq +local expect_err = global_helpers.expect_err local ok = global_helpers.ok local map = global_helpers.map +local matches = global_helpers.matches local filter = global_helpers.filter local dedent = global_helpers.dedent local table_flatten = global_helpers.table_flatten @@ -304,12 +306,10 @@ local function retry(max, max_ms, fn) end luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). if (max and tries >= max) or (luv.now() - start_time > timeout) then - if type(result) == "string" then - result = "\nretry() attempts: "..tostring(tries).."\n"..result - end - error(result) + error("\nretry() attempts: "..tostring(tries).."\n"..tostring(result)) end tries = tries + 1 + luv.sleep(20) -- Avoid hot loop... end end @@ -425,7 +425,7 @@ end local function set_shell_powershell() source([[ set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote= - set shellcmdflag=-NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command + let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep;' ]]) end @@ -468,14 +468,7 @@ end -- sleeps the test runner (_not_ the nvim instance) local function sleep(ms) - local function notification_cb(method, _) - if method == "redraw" then - error("Screen is attached; use screen:sleep() instead.") - end - return true - end - - run(nil, notification_cb, nil, ms) + luv.sleep(ms) end local function curbuf_contents() @@ -736,6 +729,7 @@ local module = { exc_exec = exc_exec, expect = expect, expect_any = expect_any, + expect_err = expect_err, expect_msg_seq = expect_msg_seq, expect_twostreams = expect_twostreams, feed = feed, @@ -747,6 +741,7 @@ local module = { insert = insert, iswin = iswin, map = map, + matches = matches, merge_args = merge_args, meth_pcall = meth_pcall, meths = meths, diff --git a/test/functional/insert/insert_spec.lua b/test/functional/insert/insert_spec.lua new file mode 100644 index 0000000000..427954f5a6 --- /dev/null +++ b/test/functional/insert/insert_spec.lua @@ -0,0 +1,41 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local funcs = helpers.funcs + +describe('insert-mode', function() + before_each(function() + clear() + end) + + it('CTRL-@', function() + -- Inserts last-inserted text, leaves insert-mode. + insert('hello') + feed('i<C-@>x') + expect('hellhello') + + -- C-Space is the same as C-@. + -- CTRL-SPC inserts last-inserted text, leaves insert-mode. + feed('i<C-Space>x') + expect('hellhellhello') + + -- CTRL-A inserts last inserted text + feed('i<C-A>x') + expect('hellhellhellhelloxo') + end) + + it('ALT/META #8213', function() + -- Mapped ALT-chord behaves as mapped. + command('inoremap <M-l> meta-l') + command('inoremap <A-j> alt-j') + feed('i<M-l> xxx <A-j><M-h>a<A-h>') + expect('meta-l xxx alt-j') + eq({ 0, 1, 14, 0, }, funcs.getpos('.')) + -- Unmapped ALT-chord behaves as ESC+c. + command('iunmap <M-l>') + feed('0i<M-l>') + eq({ 0, 1, 2, 0, }, funcs.getpos('.')) + end) +end) diff --git a/test/functional/insert/last_inserted_spec.lua b/test/functional/insert/last_inserted_spec.lua deleted file mode 100644 index dce23a3790..0000000000 --- a/test/functional/insert/last_inserted_spec.lua +++ /dev/null @@ -1,22 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local expect = helpers.expect - -clear() - -describe('insert-mode', function() - it('CTRL-@ inserts last-inserted text, leaves insert-mode', function() - insert('hello') - feed('i<C-@>x') - expect('hellhello') - end) - -- C-Space is the same as C-@ - it('CTRL-SPC inserts last-inserted text, leaves insert-mode', function() - feed('i<C-Space>x') - expect('hellhellhello') - end) - it('CTRL-A inserts last inserted text', function() - feed('i<C-A>x') - expect('hellhellhellhelloxo') - end) -end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 9e29baba2d..f452cafd22 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -5,12 +5,15 @@ local Screen = require('test.functional.ui.screen') local meths = helpers.meths local command = helpers.command local clear = helpers.clear +local exc_exec = helpers.exc_exec local eval = helpers.eval local eq = helpers.eq +local funcs = helpers.funcs local insert = helpers.insert local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir +local alter_slashes = helpers.alter_slashes describe('startup defaults', function() describe(':filetype', function() @@ -422,3 +425,276 @@ describe('XDG-based defaults', function() end) end) end) + + +describe('stdpath()', function() + context('returns a String', function() + describe('with "config"' , function () + it('knows XDG_CONFIG_HOME', function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('/home/docwhat/.config'), + }}) + eq(alter_slashes('/home/docwhat/.config/nvim'), funcs.stdpath('config')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/nvim'), funcs.stdpath('config')) + command("let $XDG_CONFIG_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/nvim'), funcs.stdpath('config')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_CONFIG_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('config')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('config')) + end) + end) + + describe('with "data"' , function () + local appended_dir + setup(function() + -- Windows appends 'nvim-data' instead of just 'nvim' to + -- prevent collisions due to XDG_CONFIG_HOME and XDG_DATA_HOME + -- being the same. + if helpers.iswin() then + appended_dir = '/nvim-data' + else + appended_dir = '/nvim' + end + end) + + it('knows XDG_DATA_HOME', function() + clear({env={ + XDG_DATA_HOME=alter_slashes('/home/docwhat/.local'), + }}) + eq(alter_slashes('/home/docwhat/.local' .. appended_dir), funcs.stdpath('data')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_DATA_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original' .. appended_dir), funcs.stdpath('data')) + command("let $XDG_DATA_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new' .. appended_dir), funcs.stdpath('data')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_DATA_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES' .. appended_dir), funcs.stdpath('data')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_DATA_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz' .. appended_dir), funcs.stdpath('data')) + end) + end) + + describe('with "cache"' , function () + it('knows XDG_CACHE_HOME', function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('/home/docwhat/.cache'), + }}) + eq(alter_slashes('/home/docwhat/.cache/nvim'), funcs.stdpath('cache')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/nvim'), funcs.stdpath('cache')) + command("let $XDG_CACHE_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/nvim'), funcs.stdpath('cache')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_CACHE_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('cache')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('cache')) + end) + end) + end) + + context('returns a List', function() + -- Some OS specific variables the system would have set. + local function base_env() + if helpers.iswin() then + return { + HOME='C:\\Users\\docwhat', -- technically, is not a usual PATH + HOMEDRIVE='C:', + HOMEPATH='\\Users\\docwhat', + LOCALAPPDATA='C:\\Users\\docwhat\\AppData\\Local', + TEMP='C:\\Users\\docwhat\\AppData\\Local\\Temp', + TMPDIR='C:\\Users\\docwhat\\AppData\\Local\\Temp', + TMP='C:\\Users\\docwhat\\AppData\\Local\\Temp', + } + else + return { + HOME='/home/docwhat', + HOMEDRIVE='HOMEDRIVE-should-be-ignored', + HOMEPATH='HOMEPATH-should-be-ignored', + LOCALAPPDATA='LOCALAPPDATA-should-be-ignored', + TEMP='TEMP-should-be-ignored', + TMPDIR='TMPDIR-should-be-ignored', + TMP='TMP-should-be-ignored', + } + end + end + + local function set_paths_via_system(var_name, paths) + local env = base_env() + env[var_name] = table.concat(paths, ':') + clear({env=env}) + end + + local function set_paths_at_runtime(var_name, paths) + clear({env=base_env()}) + meths.set_var('env_val', table.concat(paths, ':')) + command(('let $%s=g:env_val'):format(var_name)) + end + + local function behaves_like_dir_list_env(msg, stdpath_arg, env_var_name, paths, expected_paths) + describe(msg, function() + it('set via system', function() + set_paths_via_system(env_var_name, paths) + eq(expected_paths, funcs.stdpath(stdpath_arg)) + end) + + it('set at runtime', function() + set_paths_at_runtime(env_var_name, paths) + eq(expected_paths, funcs.stdpath(stdpath_arg)) + end) + end) + end + + describe('with "config_dirs"' , function () + behaves_like_dir_list_env( + 'handles XDG_CONFIG_DIRS with one path', + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('/home/docwhat/.config') + }, + { + alter_slashes('/home/docwhat/.config/nvim') + }) + + behaves_like_dir_list_env( + 'handles XDG_CONFIG_DIRS with two paths', + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('/home/docwhat/.config'), + alter_slashes('/etc/config') + }, + { + alter_slashes('/home/docwhat/.config/nvim'), + alter_slashes('/etc/config/nvim') + }) + + behaves_like_dir_list_env( + "doesn't expand $VAR and $IBLES", + 'config_dirs', 'XDG_CONFIG_DIRS', + { '$HOME', '$TMP' }, + { + alter_slashes('$HOME/nvim'), + alter_slashes('$TMP/nvim') + }) + + + behaves_like_dir_list_env( + "doesn't expand ~/", + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('~/.oldconfig'), + alter_slashes('~/.olderconfig') + }, + { + alter_slashes('~/.oldconfig/nvim'), + alter_slashes('~/.olderconfig/nvim') + }) + end) + + describe('with "data_dirs"' , function () + behaves_like_dir_list_env( + 'knows XDG_DATA_DIRS with one path', + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('/home/docwhat/.data') + }, + { + alter_slashes('/home/docwhat/.data/nvim') + }) + + behaves_like_dir_list_env( + 'knows XDG_DATA_DIRS with two paths', + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('/home/docwhat/.data'), + alter_slashes('/etc/local') + }, + { + alter_slashes('/home/docwhat/.data/nvim'), + alter_slashes('/etc/local/nvim'), + }) + + behaves_like_dir_list_env( + "doesn't expand $VAR and $IBLES", + 'data_dirs', 'XDG_DATA_DIRS', + { '$HOME', '$TMP' }, + { + alter_slashes('$HOME/nvim'), + alter_slashes('$TMP/nvim') + }) + + behaves_like_dir_list_env( + "doesn't expand ~/", + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('~/.oldconfig'), + alter_slashes('~/.olderconfig') + }, + { + alter_slashes('~/.oldconfig/nvim'), + alter_slashes('~/.olderconfig/nvim'), + }) + end) + end) + + describe('errors', function() + it('on unknown strings', function() + eq('Vim(call):E6100: "capybara" is not a valid stdpath', exc_exec('call stdpath("capybara")')) + eq('Vim(call):E6100: "" is not a valid stdpath', exc_exec('call stdpath("")')) + eq('Vim(call):E6100: "23" is not a valid stdpath', exc_exec('call stdpath(23)')) + end) + + it('on non-strings', function() + eq('Vim(call):E731: using Dictionary as a String', exc_exec('call stdpath({"eris": 23})')) + eq('Vim(call):E730: using List as a String', exc_exec('call stdpath([23])')) + end) + end) +end) diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua index 0a12b1a154..d9af020bfe 100644 --- a/test/functional/provider/nodejs_spec.lua +++ b/test/functional/provider/nodejs_spec.lua @@ -35,7 +35,7 @@ describe('nodejs host', function() nvim.command('call jobstop(g:job_id)'); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') - retry(nil, 1000, function() eq('hello', eval('g:job_out')) end) + retry(nil, 2000, function() eq('hello', eval('g:job_out')) end) end) it('plugin works', function() local fname = 'Xtest-nodejs-hello-plugin.js' @@ -56,6 +56,6 @@ describe('nodejs host', function() nvim.command('call jobstop(g:job_id)'); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') - retry(nil, 1000, function() eq('hello-plugin', eval('g:job_out')) end) + retry(nil, 2000, function() eq('hello-plugin', eval('g:job_out')) end) end) end) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 7a15c8908b..a628baff53 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -525,6 +525,85 @@ describe('ShaDa marks support code', function() eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) + it('can merge with file with mark 9 as the only numeric mark', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `9oabc') + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 + end + end + eq({['0']=1, ['1']=1}, found) + end) + + it('removes duplicates while merging', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9' + .. '\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + print(require('test.helpers').format_luav(v)) + found = found + 1 + end + end + eq(1, found) + end) + + it('does not leak when no append is performed due to too many marks', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}, found) + end) + + it('does not leak when last mark in file removes some of the earlier ones', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\003\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'}, found) + end) + it('uses last A mark with gt timestamp from file when reading with !', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') @@ -563,13 +642,14 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '-' then - found = found + 1 + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) it('uses last A mark with eq timestamp from instance when writing', @@ -580,30 +660,33 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do if v.type == 7 and v.value.f == mock_file_path .. '-' then - found = found + 1 + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) - it('uses last A mark with gt timestamp from file when writing', - function() + it('uses last A mark with gt timestamp from file when writing', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '?' then - found = found + 1 + if v.type == 7 then + local name = ('%c'):format(v.value.n) + local t = found[name] or {} + t[v.value.f] = (t[v.value.f] or 0) + 1 + found[name] = t end end - eq(1, found) + eq({['0']={[mock_file_path .. '-']=1}, A={[mock_file_path .. '?']=1}}, found) end) it('uses last a mark with gt timestamp from instance when reading', diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index ca44026852..720855860a 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -181,13 +181,13 @@ describe('ShaDa support code', function() nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) local readme_fname = funcs.resolve(paths.test_source_path) .. '/README.md' - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r~') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(readme_fname)) nvim_command('set shada-=r~') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r' .. funcs.escape( funcs.escape(paths.test_source_path, '$~'), ' "\\,')) nvim_command('wshada! ' .. shada_fname) @@ -206,7 +206,7 @@ describe('ShaDa support code', function() nvim_command('undo') nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) nvim_command('set shada+=r' .. pwd .. '/АБВ') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(fname)) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index a21d9f0a56..263a5ce79d 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -101,7 +101,7 @@ describe('terminal mouse', function() line28 │line28 | line29 │line29 | line30 │line30 | - rows: 5, cols: 24 │rows: 5, cols: 24 | + rows: 5, cols: 25 │rows: 5, cols: 25 | {2:^ } │{2: } | ========== ========== | :vsp | @@ -111,7 +111,7 @@ describe('terminal mouse', function() {7: 1 }^ │line28 | {4:~ }│line29 | {4:~ }│line30 | - {4:~ }│rows: 5, cols: 24 | + {4:~ }│rows: 5, cols: 25 | {4:~ }│{2: } | ========== ========== | :enew | set number | @@ -121,7 +121,7 @@ describe('terminal mouse', function() {7: 27 }line │line28 | {7: 28 }line │line29 | {7: 29 }line │line30 | - {7: 30 }line │rows: 5, cols: 24 | + {7: 30 }line │rows: 5, cols: 25 | {7: 31 }^ │{2: } | ========== ========== | | @@ -131,7 +131,7 @@ describe('terminal mouse', function() {7: 27 }line │line28 | {7: 28 }line │line29 | {7: 29 }line │line30 | - {7: 30 }line │rows: 5, cols: 24 | + {7: 30 }line │rows: 5, cols: 25 | {7: 31 } │{1: } | ========== ========== | {3:-- TERMINAL --} | @@ -142,7 +142,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 27 }line │line29 | {7: 28 }line │line30 | - {7: 29 }line │rows: 5, cols: 24 | + {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │mouse enabled | {7: 31 } │{1: } | ========== ========== | @@ -155,7 +155,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 21 }line │line29 | {7: 22 }line │line30 | - {7: 23 }line │rows: 5, cols: 24 | + {7: 23 }line │rows: 5, cols: 25 | {7: 24 }line │mouse enabled | {7: 25 }line │{1: } | ========== ========== | @@ -165,7 +165,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 26 }line │line29 | {7: 27 }line │line30 | - {7: 28 }line │rows: 5, cols: 24 | + {7: 28 }line │rows: 5, cols: 25 | {7: 29 }line │mouse enabled | {7: 30 }line │{1: } | ========== ========== | @@ -178,7 +178,7 @@ describe('terminal mouse', function() screen:expect([[ {7: 27 }line │line29 | {7: 28 }l^ine │line30 | - {7: 29 }line │rows: 5, cols: 24 | + {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │mouse enabled | {7: 31 } │{2: } | ========== ========== | diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 171745eb57..0ae5802a01 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -115,16 +115,12 @@ describe('tui', function() ]]) end) - it('does not mangle unmapped ALT-key chord', function() - -- Vim represents ALT/META by setting the "high bit" of the modified key; - -- we do _not_. #3982 - -- - -- Example: for input ALT+j: - -- * Vim (Nvim prior to #3982) sets high-bit, inserts "ê". - -- * Nvim (after #3982) inserts "j". - feed_data('i\027j') + it('interprets ESC+key as ALT chord', function() + -- Vim represents ALT/META by setting the "high bit" of the modified key: + -- ALT+j inserts "ê". Nvim does not (#3982). + feed_data('i\022\027j') screen:expect([[ - j{1: } | + <M-j>{1: } | {4:~ }| {4:~ }| {4:~ }| @@ -391,15 +387,7 @@ describe('tui FocusGained/FocusLost', function() -- Exit cmdline-mode. Redraws from timers/events are blocked during -- cmdline-mode, so the buffer won't be updated until we exit cmdline-mode. feed_data('\n') - screen:expect([[ - {1: } | - lost | - gained | - {4:~ }| - {5:[No Name] [+] }| - : | - {3:-- TERMINAL --} | - ]]) + screen:expect('lost'..(' '):rep(46)..'\ngained', nil, nil, nil, true) end) end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 364e0eea7f..ab3b1c3cac 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -778,6 +778,9 @@ describe("'winhighlight' highlight", function() [22] = {bold = true, foreground = Screen.colors.SeaGreen4}, [23] = {background = Screen.colors.LightMagenta}, [24] = {background = Screen.colors.WebGray}, + [25] = {bold = true, foreground = Screen.colors.Green1}, + [26] = {background = Screen.colors.Red}, + [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, }) command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") @@ -1047,6 +1050,39 @@ describe("'winhighlight' highlight", function() ]]) end) + it("background doesn't override syntax background", function() + command('syntax on') + command('syntax keyword Foobar foobar') + command('syntax keyword Article the') + command('hi Foobar guibg=#FF0000') + command('hi Article guifg=#00FF00 gui=bold') + insert('the foobar was foobar') + screen:expect([[ + {25:the} {26:foobar} was {26:fooba}| + {26:^r} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + -- winhl=Normal:Group with background doesn't override syntax background, + -- but does combine with syntax foreground. + command('set winhl=Normal:Background1') + screen:expect([[ + {27:the}{1: }{26:foobar}{1: was }{26:fooba}| + {26:^r}{1: }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + it('can override NonText, Conceal and EndOfBuffer', function() curbufmeths.set_lines(0,-1,true, {"raa\000"}) command('call matchaddpos("Conceal", [[1,2]], 0, -1, {"conceal": "#"})') diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 27e4066d9f..ee1a3240a2 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -16,6 +16,8 @@ local retry = helpers.retry local source = helpers.source local wait = helpers.wait local nvim = helpers.nvim +local iswin = helpers.iswin +local sleep = helpers.sleep local default_text = [[ Inc substitution on @@ -1354,6 +1356,23 @@ describe("inccommand=nosplit", function() :echo 'foo'^ | ]]) end) + + it("does not execute trailing bar-separated commands #7494", function() + feed(':%s/two/three/g|q!') + screen:expect([[ + Inc substitution on | + {12:three} lines | + Inc substitution on | + {12:three} lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/two/three/g|q!^ | + ]]) + eq(eval('v:null'), eval('v:exiting')) + end) end) describe(":substitute, 'inccommand' with a failing expression", function() @@ -1833,7 +1852,7 @@ describe(":substitute", function() clear() end) - it(", inccommand=split, highlights multiline substitutions", function() + it("inccommand=split, highlights multiline substitutions", function() common_setup(screen, "split", multiline_text) feed("gg") @@ -1895,7 +1914,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, highlights multiline substitutions", function() + it("inccommand=nosplit, highlights multiline substitutions", function() common_setup(screen, "nosplit", multiline_text) feed("gg") @@ -1938,7 +1957,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, highlights multiple matches on a line", function() + it("inccommand=split, highlights multiple matches on a line", function() common_setup(screen, "split", multimatch_text) command("set gdefault") feed("gg") @@ -1963,7 +1982,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, highlights multiple matches on a line", function() + it("inccommand=nosplit, highlights multiple matches on a line", function() common_setup(screen, "nosplit", multimatch_text) command("set gdefault") feed("gg") @@ -1988,7 +2007,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, with \\zs", function() + it("inccommand=split, with \\zs", function() common_setup(screen, "split", multiline_text) feed("gg") @@ -2012,7 +2031,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, with \\zs", function() + it("inccommand=nosplit, with \\zs", function() common_setup(screen, "nosplit", multiline_text) feed("gg") @@ -2036,7 +2055,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, substitutions of different length", + it("inccommand=split, substitutions of different length", function() common_setup(screen, "split", "T T123 T2T TTT T090804\nx") @@ -2060,7 +2079,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, substitutions of different length", function() + it("inccommand=nosplit, substitutions of different length", function() common_setup(screen, "nosplit", "T T123 T2T TTT T090804\nx") feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g") @@ -2083,7 +2102,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, contraction of lines", function() + it("inccommand=split, contraction of lines", function() local text = [[ T T123 T T123 T2T TT T23423424 x @@ -2132,7 +2151,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, contraction of lines", function() + it("inccommand=nosplit, contraction of lines", function() local text = [[ T T123 T T123 T2T TT T23423424 x @@ -2162,7 +2181,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, multibyte text", function() + it("inccommand=split, multibyte text", function() common_setup(screen, "split", multibyte_text) feed(":%s/£.*ѫ/X¥¥") screen:expect([[ @@ -2203,7 +2222,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=nosplit, multibyte text", function() + it("inccommand=nosplit, multibyte text", function() common_setup(screen, "nosplit", multibyte_text) feed(":%s/£.*ѫ/X¥¥") screen:expect([[ @@ -2244,7 +2263,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, small cmdwinheight", function() + it("inccommand=split, small cmdwinheight", function() common_setup(screen, "split", long_multiline_text) command("set cmdwinheight=2") @@ -2306,7 +2325,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, large cmdwinheight", function() + it("inccommand=split, large cmdwinheight", function() common_setup(screen, "split", long_multiline_text) command("set cmdwinheight=11") @@ -2368,7 +2387,7 @@ describe(":substitute", function() ]]) end) - it(", inccommand=split, lookaround", function() + it("inccommand=split, lookaround", function() common_setup(screen, "split", "something\neverything\nsomeone") feed([[:%s/\(some\)\@<lt>=thing/one/]]) screen:expect([[ @@ -2452,4 +2471,57 @@ describe(":substitute", function() :%s/some\(thing\)\@!/every/^ | ]]) end) + + it('with inccommand during :terminal activity', function() + command("set cmdwinheight=3") + if iswin() then + feed([[:terminal for /L \%I in (1,1,5000) do @(echo xxx & echo xxx & echo xxx)<cr>]]) + else + feed([[:terminal for i in $(seq 1 5000); do printf 'xxx\nxxx\nxxx\n'; done<cr>]]) + end + command('file term') + command('new') + common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz') + command('wincmd =') + + -- Allow some terminal output. + screen:expect([[ + bar baz fox | + bar foo ba^z | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + xxx | + xxx | + xxx | + xxx | + xxx | + xxx | + {10:term }| + | + ]]) + + feed('gg') + feed(':%s/foo/ZZZ') + sleep(50) -- Allow some terminal activity. + screen:expect([[ + {12:ZZZ} bar baz | + bar baz fox | + bar {12:ZZZ} baz | + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + xxx | + xxx | + {10:term }| + |1| {12:ZZZ} bar baz | + |3| bar {12:ZZZ} baz | + {15:~ }| + {10:[Preview] }| + :%s/foo/ZZZ^ | + ]]) + end) end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 9bfc87d835..3dd9a2506e 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -128,6 +128,10 @@ describe('input utf sequences that contain CSI/K_SPECIAL', function() end) describe('input non-printable chars', function() + after_each(function() + os.remove('Xtest-overwrite') + end) + it("doesn't crash when echoing them back", function() write_file("Xtest-overwrite", [[foobar]]) clear() diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 563d04416a..7fafd6b352 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -76,11 +76,26 @@ describe('Screen', function() local function check() eq(true, screen.suspended) end + + command('let g:ev = []') + command('autocmd VimResume * :call add(g:ev, "r")') + command('autocmd VimSuspend * :call add(g:ev, "s")') + + eq(false, screen.suspended) command('suspend') + eq({ 's', 'r' }, eval('g:ev')) + screen:expect(check) screen.suspended = false + feed('<c-z>') + eq({ 's', 'r', 's', 'r' }, eval('g:ev')) + screen:expect(check) + screen.suspended = false + + command('suspend') + eq({ 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev')) end) end) @@ -631,21 +646,3 @@ describe('Screen', function() end) end) end) - -describe('nvim_ui_attach()', function() - before_each(function() - clear() - end) - it('handles very large width/height #2180', function() - local screen = Screen.new(999, 999) - screen:attach() - eq(999, eval('&lines')) - eq(999, eval('&columns')) - end) - it('invalid option returns error', function() - local screen = Screen.new() - local status, rv = pcall(function() screen:attach({foo={'foo'}}) end) - eq(false, status) - eq('No such ui option', rv:match("No such .*")) - end) -end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 9f273e8dc9..168080a092 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -4,6 +4,8 @@ local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command = helpers.feed_command local eq = helpers.eq local eval = helpers.eval +local iswin = helpers.iswin +local sleep = helpers.sleep describe('search highlighting', function() local screen @@ -91,6 +93,38 @@ describe('search highlighting', function() ]]) end) + it('is preserved during :terminal activity', function() + if iswin() then + feed([[:terminal for /L \%I in (1,1,5000) do @(echo xxx & echo xxx & echo xxx)<cr>]]) + else + feed([[:terminal for i in $(seq 1 5000); do printf 'xxx\nxxx\nxxx\n'; done<cr>]]) + end + + feed(':file term<CR>') + feed(':vnew<CR>') + insert([[ + foo bar baz + bar baz foo + bar foo baz + ]]) + feed('/foo') + sleep(50) -- Allow some terminal activity. + screen:expect([[ + {3:foo} bar baz {3:│}xxx | + bar baz {2:foo} {3:│}xxx | + bar {2:foo} baz {3:│}xxx | + {3:│}xxx | + {1:~ }{3:│}xxx | + {5:[No Name] [+] }{3:term }| + /foo^ | + ]], { [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {background = Screen.colors.Yellow}, + [3] = {reverse = true}, + [4] = {foreground = Screen.colors.Red}, + [5] = {bold = true, reverse = true}, + }) + end) + it('works with incsearch', function() feed_command('set hlsearch') feed_command('set incsearch') diff --git a/test/helpers.lua b/test/helpers.lua index 91ceed4df1..efc0e911f1 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,6 +1,38 @@ local assert = require('luassert') local lfs = require('lfs') +local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) +local function shell_quote(str) + if string.find(str, quote_me) or str == '' then + return '"' .. str:gsub('[$%%"\\]', '\\%0') .. '"' + else + return str + end +end + +local function argss_to_cmd(...) + local cmd = '' + for i = 1, select('#', ...) do + local arg = select(i, ...) + if type(arg) == 'string' then + cmd = cmd .. ' ' ..shell_quote(arg) + else + for _, subarg in ipairs(arg) do + cmd = cmd .. ' ' .. shell_quote(subarg) + end + end + end + return cmd +end + +local function popen_r(...) + return io.popen(argss_to_cmd(...), 'r') +end + +local function popen_w(...) + return io.popen(argss_to_cmd(...), 'w') +end + local check_logs_useless_lines = { ['Warning: noted but unhandled ioctl']=1, ['could cause spurious value errors to appear']=2, @@ -16,6 +48,19 @@ end local function ok(res) return assert.is_true(res) end +local function matches(pat, actual) + if nil ~= string.match(actual, pat) then + return true + end + error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) +end +-- Expect an error matching pattern `pat`. +local function expect_err(pat, ...) + local fn = select(1, ...) + local fn_args = {...} + table.remove(fn_args, 1) + assert.error_matches(function() return fn(unpack(fn_args)) end, pat) +end -- initial_path: directory to recurse into -- re: include pattern (string) @@ -108,7 +153,7 @@ local uname = (function() return platform end - local status, f = pcall(io.popen, "uname -s") + local status, f = pcall(popen_r, 'uname', '-s') if status then platform = f:read("*l") f:close() @@ -240,7 +285,7 @@ local function check_cores(app, force) end local function which(exe) - local pipe = io.popen('which ' .. exe, 'r') + local pipe = popen_r('which', exe) local ret = pipe:read('*a') pipe:close() if ret == '' then @@ -250,6 +295,19 @@ local function which(exe) end end +local function repeated_read_cmd(...) + for _ = 1, 10 do + local stream = popen_r(...) + local ret = stream:read('*a') + stream:close() + if ret then + return ret + end + end + print('ERROR: Failed to execute ' .. argss_to_cmd(...) .. ': nil return after 10 attempts') + return nil +end + local function shallowcopy(orig) if type(orig) ~= 'table' then return orig @@ -469,6 +527,8 @@ format_luav = function(v, indent, opts) end elseif type(v) == 'nil' then ret = 'nil' + elseif type(v) == 'boolean' then + ret = (v and 'true' or 'false') else print(type(v)) -- Not implemented yet @@ -554,6 +614,7 @@ end return { REMOVE_THIS = REMOVE_THIS, + argss_to_cmd = argss_to_cmd, check_cores = check_cores, check_logs = check_logs, concat_tables = concat_tables, @@ -561,6 +622,7 @@ return { deepcopy = deepcopy, dictdiff = dictdiff, eq = eq, + expect_err = expect_err, filter = filter, fixtbl = fixtbl, fixtbl_rec = fixtbl_rec, @@ -570,9 +632,13 @@ return { hasenv = hasenv, intchar2lua = intchar2lua, map = map, + matches = matches, mergedicts_copy = mergedicts_copy, neq = neq, ok = ok, + popen_r = popen_r, + popen_w = popen_w, + repeated_read_cmd = repeated_read_cmd, shallowcopy = shallowcopy, table_flatten = table_flatten, tmpname = tmpname, diff --git a/test/includes/pre/sys/stat.h b/test/includes/pre/sys/stat.h index c6cac80913..1cb811d579 100644 --- a/test/includes/pre/sys/stat.h +++ b/test/includes/pre/sys/stat.h @@ -1,4 +1,6 @@ -#define _GNU_SOURCE +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif #include <sys/stat.h> static const mode_t kS_IFMT = S_IFMT; diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 5cc2be50b1..f8143a0125 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -779,7 +779,7 @@ local function gen_itp(it) end local function cppimport(path) - return cimport(Paths.test_include_path .. '/' .. path) + return cimport(Paths.test_source_path .. '/test/includes/pre/' .. path) end cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h') diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua index d9c98e8afa..4d58a8934e 100644 --- a/test/unit/os/fileio_spec.lua +++ b/test/unit/os/fileio_spec.lua @@ -113,7 +113,7 @@ end describe('file_open_fd', function() itp('can use file descriptor returned by os_open for reading', function() local fd = m.os_open(file1, m.kO_RDONLY, 0) - local err, fp = file_open_fd(fd, false) + local err, fp = file_open_fd(fd, m.kFileReadOnly) eq(0, err) eq({#fcontents, fcontents}, {file_read(fp, #fcontents)}) eq(0, m.file_close(fp, false)) @@ -121,7 +121,7 @@ describe('file_open_fd', function() itp('can use file descriptor returned by os_open for writing', function() eq(nil, lfs.attributes(filec)) local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384) - local err, fp = file_open_fd(fd, true) + local err, fp = file_open_fd(fd, m.kFileWriteOnly) eq(0, err) eq(4, file_write(fp, 'test')) eq(0, m.file_close(fp, false)) @@ -133,7 +133,7 @@ end) describe('file_open_fd_new', function() itp('can use file descriptor returned by os_open for reading', function() local fd = m.os_open(file1, m.kO_RDONLY, 0) - local err, fp = file_open_fd_new(fd, false) + local err, fp = file_open_fd_new(fd, m.kFileReadOnly) eq(0, err) eq({#fcontents, fcontents}, {file_read(fp, #fcontents)}) eq(0, m.file_free(fp, false)) @@ -141,7 +141,7 @@ describe('file_open_fd_new', function() itp('can use file descriptor returned by os_open for writing', function() eq(nil, lfs.attributes(filec)) local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384) - local err, fp = file_open_fd_new(fd, true) + local err, fp = file_open_fd_new(fd, m.kFileWriteOnly) eq(0, err) eq(4, file_write(fp, 'test')) eq(0, m.file_free(fp, false)) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 78455ee324..ae6dfe6423 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -390,7 +390,7 @@ describe('fs.c', function() buf = ffi.new('char[?]', size + 1, ('\0'):rep(size)) end local eof = ffi.new('bool[?]', 1, {true}) - local ret2 = fs.os_read(fd, eof, buf, size) + local ret2 = fs.os_read(fd, eof, buf, size, false) local ret1 = eof[0] local ret3 = '' if buf ~= nil then @@ -408,7 +408,7 @@ describe('fs.c', function() end local iov = ffi.new('struct iovec[?]', #sizes, bufs) local eof = ffi.new('bool[?]', 1, {true}) - local ret2 = fs.os_readv(fd, eof, iov, #sizes) + local ret2 = fs.os_readv(fd, eof, iov, #sizes, false) local ret1 = eof[0] local ret3 = {} for i = 1,#sizes do @@ -418,7 +418,7 @@ describe('fs.c', function() return ret1, ret2, ret3 end local function os_write(fd, data) - return fs.os_write(fd, data, data and #data or 0) + return fs.os_write(fd, data, data and #data or 0, false) end describe('os_path_exists', function() @@ -491,6 +491,22 @@ describe('fs.c', function() end) end) + describe('os_dup', function() + itp('returns new file descriptor', function() + local dup0 = fs.os_dup(0) + local dup1 = fs.os_dup(1) + local dup2 = fs.os_dup(2) + local tbl = {[0]=true, [1]=true, [2]=true, + [tonumber(dup0)]=true, [tonumber(dup1)]=true, + [tonumber(dup2)]=true} + local i = 0 + for _, _ in pairs(tbl) do + i = i + 1 + end + eq(i, 6) -- All fds must be unique + end) + end) + describe('os_open', function() local new_file = 'test_new_file' local existing_file = 'unit-test-directory/test_existing.file' diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 363358d134..1073855a7d 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -2,6 +2,10 @@ -- windows, will probably need quite a bit of adjustment to run there. local ffi = require("ffi") +local global_helpers = require('test.helpers') + +local argss_to_cmd = global_helpers.argss_to_cmd +local repeated_read_cmd = global_helpers.repeated_read_cmd local ccs = {} @@ -22,15 +26,6 @@ table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.7"}, type = "gcc"}) table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"}) table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"}) -local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) -local function shell_quote(str) - if string.find(str, quote_me) or str == '' then - return "'" .. string.gsub(str, "'", [['"'"']]) .. "'" - else - return str - end -end - -- parse Makefile format dependencies into a Lua table local function parse_make_deps(deps) -- remove line breaks and line concatenators @@ -149,16 +144,6 @@ function Gcc:add_to_include_path(...) end end -local function argss_to_cmd(...) - local cmd = '' - for i = 1, select('#', ...) do - for _, arg in ipairs(select(i, ...)) do - cmd = cmd .. ' ' .. shell_quote(arg) - end - end - return cmd -end - -- returns a list of the headers files upon which this file relies function Gcc:dependencies(hdr) local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1' @@ -172,29 +157,15 @@ function Gcc:dependencies(hdr) end end -local function repeated_call(...) - local cmd = argss_to_cmd(...) - for _ = 1, 10 do - local stream = io.popen(cmd) - local ret = stream:read('*a') - stream:close() - if ret then - return ret - end - end - print('ERROR: preprocess.lua: Failed to execute ' .. cmd .. ': nil return after 10 attempts') - return nil -end - 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') pseudoheader_file:close() - local standard_defines = repeated_call(self.path, - self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local standard_defines = repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname}) os.remove(pseudoheader_fname) self.standard_defines = {} for line in standard_defines:gmatch('[^\n]+') do @@ -223,9 +194,9 @@ function Gcc:preprocess(previous_defines, ...) pseudoheader_file:flush() pseudoheader_file:close() - local defines = repeated_call(self.path, self.preprocessor_extra_flags, - self.get_defines_extra_flags, - {pseudoheader_fname}) + local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags, + self.get_defines_extra_flags, + {pseudoheader_fname}) defines = self:filter_standard_defines(defines) -- lfs = require("lfs") @@ -234,9 +205,10 @@ function Gcc:preprocess(previous_defines, ...) -- io.stderr\write("CWD: #{lfs.currentdir!}\n") -- io.stderr\write("CMD: #{cmd}\n") - local declarations = repeated_call(self.path, self.preprocessor_extra_flags, - self.get_declarations_extra_flags, - {pseudoheader_fname}) + local declarations = repeated_read_cmd(self.path, + self.preprocessor_extra_flags, + self.get_declarations_extra_flags, + {pseudoheader_fname}) os.remove(pseudoheader_fname) |