aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/functional/api/server_requests_spec.lua4
-rw-r--r--test/functional/autocmd/autocmd_spec.lua2
-rw-r--r--test/functional/autocmd/bufenter_spec.lua35
-rw-r--r--test/functional/autocmd/dirchanged_spec.lua113
-rw-r--r--test/functional/autocmd/tabnew_spec.lua9
-rw-r--r--test/functional/autocmd/tabnewentered_spec.lua8
-rw-r--r--test/functional/core/job_partial_spec.lua13
-rw-r--r--test/functional/core/job_spec.lua18
-rw-r--r--test/functional/eval/execute_spec.lua91
-rw-r--r--test/functional/eval/json_functions_spec.lua6
-rw-r--r--test/functional/eval/msgpack_functions_spec.lua19
-rw-r--r--test/functional/eval/server_spec.lua17
-rw-r--r--test/functional/eval/setpos_spec.lua64
-rw-r--r--test/functional/eval/string_spec.lua92
-rw-r--r--test/functional/eval/system_spec.lua101
-rw-r--r--test/functional/ex_cmds/ctrl_c_spec.lua62
-rw-r--r--test/functional/ex_cmds/dict_notifications_spec.lua17
-rw-r--r--test/functional/ex_cmds/global_spec.lua74
-rw-r--r--test/functional/fixtures/CMakeLists.txt1
-rw-r--r--test/functional/fixtures/printargs-test.c9
-rw-r--r--test/functional/helpers.lua82
-rw-r--r--test/functional/legacy/autochdir_spec.lua26
-rw-r--r--test/functional/options/defaults_spec.lua35
-rw-r--r--test/functional/plugin/msgpack_spec.lua2
-rw-r--r--test/functional/plugin/shada_spec.lua37
-rw-r--r--test/functional/shada/buffers_spec.lua2
-rw-r--r--test/functional/shada/marks_spec.lua15
-rw-r--r--test/functional/shada/shada_spec.lua6
-rw-r--r--test/functional/ui/inccommand_spec.lua71
-rw-r--r--test/functional/ui/mouse_spec.lua14
-rw-r--r--test/functional/ui/screen.lua107
-rw-r--r--test/functional/ui/screen_basic_spec.lua16
-rw-r--r--test/helpers.lua40
-rw-r--r--test/unit/api/private_helpers_spec.lua17
-rw-r--r--test/unit/eval/helpers.lua286
-rw-r--r--test/unit/eval/tv_clear_spec.lua127
-rw-r--r--test/unit/formatc.lua8
-rw-r--r--test/unit/helpers.lua93
-rw-r--r--test/unit/path_spec.lua11
-rw-r--r--test/unit/preprocess.lua156
40 files changed, 1512 insertions, 394 deletions
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index 3245e1b52d..aa91cd1396 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -8,8 +8,6 @@ local nvim_prog, command, funcs = helpers.nvim_prog, helpers.command, helpers.fu
local source, next_message = helpers.source, helpers.next_message
local meths = helpers.meths
-if helpers.pending_win32(pending) then return end
-
describe('server -> client', function()
local cid
@@ -212,6 +210,8 @@ describe('server -> client', function()
funcs.jobstop(jobid)
end)
+ if helpers.pending_win32(pending) then return end
+
it('rpc and text stderr can be combined', function()
eq("ok",funcs.rpcrequest(jobid, "poll"))
funcs.rpcnotify(jobid, "ping")
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index 162e112047..c38bd75c69 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -12,8 +12,8 @@ describe('autocmds:', function()
local expected = {
'WinLeave',
'TabLeave',
- 'TabNew',
'WinEnter',
+ 'TabNew',
'TabEnter',
'BufLeave',
'BufEnter'
diff --git a/test/functional/autocmd/bufenter_spec.lua b/test/functional/autocmd/bufenter_spec.lua
new file mode 100644
index 0000000000..ccbcdf5c5e
--- /dev/null
+++ b/test/functional/autocmd/bufenter_spec.lua
@@ -0,0 +1,35 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local eval = helpers.eval
+local execute = helpers.execute
+local request = helpers.request
+local source = helpers.source
+
+describe('autocmd BufEnter', function()
+ before_each(clear)
+
+ it("triggered by nvim_command('edit <dir>')", function()
+ command("autocmd BufEnter * if isdirectory(expand('<afile>')) | let g:dir_bufenter = 1 | endif")
+ request("nvim_command", "split .")
+ eq(1, eval("exists('g:dir_bufenter')")) -- Did BufEnter for the directory.
+ eq(2, eval("bufnr('%')")) -- Switched to the dir buffer.
+ end)
+
+ it('triggered by "try|:split <dir>|endtry" in a function', function()
+ command("autocmd BufEnter * if isdirectory(expand('<afile>')) | let g:dir_bufenter = 1 | endif")
+ source([[
+ function! Test()
+ try
+ exe 'split .'
+ catch
+ endtry
+ endfunction
+ ]])
+ execute("call Test()")
+ eq(1, eval("exists('g:dir_bufenter')")) -- Did BufEnter for the directory.
+ eq(2, eval("bufnr('%')")) -- Switched to the dir buffer.
+ end)
+end)
diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua
new file mode 100644
index 0000000000..15196dbd44
--- /dev/null
+++ b/test/functional/autocmd/dirchanged_spec.lua
@@ -0,0 +1,113 @@
+local lfs = require('lfs')
+local h = require('test.functional.helpers')(after_each)
+
+local clear = h.clear
+local command = h.command
+local eq = h.eq
+local eval = h.eval
+local request = h.request
+
+describe('autocmd DirChanged', function()
+ local curdir = string.gsub(lfs.currentdir(), '\\', '/')
+ local dirs = {
+ curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
+ curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',
+ curdir .. '/Xtest-functional-autocmd-dirchanged.dir3',
+ }
+
+ setup(function() for _, dir in pairs(dirs) do h.mkdir(dir) end end)
+ teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end)
+
+ before_each(function()
+ clear()
+ command('autocmd DirChanged * let [g:event, g:scope, g:cdcount] = [copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
+ end)
+
+ it('sets v:event', function()
+ command('lcd '..dirs[1])
+ eq({cwd=dirs[1], scope='window'}, eval('g:event'))
+ eq(1, eval('g:cdcount'))
+
+ command('tcd '..dirs[2])
+ eq({cwd=dirs[2], scope='tab'}, eval('g:event'))
+ eq(2, eval('g:cdcount'))
+
+ command('cd '..dirs[3])
+ eq({cwd=dirs[3], scope='global'}, eval('g:event'))
+ eq(3, eval('g:cdcount'))
+ end)
+
+ it('disallows recursion', function()
+ command('set shellslash')
+ -- Set up a _nested_ handler.
+ command('autocmd DirChanged * nested lcd '..dirs[3])
+ command('lcd '..dirs[1])
+ eq({cwd=dirs[1], scope='window'}, eval('g:event'))
+ eq(1, eval('g:cdcount'))
+ -- autocmd changed to dirs[3], but did NOT trigger another DirChanged.
+ eq(dirs[3], eval('getcwd()'))
+ end)
+
+ it('sets <amatch> to CWD "scope"', function()
+ command('lcd '..dirs[1])
+ eq('window', eval('g:scope'))
+
+ command('tcd '..dirs[2])
+ eq('tab', eval('g:scope'))
+
+ command('cd '..dirs[3])
+ eq('global', eval('g:scope'))
+ end)
+
+ it('does not trigger if :cd fails', function()
+ command('let g:event = {}')
+
+ local status1, err1 = pcall(function()
+ command('lcd '..dirs[1] .. '/doesnotexist')
+ end)
+ eq({}, eval('g:event'))
+
+ local status2, err2 = pcall(function()
+ command('lcd '..dirs[2] .. '/doesnotexist')
+ end)
+ eq({}, eval('g:event'))
+
+ local status3, err3 = pcall(function()
+ command('lcd '..dirs[3] .. '/doesnotexist')
+ end)
+ eq({}, eval('g:event'))
+
+ eq(false, status1)
+ eq(false, status2)
+ eq(false, status3)
+
+ eq('E344:', string.match(err1, "E%d*:"))
+ eq('E344:', string.match(err2, "E%d*:"))
+ eq('E344:', string.match(err3, "E%d*:"))
+ end)
+
+ it("is triggered by 'autochdir'", function()
+ command('set autochdir')
+
+ command('split '..dirs[1]..'/foo')
+ eq({cwd=dirs[1], scope='window'}, eval('g:event'))
+
+ command('split '..dirs[2]..'/bar')
+ eq({cwd=dirs[2], scope='window'}, eval('g:event'))
+ end)
+
+ it('is triggered by nvim_set_current_dir()', function()
+ request('nvim_set_current_dir', dirs[1])
+ eq({cwd=dirs[1], scope='global'}, eval('g:event'))
+
+ request('nvim_set_current_dir', dirs[2])
+ eq({cwd=dirs[2], scope='global'}, eval('g:event'))
+
+ local status, err = pcall(function()
+ request('nvim_set_current_dir', '/doesnotexist')
+ end)
+ eq(false, status)
+ eq('Failed to change directory', string.match(err, ': (.*)'))
+ eq({cwd=dirs[2], scope='global'}, eval('g:event'))
+ end)
+end)
diff --git a/test/functional/autocmd/tabnew_spec.lua b/test/functional/autocmd/tabnew_spec.lua
index 2148b21832..ad40954f76 100644
--- a/test/functional/autocmd/tabnew_spec.lua
+++ b/test/functional/autocmd/tabnew_spec.lua
@@ -5,8 +5,6 @@ local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
-if helpers.pending_win32(pending) then return end
-
describe('autocmd TabNew', function()
before_each(clear)
@@ -19,12 +17,11 @@ describe('autocmd TabNew', function()
end)
it('matches when opening a new tab for FILE', function()
- local tmp_path = helpers.funcs.tempname()
command('let g:test = "foo"')
- command('autocmd! TabNew ' .. tmp_path .. ' let g:test = "bar"')
- command('tabnew ' .. tmp_path ..'X')
+ command('autocmd! TabNew Xtest-tabnew let g:test = "bar"')
+ command('tabnew Xtest-tabnewX')
eq('foo', eval('g:test'))
- command('tabnew ' .. tmp_path)
+ command('tabnew Xtest-tabnew')
eq('bar', eval('g:test'))
end)
end)
diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua
index f033bd5fe4..bdbe677132 100644
--- a/test/functional/autocmd/tabnewentered_spec.lua
+++ b/test/functional/autocmd/tabnewentered_spec.lua
@@ -1,8 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq
-if helpers.pending_win32(pending) then return end
-
describe('TabNewEntered', function()
describe('au TabNewEntered', function()
describe('with * as <afile>', function()
@@ -15,9 +13,9 @@ describe('TabNewEntered', function()
end)
describe('with FILE as <afile>', function()
it('matches when opening a new tab for FILE', function()
- local tmp_path = nvim('eval', 'tempname()')
- nvim('command', 'au! TabNewEntered '..tmp_path..' echom "tabnewentered:match"')
- eq("\n\""..tmp_path.."\" [New File]\ntabnewentered:4:4\ntabnewentered:match", nvim('command_output', 'tabnew '..tmp_path))
+ nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"')
+ eq('\n"Xtest-tabnewentered" [New File]\ntabnewentered:4:4\ntabnewentered:match',
+ nvim('command_output', 'tabnew Xtest-tabnewentered'))
end)
end)
describe('with CTRL-W T', function()
diff --git a/test/functional/core/job_partial_spec.lua b/test/functional/core/job_partial_spec.lua
index b60f239db9..7643b283c4 100644
--- a/test/functional/core/job_partial_spec.lua
+++ b/test/functional/core/job_partial_spec.lua
@@ -2,13 +2,14 @@ local helpers = require('test.functional.helpers')(after_each)
local clear, eq, next_msg, nvim, source = helpers.clear, helpers.eq,
helpers.next_message, helpers.nvim, helpers.source
-if helpers.pending_win32(pending) then return end
-
describe('jobs with partials', function()
local channel
before_each(function()
clear()
+ if helpers.os_name() == 'windows' then
+ helpers.set_shell_powershell()
+ end
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
end)
@@ -16,12 +17,14 @@ describe('jobs with partials', function()
it('works correctly', function()
source([[
function PrintArgs(a1, a2, id, data, event)
- call rpcnotify(g:channel, '1', a:a1, a:a2, a:data, a:event)
+ " Windows: Remove ^M char.
+ let normalized = map(a:data, 'substitute(v:val, "\r", "", "g")')
+ call rpcnotify(g:channel, '1', a:a1, a:a2, normalized, a:event)
endfunction
let Callback = function('PrintArgs', ["foo", "bar"])
let g:job_opts = {'on_stdout': Callback}
- call jobstart(['echo'], g:job_opts)
+ call jobstart('echo "some text"', g:job_opts)
]])
- eq({'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, next_msg())
+ eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg())
end)
end)
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 75b50aad0a..48a4689545 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -312,6 +312,24 @@ describe('jobs', function()
end)
end)
+ it('does not repeat output with slow output handlers', function()
+ source([[
+ let d = {'data': []}
+ function! d.on_stdout(job, data, event) dict
+ call add(self.data, a:data)
+ sleep 200m
+ endfunction
+ if has('win32')
+ let cmd = '1,2,3,4,5 | foreach-object -process {echo $_; sleep 0.1}'
+ else
+ let cmd = ['sh', '-c', 'for i in $(seq 1 5); do echo $i; sleep 0.1; done']
+ endif
+ call jobwait([jobstart(cmd, d)])
+ call rpcnotify(g:channel, 'data', d.data)
+ ]])
+ eq({'notification', 'data', {{{'1', ''}, {'2', ''}, {'3', ''}, {'4', ''}, {'5', ''}}}}, next_msg())
+ end)
+
describe('jobwait', function()
it('returns a list of status codes', function()
source([[
diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua
index fc13c0a72b..cc9b61b842 100644
--- a/test/functional/eval/execute_spec.lua
+++ b/test/functional/eval/execute_spec.lua
@@ -7,7 +7,7 @@ local redir_exec = helpers.redir_exec
local exc_exec = helpers.exc_exec
local funcs = helpers.funcs
local Screen = require('test.functional.ui.screen')
-local feed = helpers.feed
+local command = helpers.command
describe('execute()', function()
before_each(clear)
@@ -62,11 +62,11 @@ describe('execute()', function()
ret = exc_exec('call execute(function("tr"))')
eq('Vim(call):E729: using Funcref as a String', ret)
ret = exc_exec('call execute(["echo 42", 0.0, "echo 44"])')
- eq('Vim(call):E806: using Float as a String', ret)
+ eq('Vim:E806: using Float as a String', ret)
ret = exc_exec('call execute(["echo 42", v:_null_dict, "echo 44"])')
- eq('Vim(call):E731: using Dictionary as a String', ret)
+ eq('Vim:E731: using Dictionary as a String', ret)
ret = exc_exec('call execute(["echo 42", function("tr"), "echo 44"])')
- eq('Vim(call):E729: using Funcref as a String', ret)
+ eq('Vim:E729: using Funcref as a String', ret)
end)
-- This matches Vim behavior.
@@ -74,18 +74,75 @@ describe('execute()', function()
eq('\n:!echo "foo"\13\n', funcs.execute('!echo "foo"'))
end)
- it('silences command run inside', function()
- local screen = Screen.new(40, 5)
- screen:attach()
- screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} )
- feed(':let g:mes = execute("echon 42")<CR>')
- screen:expect([[
- ^ |
- {0:~ }|
- {0:~ }|
- {0:~ }|
- :let g:mes = execute("echon 42") |
- ]])
- eq('42', eval('g:mes'))
+ describe('{silent} argument', function()
+ it('captures & displays output for ""', function()
+ local screen = Screen.new(40, 5)
+ screen:attach()
+ command('let g:mes = execute("echon 42", "")')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ 42 |
+ ]])
+ eq('42', eval('g:mes'))
+ end)
+
+ it('captures but does not display output for "silent"', function()
+ local screen = Screen.new(40, 5)
+ screen:attach()
+ command('let g:mes = execute("echon 42")')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ eq('42', eval('g:mes'))
+
+ command('let g:mes = execute("echon 13", "silent")')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ eq('13', eval('g:mes'))
+ end)
+
+ it('suppresses errors for "silent!"', function()
+ eq(0, exc_exec('let g:mes = execute(0.0, "silent!")'))
+ eq('', eval('g:mes'))
+
+ eq(0, exc_exec('let g:mes = execute("echon add(1, 1)", "silent!")'))
+ eq('1', eval('g:mes'))
+
+ eq(0, exc_exec('let g:mes = execute(["echon 42", "echon add(1, 1)"], "silent!")'))
+ eq('421', eval('g:mes'))
+ end)
+
+ it('propagates errors for "" and "silent"', function()
+ local ret
+ ret = exc_exec('call execute(0.0, "")')
+ eq('Vim(call):E806: using Float as a String', ret)
+
+ ret = exc_exec('call execute(v:_null_dict, "silent")')
+ eq('Vim(call):E731: using Dictionary as a String', ret)
+
+ ret = exc_exec('call execute("echo add(1, 1)", "")')
+ eq('Vim(echo):E714: List required', ret)
+
+ ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "")')
+ eq('Vim(echo):E714: List required', ret)
+
+ ret = exc_exec('call execute("echo add(1, 1)", "silent")')
+ eq('Vim(echo):E714: List required', ret)
+
+ ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "silent")')
+ eq('Vim(echo):E714: List required', ret)
+ end)
end)
end)
diff --git a/test/functional/eval/json_functions_spec.lua b/test/functional/eval/json_functions_spec.lua
index 4a6758019b..fc0a19bdfa 100644
--- a/test/functional/eval/json_functions_spec.lua
+++ b/test/functional/eval/json_functions_spec.lua
@@ -672,6 +672,12 @@ describe('json_encode() function', function()
exc_exec('call json_encode(function("tr"))'))
end)
+ it('fails to dump a partial', function()
+ execute('function T() dict\nendfunction')
+ eq('Vim(call):E474: Error while dumping encode_tv2json() argument, itself: attempt to dump function reference',
+ exc_exec('call json_encode(function("T", [1, 2], {}))'))
+ end)
+
it('fails to dump a function reference in a list', function()
eq('Vim(call):E474: Error while dumping encode_tv2json() argument, index 0: attempt to dump function reference',
exc_exec('call json_encode([function("tr")])'))
diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua
index 3fb54c2ee7..44c01d2226 100644
--- a/test/functional/eval/msgpack_functions_spec.lua
+++ b/test/functional/eval/msgpack_functions_spec.lua
@@ -493,6 +493,12 @@ describe('msgpackparse() function', function()
exc_exec('call msgpackparse(function("tr"))'))
end)
+ it('fails to parse a partial', function()
+ execute('function T() dict\nendfunction')
+ eq('Vim(call):E686: Argument of msgpackparse() must be a List',
+ exc_exec('call msgpackparse(function("T", [1, 2], {}))'))
+ end)
+
it('fails to parse a float', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
exc_exec('call msgpackparse(0.0)'))
@@ -570,6 +576,13 @@ describe('msgpackdump() function', function()
exc_exec('call msgpackdump([Todump])'))
end)
+ it('fails to dump a partial', function()
+ execute('function T() dict\nendfunction')
+ execute('let Todump = function("T", [1, 2], {})')
+ eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference',
+ exc_exec('call msgpackdump([Todump])'))
+ end)
+
it('fails to dump a function reference in a list', function()
execute('let todump = [function("tr")]')
eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, index 0: attempt to dump function reference',
@@ -675,6 +688,12 @@ describe('msgpackdump() function', function()
exc_exec('call msgpackdump(function("tr"))'))
end)
+ it('fails to dump a partial', function()
+ execute('function T() dict\nendfunction')
+ eq('Vim(call):E686: Argument of msgpackdump() must be a List',
+ exc_exec('call msgpackdump(function("T", [1, 2], {}))'))
+ end)
+
it('fails to dump a float', function()
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump(0.0)'))
diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua
index d2c985e894..420aea04aa 100644
--- a/test/functional/eval/server_spec.lua
+++ b/test/functional/eval/server_spec.lua
@@ -4,8 +4,6 @@ local nvim, eq, neq, eval = helpers.nvim, helpers.eq, helpers.neq, helpers.eval
local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths
local os_name = helpers.os_name
-if helpers.pending_win32(pending) then return end
-
describe('serverstart(), serverstop()', function()
before_each(clear)
@@ -42,8 +40,8 @@ describe('serverstart(), serverstop()', function()
-- v:servername will take the next available server.
local servername = (os_name() == 'windows'
- and [[\\.\pipe\Xtest-functional-server-server-pipe]]
- or 'Xtest-functional-server-server-socket')
+ and [[\\.\pipe\Xtest-functional-server-pipe]]
+ or 'Xtest-functional-server-socket')
funcs.serverstart(servername)
eq(servername, meths.get_vvar('servername'))
end)
@@ -63,9 +61,11 @@ describe('serverlist()', function()
local n = eval('len(serverlist())')
-- Add a few
- local servs = {'should-not-exist', 'another-one-that-shouldnt'}
+ local servs = (os_name() == 'windows'
+ and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] }
+ or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] })
for _, s in ipairs(servs) do
- eq(s, eval('serverstart("'..s..'")'))
+ eq(s, eval("serverstart('"..s.."')"))
end
local new_servs = eval('serverlist()')
@@ -75,10 +75,9 @@ 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])
- nvim('command', 'call serverstop("'..servs[i]..'")')
+ nvim('command', "call serverstop('"..servs[i].."')")
end
- -- After calling serverstop() on the new servers, they should no longer be
- -- in the list.
+ -- After serverstop() the servers should NOT be in the list.
eq(n, eval('len(serverlist())'))
end)
end)
diff --git a/test/functional/eval/setpos_spec.lua b/test/functional/eval/setpos_spec.lua
new file mode 100644
index 0000000000..2e27cd8ac0
--- /dev/null
+++ b/test/functional/eval/setpos_spec.lua
@@ -0,0 +1,64 @@
+local helpers = require('test.functional.helpers')(after_each)
+local setpos = helpers.funcs.setpos
+local getpos = helpers.funcs.getpos
+local insert = helpers.insert
+local clear = helpers.clear
+local execute = helpers.execute
+local eval = helpers.eval
+local eq = helpers.eq
+local exc_exec = helpers.exc_exec
+
+
+describe('setpos() function', function()
+ before_each(function()
+ clear()
+ insert([[
+ First line of text
+ Second line of text
+ Third line of text]])
+ execute('new')
+ insert([[
+ Line of text 1
+ Line of text 2
+ Line of text 3]])
+ end)
+ it('can set the current cursor position', function()
+ setpos(".", {0, 2, 1, 0})
+ eq(getpos("."), {0, 2, 1, 0})
+ setpos(".", {2, 1, 1, 0})
+ eq(getpos("."), {0, 1, 1, 0})
+ -- Ensure get an error attempting to set position to another buffer
+ local ret = exc_exec('call setpos(".", [1, 1, 1, 0])')
+ eq('Vim(call):E474: Invalid argument', ret)
+ end)
+ it('can set lowercase marks in the current buffer', function()
+ setpos("'d", {0, 2, 1, 0})
+ eq(getpos("'d"), {0, 2, 1, 0})
+ execute('undo', 'call setpos("\'d", [2, 3, 1, 0])')
+ eq(getpos("'d"), {0, 3, 1, 0})
+ end)
+ it('can set lowercase marks in other buffers', function()
+ local retval = setpos("'d", {1, 2, 1, 0})
+ eq(0, retval)
+ setpos("'d", {1, 2, 1, 0})
+ eq(getpos("'d"), {0, 0, 0, 0})
+ execute('wincmd w')
+ eq(eval('bufnr("%")'), 1)
+ eq(getpos("'d"), {0, 2, 1, 0})
+ end)
+ it("fails when setting a mark in a buffer that doesn't exist", function()
+ local retval = setpos("'d", {3, 2, 1, 0})
+ eq(-1, retval)
+ eq(getpos("'d"), {0, 0, 0, 0})
+ retval = setpos("'D", {3, 2, 1, 0})
+ eq(-1, retval)
+ eq(getpos("'D"), {0, 0, 0, 0})
+ end)
+ it('can set uppercase marks', function()
+ setpos("'D", {2, 2, 3, 0})
+ eq(getpos("'D"), {2, 2, 3, 0})
+ -- Can set a mark in another buffer
+ setpos("'D", {1, 2, 2, 0})
+ eq(getpos("'D"), {1, 2, 2, 0})
+ end)
+end)
diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua
index 9e2dc4e111..f6279e85e8 100644
--- a/test/functional/eval/string_spec.lua
+++ b/test/functional/eval/string_spec.lua
@@ -9,6 +9,8 @@ local redir_exec = helpers.redir_exec
local funcs = helpers.funcs
local write_file = helpers.write_file
local NIL = helpers.NIL
+local source = helpers.source
+local dedent = helpers.dedent
describe('string() function', function()
before_each(clear)
@@ -110,10 +112,10 @@ describe('string() function', function()
function Test1()
endfunction
- function s:Test2()
+ function s:Test2() dict
endfunction
- function g:Test3()
+ function g:Test3() dict
endfunction
let g:Test2_f = function('s:Test2')
@@ -137,6 +139,85 @@ describe('string() function', function()
it('dumps references to script functions', function()
eq('function(\'<SNR>1_Test2\')', eval('string(Test2_f)'))
end)
+
+ it('dumps partials with self referencing a partial', function()
+ source([[
+ function TestDict() dict
+ endfunction
+ let d = {}
+ let TestDictRef = function('TestDict', d)
+ let d.tdr = TestDictRef
+ ]])
+ eq("\nE724: unable to correctly dump variable with self-referencing container\nfunction('TestDict', {'tdr': function('TestDict', {E724@1})})",
+ redir_exec('echo string(d.tdr)'))
+ end)
+
+ it('dumps automatically created partials', function()
+ eq('function(\'<SNR>1_Test2\', {\'f\': function(\'<SNR>1_Test2\')})',
+ eval('string({"f": Test2_f}.f)'))
+ eq('function(\'<SNR>1_Test2\', [1], {\'f\': function(\'<SNR>1_Test2\', [1])})',
+ eval('string({"f": function(Test2_f, [1])}.f)'))
+ end)
+
+ it('dumps manually created partials', function()
+ eq('function(\'Test3\', [1, 2], {})',
+ eval('string(function("Test3", [1, 2], {}))'))
+ eq('function(\'Test3\', {})',
+ eval('string(function("Test3", {}))'))
+ eq('function(\'Test3\', [1, 2])',
+ eval('string(function("Test3", [1, 2]))'))
+ end)
+
+ it('does not crash or halt when dumping partials with reference cycles in self',
+ function()
+ meths.set_var('d', {v=true})
+ eq(dedent([[
+
+ E724: unable to correctly dump variable with self-referencing container
+ {'p': function('<SNR>1_Test2', {E724@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]),
+ redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))'))
+ end)
+
+ it('does not show errors when dumping partials referencing the same dictionary',
+ function()
+ command('let d = {}')
+ -- Regression for “eval/typval_encode: Dump empty dictionary before
+ -- checking for refcycle”, results in error.
+ eq('[function(\'tr\', {}), function(\'tr\', {})]', eval('string([function("tr", d), function("tr", d)])'))
+ -- Regression for “eval: Work with reference cycles in partials (self)
+ -- properly”, results in crash.
+ eval('extend(d, {"a": 1})')
+ eq('[function(\'tr\', {\'a\': 1}), function(\'tr\', {\'a\': 1})]', eval('string([function("tr", d), function("tr", d)])'))
+ end)
+
+ it('does not crash or halt when dumping partials with reference cycles in arguments',
+ function()
+ meths.set_var('l', {})
+ eval('add(l, l)')
+ -- Regression: the below line used to crash (add returns original list and
+ -- there was error in dumping partials). Tested explicitly in
+ -- test/unit/api/private_helpers_spec.lua.
+ eval('add(l, function("Test1", l))')
+ eq(dedent([=[
+
+ E724: unable to correctly dump variable with self-referencing container
+ function('Test1', [[{E724@2}, function('Test1', [{E724@2}])], function('Test1', [[{E724@4}, function('Test1', [{E724@4}])]])])]=]),
+ redir_exec('echo string(function("Test1", l))'))
+ end)
+
+ it('does not crash or halt when dumping partials with reference cycles in self and arguments',
+ function()
+ meths.set_var('d', {v=true})
+ meths.set_var('l', {})
+ eval('add(l, l)')
+ eval('add(l, function("Test1", l))')
+ eval('add(l, function("Test1", d))')
+ eq(dedent([=[
+
+ E724: unable to correctly dump variable with self-referencing container
+ {'p': function('<SNR>1_Test2', [[{E724@3}, function('Test1', [{E724@3}]), function('Test1', {E724@0})], function('Test1', [[{E724@5}, function('Test1', [{E724@5}]), function('Test1', {E724@0})]]), function('Test1', {E724@0})], {E724@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]=]),
+ redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))'))
+ end)
end)
describe('used to represent lists', function()
@@ -174,6 +255,13 @@ describe('string() function', function()
eq('{}', eval('string({})'))
end)
+ it('dumps list with two same empty dictionaries, also in partials', function()
+ command('let d = {}')
+ eq('[{}, {}]', eval('string([d, d])'))
+ eq('[function(\'tr\', {}), {}]', eval('string([function("tr", d), d])'))
+ eq('[{}, function(\'tr\', {})]', eval('string([d, function("tr", d)])'))
+ end)
+
it('dumps non-empty dictionary', function()
eq('{\'t\'\'est\': 1}', funcs.string({['t\'est']=1}))
end)
diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua
index 6393477260..d5845132dd 100644
--- a/test/functional/eval/system_spec.lua
+++ b/test/functional/eval/system_spec.lua
@@ -1,12 +1,10 @@
local helpers = require('test.functional.helpers')(after_each)
-local eq, clear, eval, execute, feed, nvim =
- helpers.eq, helpers.clear, helpers.eval, helpers.execute, helpers.feed,
- helpers.nvim
+local eq, call, clear, eval, execute, feed, nvim =
+ helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.execute,
+ helpers.feed, helpers.nvim
local Screen = require('test.functional.ui.screen')
-if helpers.pending_win32(pending) then return end
-
local function create_file_with_nuls(name)
return function()
feed('ipart1<C-V>000part2<C-V>000part3<ESC>:w '..name..'<CR>')
@@ -31,7 +29,70 @@ end
describe('system()', function()
before_each(clear)
- it('sets the v:shell_error variable', function()
+ describe('command passed as a List', function()
+ local printargs_path = helpers.nvim_dir..'/printargs-test'
+ .. (helpers.os_name() == 'windows' and '.exe' or '')
+
+ it('sets v:shell_error if cmd[0] is not executable', function()
+ call('system', { 'this-should-not-exist' })
+ eq(-1, eval('v:shell_error'))
+ end)
+
+ it('parameter validation does NOT modify v:shell_error', function()
+ -- 1. Call system() with invalid parameters.
+ -- 2. Assert that v:shell_error was NOT set.
+ execute('call system({})')
+ eq('E475: Invalid argument: expected String or List', eval('v:errmsg'))
+ eq(0, eval('v:shell_error'))
+ execute('call system([])')
+ eq('E474: Invalid argument', eval('v:errmsg'))
+ eq(0, eval('v:shell_error'))
+
+ -- Provoke a non-zero v:shell_error.
+ call('system', { 'this-should-not-exist' })
+ local old_val = eval('v:shell_error')
+ eq(-1, old_val)
+
+ -- 1. Call system() with invalid parameters.
+ -- 2. Assert that v:shell_error was NOT modified.
+ execute('call system({})')
+ eq(old_val, eval('v:shell_error'))
+ execute('call system([])')
+ eq(old_val, eval('v:shell_error'))
+ end)
+
+ it('quotes arguments correctly #5280', function()
+ local out = call('system',
+ { printargs_path, [[1]], [[2 "3]], [[4 ' 5]], [[6 ' 7']] })
+
+ eq(0, eval('v:shell_error'))
+ eq([[arg1=1;arg2=2 "3;arg3=4 ' 5;arg4=6 ' 7';]], out)
+
+ out = call('system', { printargs_path, [['1]], [[2 "3]] })
+ eq(0, eval('v:shell_error'))
+ eq([[arg1='1;arg2=2 "3;]], out)
+
+ out = call('system', { printargs_path, "A\nB" })
+ eq(0, eval('v:shell_error'))
+ eq("arg1=A\nB;", out)
+ end)
+
+ it('calls executable in $PATH', function()
+ if 0 == eval("executable('python')") then pending("missing `python`") end
+ eq("foo\n", eval([[system(['python', '-c', 'print("foo")'])]]))
+ eq(0, eval('v:shell_error'))
+ end)
+
+ it('does NOT run in shell', function()
+ if helpers.os_name() ~= 'windows' then
+ eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])"))
+ end
+ end)
+ end)
+
+ if helpers.pending_win32(pending) then return end
+
+ it('sets v:shell_error', function()
eval([[system("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[system("sh -c 'exit 1'")]])
@@ -158,7 +219,7 @@ describe('system()', function()
end)
end)
- describe('passing number as input', function()
+ describe('input passed as Number', function()
it('stringifies the input', function()
eq('1', eval('system("cat", 1)'))
end)
@@ -175,8 +236,8 @@ describe('system()', function()
end)
end)
- describe('passing list as input', function()
- it('joins list items with linefeed characters', function()
+ describe('input passed as List', function()
+ it('joins List items with linefeed characters', function()
eq('line1\nline2\nline3',
eval("system('cat -', ['line1', 'line2', 'line3'])"))
end)
@@ -185,7 +246,7 @@ describe('system()', function()
-- is inconsistent and is a good reason for the existence of the
-- `systemlist()` function, where input and output map to the same
-- characters(see the following tests with `systemlist()` below)
- describe('with linefeed characters inside list items', function()
+ describe('with linefeed characters inside List items', function()
it('converts linefeed characters to NULs', function()
eq('l1\001p2\nline2\001a\001b\nl3',
eval([[system('cat -', ["l1\np2", "line2\na\nb", 'l3'])]]))
@@ -202,7 +263,7 @@ describe('system()', function()
describe("with a program that doesn't close stdout", function()
if not xclip then
- pending('skipped (missing xclip)', function() end)
+ pending('missing `xclip`', function() end)
else
it('will exit properly after passing input', function()
eq('', eval([[system('xclip -i -selection clipboard', 'clip-data')]]))
@@ -210,18 +271,12 @@ describe('system()', function()
end)
end
end)
-
- describe('command passed as a list', function()
- it('does not execute &shell', function()
- eq('* $NOTHING ~/file',
- eval("system(['echo', '-n', '*', '$NOTHING', '~/file'])"))
- end)
- end)
end)
+if helpers.pending_win32(pending) then return end
+
describe('systemlist()', function()
- -- behavior is similar to `system()` but it returns a list instead of a
- -- string.
+ -- Similar to `system()`, but returns List instead of String.
before_each(clear)
it('sets the v:shell_error variable', function()
@@ -334,14 +389,14 @@ describe('systemlist()', function()
end)
end)
- describe('passing list as input', function()
+ describe('input passed as List', function()
it('joins list items with linefeed characters', function()
eq({'line1', 'line2', 'line3'},
eval("systemlist('cat -', ['line1', 'line2', 'line3'])"))
end)
-- Unlike `system()` which uses SOH to represent NULs, with `systemlist()`
- -- input and ouput are the same
+ -- input and ouput are the same.
describe('with linefeed characters inside list items', function()
it('converts linefeed characters to NULs', function()
eq({'l1\np2', 'line2\na\nb', 'l3'},
@@ -381,7 +436,7 @@ describe('systemlist()', function()
describe("with a program that doesn't close stdout", function()
if not xclip then
- pending('skipped (missing xclip)', function() end)
+ pending('missing `xclip`', function() end)
else
it('will exit properly after passing input', function()
eq({}, eval(
diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/ex_cmds/ctrl_c_spec.lua
new file mode 100644
index 0000000000..b0acb02000
--- /dev/null
+++ b/test/functional/ex_cmds/ctrl_c_spec.lua
@@ -0,0 +1,62 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear, feed, source = helpers.clear, helpers.feed, helpers.source
+local execute = helpers.execute
+
+if helpers.pending_win32(pending) then return end
+
+describe("CTRL-C (mapped)", function()
+ before_each(function()
+ clear()
+ end)
+
+ it("interrupts :global", function()
+ if helpers.skip_fragile(pending,
+ (os.getenv("TRAVIS") and os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN"))
+ then
+ return
+ end
+
+ source([[
+ set nomore nohlsearch undolevels=-1
+ nnoremap <C-C> <NOP>
+ ]])
+
+ execute("silent edit! test/functional/fixtures/bigfile.txt")
+ local screen = Screen.new(52, 6)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.White,
+ background = Screen.colors.Red},
+ [1] = {bold = true,
+ foreground = Screen.colors.SeaGreen}
+ })
+
+ screen:expect([[
+ ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
+ 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; |
+ 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
+ 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
+ 0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;;|
+ |
+ ]])
+
+ local function test_ctrl_c(ms)
+ feed(":global/^/p<CR>")
+ helpers.sleep(ms)
+ feed("<C-C>")
+ screen:expect([[Interrupt]], nil, nil, nil, true)
+ end
+
+ -- The test is time-sensitive. Try different sleep values.
+ local ms_values = {1, 10, 100}
+ for i, ms in ipairs(ms_values) do
+ if i < #ms_values then
+ local status, _ = pcall(test_ctrl_c, ms)
+ if status then break end
+ else -- Call the last attempt directly.
+ test_ctrl_c(ms)
+ end
+ end
+ end)
+end)
diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua
index e6f7609016..5e89986c0f 100644
--- a/test/functional/ex_cmds/dict_notifications_spec.lua
+++ b/test/functional/ex_cmds/dict_notifications_spec.lua
@@ -3,6 +3,7 @@ local clear, nvim, source = helpers.clear, helpers.nvim, helpers.source
local eq, next_msg = helpers.eq, helpers.next_message
local exc_exec = helpers.exc_exec
local command = helpers.command
+local eval = helpers.eval
describe('dictionary change notifications', function()
@@ -255,5 +256,21 @@ describe('dictionary change notifications', function()
eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg())
end)
+
+ it('does not crash when freeing a watched dictionary', function()
+ source([[
+ function! Watcher(dict, key, value)
+ echo a:key string(a:value)
+ endfunction
+
+ function! MakeWatch()
+ let d = {'foo': 'bar'}
+ call dictwatcheradd(d, 'foo', function('Watcher'))
+ endfunction
+ ]])
+
+ command('call MakeWatch()')
+ eq(2, eval('1+1')) -- Still alive?
+ end)
end)
end)
diff --git a/test/functional/ex_cmds/global_spec.lua b/test/functional/ex_cmds/global_spec.lua
deleted file mode 100644
index 81a0ef3248..0000000000
--- a/test/functional/ex_cmds/global_spec.lua
+++ /dev/null
@@ -1,74 +0,0 @@
-local helpers = require('test.functional.helpers')(after_each)
-local Screen = require('test.functional.ui.screen')
-local clear, feed, source = helpers.clear, helpers.feed, helpers.source
-
-if helpers.pending_win32(pending) then return end
-
-describe(':global', function()
- before_each(function()
- clear()
- end)
-
- it('is interrupted by mapped CTRL-C', function()
- if os.getenv("TRAVIS") and os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN" then
- -- XXX: ASAN_UBSAN is too slow to react to the CTRL-C.
- pending("", function() end)
- return
- end
-
- source([[
- set nomore
- set undolevels=-1
- nnoremap <C-C> <NOP>
- for i in range(0, 99999)
- put ='XXX'
- endfor
- put ='ZZZ'
- 1
- .delete
- ]])
-
- local screen = Screen.new(52, 6)
- screen:attach()
- screen:set_default_attr_ids({
- [0] = {foreground = Screen.colors.White,
- background = Screen.colors.Red},
- [1] = {bold = true,
- foreground = Screen.colors.SeaGreen}
- })
-
- screen:expect([[
- ^XXX |
- XXX |
- XXX |
- XXX |
- XXX |
- |
- ]])
-
- local function test_ctrl_c(ms)
- feed(":global/^/p<CR>")
- helpers.sleep(ms)
- feed("<C-C>")
- screen:expect([[
- XXX |
- XXX |
- XXX |
- XXX |
- {0:Interrupted} |
- Interrupt: {1:Press ENTER or type command to continue}^ |
- ]])
- end
-
- -- The test is time-sensitive. Try with different sleep values.
- local ms_values = {10, 50, 100}
- for i, ms in ipairs(ms_values) do
- if i < #ms_values then
- local status, _ = pcall(test_ctrl_c, ms)
- if status then break end
- else -- Call the last attempt directly.
- test_ctrl_c(ms)
- end
- end
- end)
-end)
diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt
index 70aee6efa9..8537ea390f 100644
--- a/test/functional/fixtures/CMakeLists.txt
+++ b/test/functional/fixtures/CMakeLists.txt
@@ -2,3 +2,4 @@ add_executable(tty-test tty-test.c)
target_link_libraries(tty-test ${LIBUV_LIBRARIES})
add_executable(shell-test shell-test.c)
+add_executable(printargs-test printargs-test.c)
diff --git a/test/functional/fixtures/printargs-test.c b/test/functional/fixtures/printargs-test.c
new file mode 100644
index 0000000000..2c25cf8447
--- /dev/null
+++ b/test/functional/fixtures/printargs-test.c
@@ -0,0 +1,9 @@
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ for (int i=1; i<argc; i++) {
+ printf("arg%d=%s;", i, argv[i]);
+ }
+ return 0;
+}
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index f3332cff4f..2939184d2c 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -14,6 +14,7 @@ local neq = global_helpers.neq
local eq = global_helpers.eq
local ok = global_helpers.ok
+local start_dir = lfs.currentdir()
local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim'
local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
'--cmd', 'set shortmess+=I background=light noswapfile noautoindent laststatus=1 undodir=. directory=. viewdir=. backupdir=.',
@@ -21,6 +22,9 @@ local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
local mpack = require('mpack')
+local tmpname = global_helpers.tmpname
+local uname = global_helpers.uname
+
-- Formulate a path to the directory containing nvim. We use this to
-- help run test executables. It helps to keep the tests working, even
-- when the build is not in the default location.
@@ -334,44 +338,6 @@ local function write_file(name, text, dont_dedent)
file:close()
end
--- Tries to get platform name from $SYSTEM_NAME, uname; fallback is "Windows".
-local uname = (function()
- local platform = nil
- return (function()
- if platform then
- return platform
- end
-
- platform = os.getenv("SYSTEM_NAME")
- if platform then
- return platform
- end
-
- local status, f = pcall(io.popen, "uname -s")
- if status then
- platform = f:read("*l")
- else
- platform = 'Windows'
- end
- return platform
- end)
-end)()
-
-local function tmpname()
- local fname = os.tmpname()
- if uname() == 'Windows' and fname:sub(1, 2) == '\\s' then
- -- In Windows tmpname() returns a filename starting with
- -- special sequence \s, prepend $TEMP path
- local tmpdir = os.getenv('TEMP')
- return tmpdir..fname
- elseif fname:match('^/tmp') and uname() == 'Darwin' then
- -- In OS X /tmp links to /private/tmp
- return '/private'..fname
- else
- return fname
- end
-end
-
local function source(code)
local fname = tmpname()
write_file(fname, code)
@@ -380,6 +346,14 @@ local function source(code)
return fname
end
+local function set_shell_powershell()
+ source([[
+ set shell=powershell shellquote=\" shellpipe=\| shellredir=>
+ set shellcmdflag=\ -ExecutionPolicy\ RemoteSigned\ -Command
+ let &shellxquote=' '
+ ]])
+end
+
local function nvim(method, ...)
return request('nvim_'..method, ...)
end
@@ -475,6 +449,12 @@ end
local function rmdir(path)
local ret, _ = pcall(do_rmdir, path)
+ if not ret and os_name() == "windows" then
+ -- Maybe "Permission denied"; try again after changing the nvim
+ -- process to the top-level directory.
+ nvim_command([[exe 'cd '.fnameescape(']]..start_dir.."')")
+ ret, _ = pcall(do_rmdir, path)
+ end
-- During teardown, the nvim process may not exit quickly enough, then rmdir()
-- will fail (on Windows).
if not ret then -- Try again.
@@ -520,12 +500,12 @@ local function create_callindex(func)
end
-- Helper to skip tests. Returns true in Windows systems.
--- pending_func is pending() from busted
-local function pending_win32(pending_func)
+-- pending_fn is pending() from busted
+local function pending_win32(pending_fn)
clear()
if uname() == 'Windows' then
- if pending_func ~= nil then
- pending_func('FIXME: Windows', function() end)
+ if pending_fn ~= nil then
+ pending_fn('FIXME: Windows', function() end)
end
return true
else
@@ -533,6 +513,22 @@ local function pending_win32(pending_func)
end
end
+-- Calls pending() and returns `true` if the system is too slow to
+-- run fragile or expensive tests. Else returns `false`.
+local function skip_fragile(pending_fn, cond)
+ if pending_fn == nil or type(pending_fn) ~= type(function()end) then
+ error("invalid pending_fn")
+ end
+ if cond then
+ pending_fn("skipped (test is fragile on this system)", function() end)
+ return true
+ elseif os.getenv("TEST_SKIP_FRAGILE") then
+ pending_fn("skipped (TEST_SKIP_FRAGILE)", function() end)
+ return true
+ end
+ return false
+end
+
local funcs = create_callindex(nvim_call)
local meths = create_callindex(nvim)
local uimeths = create_callindex(ui)
@@ -601,6 +597,8 @@ return function(after_each)
curwinmeths = curwinmeths,
curtabmeths = curtabmeths,
pending_win32 = pending_win32,
+ skip_fragile = skip_fragile,
+ set_shell_powershell = set_shell_powershell,
tmpname = tmpname,
NIL = mpack.NIL,
}
diff --git a/test/functional/legacy/autochdir_spec.lua b/test/functional/legacy/autochdir_spec.lua
new file mode 100644
index 0000000000..06f7c1dd11
--- /dev/null
+++ b/test/functional/legacy/autochdir_spec.lua
@@ -0,0 +1,26 @@
+local lfs = require('lfs')
+local helpers = require('test.functional.helpers')(after_each)
+local clear, eq = helpers.clear, helpers.eq
+local eval, execute = helpers.eval, helpers.execute
+
+describe('autochdir behavior', function()
+ local dir = 'Xtest-functional-legacy-autochdir'
+
+ before_each(function()
+ lfs.mkdir(dir)
+ clear()
+ end)
+
+ after_each(function()
+ helpers.rmdir(dir)
+ end)
+
+ -- Tests vim/vim/777 without test_autochdir().
+ it('sets filename', function()
+ execute('set acd')
+ execute('new')
+ execute('w '..dir..'/Xtest')
+ eq('Xtest', eval("expand('%')"))
+ eq(dir, eval([[substitute(getcwd(), '.*[/\\]\(\k*\)', '\1', '')]]))
+ end)
+end)
diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua
index 1ae855f26c..caeca5e4e2 100644
--- a/test/functional/options/defaults_spec.lua
+++ b/test/functional/options/defaults_spec.lua
@@ -9,8 +9,6 @@ local eval = helpers.eval
local eq = helpers.eq
local neq = helpers.neq
-if helpers.pending_win32(pending) then return end
-
local function init_session(...)
local args = { helpers.nvim_prog, '-i', 'NONE', '--embed',
'--cmd', 'set shortmess+=I background=light noswapfile noautoindent',
@@ -24,6 +22,8 @@ end
describe('startup defaults', function()
describe(':filetype', function()
+ if helpers.pending_win32(pending) then return end
+
local function expect_filetype(expected)
local screen = Screen.new(48, 4)
screen:attach()
@@ -99,8 +99,37 @@ describe('startup defaults', function()
end)
describe('XDG-based defaults', function()
- -- Need to be in separate describe() block to not run clear() twice.
+ -- Need separate describe() blocks to not run clear() twice.
-- Do not put before_each() here for the same reasons.
+
+ describe('with empty/broken environment', function()
+ it('sets correct defaults', function()
+ clear({env={
+ XDG_CONFIG_HOME=nil,
+ XDG_DATA_HOME=nil,
+ XDG_CACHE_HOME=nil,
+ XDG_RUNTIME_DIR=nil,
+ XDG_CONFIG_DIRS=nil,
+ XDG_DATA_DIRS=nil,
+ LOCALAPPDATA=nil,
+ HOMEPATH=nil,
+ HOMEDRIVE=nil,
+ HOME=nil,
+ TEMP=nil,
+ VIMRUNTIME=nil,
+ USER=nil,
+ }})
+
+ eq('.', meths.get_option('backupdir'))
+ eq('.', meths.get_option('viewdir'))
+ eq('.', meths.get_option('directory'))
+ eq('.', meths.get_option('undodir'))
+ end)
+ end)
+
+ -- TODO(jkeyes): tests below fail on win32 because of path separator.
+ if helpers.pending_win32(pending) then return end
+
describe('with too long XDG variables', function()
before_each(function()
clear({env={
diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua
index c8da8e8f6c..5ba19708cf 100644
--- a/test/functional/plugin/msgpack_spec.lua
+++ b/test/functional/plugin/msgpack_spec.lua
@@ -652,6 +652,8 @@ describe('In autoload/msgpack.vim', function()
eval_eq('integer', ('a'):byte(), '\'a\'')
eval_eq('integer', 0xAB, '\'«\'')
+ eval_eq('integer', 0, '\'\\0\'')
+ eval_eq('integer', 10246567, '\'\\10246567\'')
end)
it('correctly loads constants', function()
diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua
index b1209a22e9..b543037ae2 100644
--- a/test/functional/plugin/shada_spec.lua
+++ b/test/functional/plugin/shada_spec.lua
@@ -609,6 +609,18 @@ describe('In autoload/shada.vim', function()
'abc',
-1,
]}] ]]):gsub('\n', ''))
+ -- Regression: NUL separator must be properly supported
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents ""',
+ ' - separator \'\\0\'',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 1,
+ '',
+ 0x0
+ ]}] ]]):gsub('\n', ''))
end)
it('works with register items', function()
@@ -837,7 +849,7 @@ describe('In autoload/shada.vim', function()
sd2strings_eq({
'Global mark with timestamp ' .. epoch .. ':',
' % Key Description Value',
- ' + n name 20',
+ ' + n name \'\\20\'',
' + f file name "foo"',
' # Value is negative',
' + l line number -10',
@@ -852,7 +864,18 @@ describe('In autoload/shada.vim', function()
sd2strings_eq({
'Global mark with timestamp ' .. epoch .. ':',
' % Key Description Value',
- ' + n name 20',
+ ' + n name 128',
+ ' + f file name "foo"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'n': 128,
+ 'f': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \'\\20\'',
' + f file name "foo"',
' # Expected integer',
' + l line number "FOO"',
@@ -1123,7 +1146,7 @@ describe('In autoload/shada.vim', function()
'Local mark with timestamp ' .. epoch .. ':',
' % Key Description Value',
' + f file name "foo"',
- ' + n name 20',
+ ' + n name \'\\20\'',
' # Value is negative',
' + l line number -10',
' # Value is negative',
@@ -1138,7 +1161,7 @@ describe('In autoload/shada.vim', function()
'Local mark with timestamp ' .. epoch .. ':',
' % Key Description Value',
' + f file name "foo"',
- ' + n name 20',
+ ' + n name \'\\20\'',
' # Expected integer',
' + l line number "FOO"',
' # Expected integer',
@@ -1932,13 +1955,13 @@ describe('In autoload/shada.vim', function()
'Buffer list with timestamp ' .. epoch .. ':',
' % Key Description Value',
' # Expected binary string',
- ' + f file name 10',
+ ' + f file name \'\\10\'',
' + l line number 1',
' + c column 0',
'',
' % Key Description Value',
' # Expected binary string',
- ' + f file name 20',
+ ' + f file name \'\\20\'',
' + l line number 1',
' + c column 0',
})
@@ -1948,7 +1971,7 @@ describe('In autoload/shada.vim', function()
'Buffer list with timestamp ' .. epoch .. ':',
' % Key Description Value',
' # Expected binary string',
- ' + f file name 10',
+ ' + f file name \'\\10\'',
' + l line number 1',
' + c column 0',
'',
diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua
index 7e6338897a..a4746c2205 100644
--- a/test/functional/shada/buffers_spec.lua
+++ b/test/functional/shada/buffers_spec.lua
@@ -8,8 +8,6 @@ local reset, set_additional_cmd, clear =
shada_helpers.reset, shada_helpers.set_additional_cmd,
shada_helpers.clear
-if helpers.pending_win32(pending) then return end
-
describe('ShaDa support code', function()
local testfilename = 'Xtestfile-functional-shada-buffers'
local testfilename_2 = 'Xtestfile-functional-shada-buffers-2'
diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua
index b7c0f61f57..fa760ceb5b 100644
--- a/test/functional/shada/marks_spec.lua
+++ b/test/functional/shada/marks_spec.lua
@@ -14,8 +14,6 @@ local nvim_current_line = function()
return curwinmeths.get_cursor()[1]
end
-if helpers.pending_win32(pending) then return end
-
describe('ShaDa support code', function()
local testfilename = 'Xtestfile-functional-shada-marks'
local testfilename_2 = 'Xtestfile-functional-shada-marks-2'
@@ -153,6 +151,19 @@ describe('ShaDa support code', function()
eq(saved, redir_exec('jumps'))
end)
+ it('when dumping jump list also dumps current position', function()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! G')
+ nvim_command('split ' .. testfilename_2)
+ nvim_command('normal! G')
+ nvim_command('wshada')
+ nvim_command('quit')
+ nvim_command('rshada')
+ nvim_command('normal! \15') -- <C-o>
+ eq(testfilename_2, funcs.bufname('%'))
+ eq({2, 0}, curwinmeths.get_cursor())
+ end)
+
it('is able to dump and restore jump list with different times (slow!)',
function()
nvim_command('edit ' .. testfilename_2)
diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua
index f845f6f93b..32598fc399 100644
--- a/test/functional/shada/shada_spec.lua
+++ b/test/functional/shada/shada_spec.lua
@@ -23,8 +23,6 @@ local wshada, _, shada_fname, clean =
local dirname = 'Xtest-functional-shada-shada.d'
local dirshada = dirname .. '/main.shada'
-if helpers.pending_win32(pending) then return end
-
describe('ShaDa support code', function()
before_each(reset)
after_each(function()
@@ -173,6 +171,7 @@ describe('ShaDa support code', function()
end
it('correctly uses shada-r option', function()
+ nvim_command('set shellslash')
meths.set_var('__home', paths.test_source_path)
nvim_command('let $HOME = __home')
nvim_command('unlet __home')
@@ -196,6 +195,7 @@ describe('ShaDa support code', function()
end)
it('correctly ignores case with shada-r option', function()
+ nvim_command('set shellslash')
local pwd = funcs.getcwd()
local relfname = 'абв/test'
local fname = pwd .. '/' .. relfname
@@ -240,6 +240,8 @@ describe('ShaDa support code', function()
end)
it('does not crash when ShaDa file directory is not writable', function()
+ if helpers.pending_win32(pending) then return end
+
funcs.mkdir(dirname, '', 0)
eq(0, funcs.filewritable(dirname))
set_additional_cmd('set shada=')
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index 35aeb6e67c..41ebfd2334 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -42,6 +42,7 @@ local function common_setup(screen, inccommand, text)
[14] = {foreground = Screen.colors.White, background = Screen.colors.Red},
[15] = {bold=true, foreground=Screen.colors.Blue},
[16] = {background=Screen.colors.Grey90}, -- cursorline
+ vis = {background=Screen.colors.LightGrey}
})
end
@@ -207,6 +208,42 @@ describe(":substitute, 'inccommand' preserves", function()
end)
end
+ for _, case in pairs{"", "split", "nosplit"} do
+ it("visual selection for non-previewable command (inccommand="..case..") #5888", function()
+ local screen = Screen.new(30,10)
+ common_setup(screen, case, default_text)
+ feed('1G2V')
+
+ feed(':s')
+ screen:expect([[
+ {vis:Inc substitution on} |
+ t{vis:wo lines} |
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ :'<,'>s^ |
+ ]])
+
+ feed('o')
+ screen:expect([[
+ {vis:Inc substitution on} |
+ t{vis:wo lines} |
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ :'<,'>so^ |
+ ]])
+ end)
+ end
+
end)
describe(":substitute, 'inccommand' preserves undo", function()
@@ -1201,6 +1238,40 @@ describe(":substitute, 'inccommand' with a failing expression", function()
end
end)
+ it('in the range does not error #5912', function()
+ for _, case in pairs(cases) do
+ refresh(case)
+ feed(':100s/')
+
+ screen:expect([[
+ Inc substitution on |
+ two lines |
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ :100s/^ |
+ ]])
+
+ feed('<enter>')
+ screen:expect([[
+ Inc substitution on |
+ two lines |
+ ^ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {14:E16: Invalid range} |
+ ]])
+ end
+ end)
+
end)
describe("'inccommand' and :cnoremap", function()
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index b729b0db08..17d949825a 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -153,9 +153,10 @@ describe('Mouse input', function()
end)
it('in tabline to the left moves tab left', function()
- if os.getenv("TRAVIS") and (helpers.os_name() == "osx"
- or os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN") then
- pending("[Fails on Travis macOS, ASAN_UBSAN. #4874]", function() end)
+ if helpers.skip_fragile(pending,
+ os.getenv("TRAVIS") and (helpers.os_name() == "osx"
+ or os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN")) -- #4874
+ then
return
end
@@ -257,9 +258,10 @@ describe('Mouse input', function()
end)
it('out of tabline to the left moves tab left', function()
- if os.getenv("TRAVIS") and (helpers.os_name() == "osx"
- or os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN") then
- pending("[Fails on Travis macOS, ASAN_UBSAN. #4874]", function() end)
+ if helpers.skip_fragile(pending,
+ os.getenv("TRAVIS") and (helpers.os_name() == "osx"
+ or os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN")) -- #4874
+ then
return
end
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 2581b36711..ef1f0783e7 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -1,31 +1,17 @@
--- This module contains the Screen class, a complete Nvim screen implementation
--- designed for functional testing. The goal is to provide a simple and
--- intuitive API for verifying screen state after a set of actions.
+-- This module contains the Screen class, a complete Nvim UI implementation
+-- designed for functional testing (verifying screen state, in particular).
--
--- The screen class exposes a single assertion method, "Screen:expect". This
--- method takes a string representing the expected screen state and an optional
--- set of attribute identifiers for checking highlighted characters(more on
--- this later).
---
--- The string passed to "expect" will be processed according to these rules:
---
--- - Each line of the string represents and is matched individually against
--- a screen row.
--- - The entire string is stripped of common indentation
--- - Expected screen rows are stripped of the last character. The last
--- character should be used to write pipes(|) that make clear where the
--- screen ends
--- - The last line is stripped, so the string must have (row count + 1)
--- lines.
+-- Screen:expect() takes a string representing the expected screen state and an
+-- optional set of attribute identifiers for checking highlighted characters.
--
-- Example usage:
--
-- local screen = Screen.new(25, 10)
--- -- attach the screen to the current Nvim instance
+-- -- Attach the screen to the current Nvim instance.
-- screen:attach()
--- --enter insert mode and type some text
+-- -- Enter insert-mode and type some text.
-- feed('ihello screen')
--- -- declare an expectation for the eventual screen state
+-- -- Assert the expected screen state.
-- screen:expect([[
-- hello screen |
-- ~ |
@@ -39,31 +25,19 @@
-- -- INSERT -- |
-- ]]) -- <- Last line is stripped
--
--- Since screen updates are received asynchronously, "expect" is actually
--- specifying the eventual screen state. This is how "expect" works: It will
--- start the event loop with a timeout of 5 seconds. Each time it receives an
--- update the expected state will be checked against the updated state.
---
--- If the expected state matches the current state, the event loop will be
--- stopped and "expect" will return. If the timeout expires, the last match
--- error will be reported and the test will fail.
+-- Since screen updates are received asynchronously, expect() actually specifies
+-- the _eventual_ screen state.
--
--- If the second argument is passed to "expect", the screen rows will be
--- transformed before being matched against the string lines. The
--- transformation rule is simple: Each substring "S" composed with characters
--- having the exact same set of attributes will be substituted by "{K:S}",
--- where K is a key associated the attribute set via the second argument of
--- "expect".
--- If a transformation table is present, unexpected attribute sets in the final
--- state is considered an error. To make testing simpler, a list of attribute
--- sets that should be ignored can be passed as a third argument. Alternatively,
--- this third argument can be "true" to indicate that all unexpected attribute
--- sets should be ignored.
+-- This is how expect() works:
+-- * It starts the event loop with a timeout.
+-- * Each time it receives an update it checks that against the expected state.
+-- * If the expected state matches the current state, the event loop will be
+-- stopped and expect() will return.
+-- * If the timeout expires, the last match error will be reported and the
+-- test will fail.
--
--- To illustrate how this works, let's say that in the above example we wanted
--- to assert that the "-- INSERT --" string is highlighted with the bold
--- attribute(which normally is), here's how the call to "expect" should look
--- like:
+-- Continuing the above example, say we want to assert that "-- INSERT --" is
+-- highlighted with the bold attribute. The expect() call should look like this:
--
-- NonText = Screen.colors.Blue
-- screen:expect([[
@@ -81,29 +55,20 @@
--
-- In this case "b" is a string associated with the set composed of one
-- attribute: bold. Note that since the {b:} markup is not a real part of the
--- screen, the delimiter(|) had to be moved right. Also, the highlighting of the
--- NonText markers (~) is ignored in this test.
+-- screen, the delimiter "|" moved to the right. Also, the highlighting of the
+-- NonText markers "~" is ignored in this test.
+--
+-- Tests will often share a group of attribute sets to expect(). Those can be
+-- defined at the beginning of a test:
--
--- Multiple expect:s will likely share a group of attribute sets to test.
--- Therefore these could be specified at the beginning of a test like this:
-- NonText = Screen.colors.Blue
-- screen:set_default_attr_ids( {
-- [1] = {reverse = true, bold = true},
-- [2] = {reverse = true}
-- })
-- screen:set_default_attr_ignore( {{}, {bold=true, foreground=NonText}} )
--- These can be overridden for a specific expect expression, by passing
--- different sets as parameters.
--
--- To help writing screen tests, there is a utility function
--- "screen:snapshot_util()", that can be placed in a test file at any point an
--- "expect(...)" should be. It will wait a short amount of time and then dump
--- the current state of the screen, in the form of an "expect(..)" expression
--- that would match it exactly. "snapshot_util" optionally also take the
--- transformation and ignore set as parameters, like expect, or uses the default
--- set. It will generate a larger attribute transformation set, if needed.
--- To generate a text-only test without highlight checks,
--- use `screen:snapshot_util({},true)`
+-- To help write screen tests, see screen:snapshot_util().
local helpers = require('test.functional.helpers')(nil)
local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths
@@ -205,13 +170,22 @@ end
function Screen:try_resize(columns, rows)
uimeths.try_resize(columns, rows)
+ -- Give ourselves a chance to _handle_resize, which requires using
+ -- self.sleep() (for the resize notification) rather than run()
+ self:sleep(0.1)
end
-- Asserts that `expected` eventually matches the screen state.
--
--- expected: Expected screen state (string).
--- attr_ids: Text attribute definitions.
--- attr_ignore: Ignored text attributes.
+-- expected: Expected screen state (string). Each line represents a screen
+-- row. Last character of each row (typically "|") is stripped.
+-- Common indentation is stripped.
+-- attr_ids: Expected text attributes. Screen rows are transformed according
+-- to this table, as follows: each substring S composed of
+-- characters having the same attributes will be substituted by
+-- "{K:S}", where K is a key in `attr_ids`. Any unexpected
+-- attributes in the final state are an error.
+-- attr_ignore: Ignored text attributes, or `true` to ignore all.
-- condition: Function asserting some arbitrary condition.
-- any: true: Succeed if `expected` matches ANY screen line(s).
-- false (default): `expected` must match screen exactly.
@@ -224,6 +198,11 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any)
row = row:sub(1, #row - 1)
table.insert(expected_rows, row)
end
+ if not any then
+ assert(self._height == #expected_rows,
+ "Expected screen state's row count(" .. #expected_rows
+ .. ') differs from configured height(' .. self._height .. ') of Screen.')
+ end
local ids = attr_ids or self._default_attr_ids
local ignore = attr_ignore or self._default_attr_ignore
self:wait(function()
@@ -541,8 +520,10 @@ function Screen:_current_screen()
return table.concat(rv, '\n')
end
+-- Utility to generate/debug tests. Call it where screen:expect() would be.
+-- Waits briefly, then dumps the current screen state in the form of
+-- screen:expect(). Use snapshot_util({},true) to generate a text-only test.
function Screen:snapshot_util(attrs, ignore)
- -- util to generate screen test
self:sleep(250)
self:print_snapshot(attrs, ignore)
end
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index d03f98c26f..0824585717 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -4,8 +4,7 @@ local spawn, set_session, clear = helpers.spawn, helpers.set_session, helpers.cl
local feed, execute = helpers.feed, helpers.execute
local insert = helpers.insert
local eq = helpers.eq
-
-if helpers.pending_win32(pending) then return end
+local eval = helpers.eval
describe('Initial screen', function()
local screen
@@ -563,11 +562,10 @@ describe('Screen', function()
]])
end)
- -- FIXME this has some race conditions that cause it to fail periodically
- pending('has minimum width/height values', function()
+ it('has minimum width/height values', function()
screen:try_resize(1, 1)
screen:expect([[
- -- INS^ERT --|
+ {2:-- INS^ERT --}|
|
]])
feed('<esc>:ls')
@@ -690,4 +688,12 @@ describe('Screen', function()
end)
end)
end)
+
+ it('nvim_ui_attach() handles very large width/height #2180', function()
+ screen:detach()
+ screen = Screen.new(999, 999)
+ screen:attach()
+ eq(999, eval('&lines'))
+ eq(999, eval('&columns'))
+ end)
end)
diff --git a/test/helpers.lua b/test/helpers.lua
index 4c50c7644f..0bc62da5d7 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -52,9 +52,49 @@ local function check_logs()
assert(0 == runtime_errors)
end
+-- Tries to get platform name from $SYSTEM_NAME, uname; fallback is "Windows".
+local uname = (function()
+ local platform = nil
+ return (function()
+ if platform then
+ return platform
+ end
+
+ platform = os.getenv("SYSTEM_NAME")
+ if platform then
+ return platform
+ end
+
+ local status, f = pcall(io.popen, "uname -s")
+ if status then
+ platform = f:read("*l")
+ else
+ platform = 'Windows'
+ end
+ return platform
+ end)
+end)()
+
+local function tmpname()
+ local fname = os.tmpname()
+ if uname() == 'Windows' and fname:sub(1, 2) == '\\s' then
+ -- In Windows tmpname() returns a filename starting with
+ -- special sequence \s, prepend $TEMP path
+ local tmpdir = os.getenv('TEMP')
+ return tmpdir..fname
+ elseif fname:match('^/tmp') and uname() == 'Darwin' then
+ -- In OS X /tmp links to /private/tmp
+ return '/private'..fname
+ else
+ return fname
+ end
+end
+
return {
eq = eq,
neq = neq,
ok = ok,
check_logs = check_logs,
+ uname = uname,
+ tmpname = tmpname,
}
diff --git a/test/unit/api/private_helpers_spec.lua b/test/unit/api/private_helpers_spec.lua
index 1d7c03787b..8c54ea6a2a 100644
--- a/test/unit/api/private_helpers_spec.lua
+++ b/test/unit/api/private_helpers_spec.lua
@@ -7,6 +7,7 @@ local NULL = helpers.NULL
local eq = helpers.eq
local lua2typvalt = eval_helpers.lua2typvalt
+local typvalt2lua = eval_helpers.typvalt2lua
local typvalt = eval_helpers.typvalt
local nil_value = api_helpers.nil_value
@@ -14,6 +15,7 @@ local list_type = api_helpers.list_type
local int_type = api_helpers.int_type
local type_key = api_helpers.type_key
local obj2lua = api_helpers.obj2lua
+local func_type = api_helpers.func_type
local api = cimport('./src/nvim/api/private/helpers.h')
@@ -85,4 +87,19 @@ describe('vim_to_object', function()
eq(nil, tt.vval.v_dict)
eq({}, obj2lua(api.vim_to_object(tt)))
end)
+
+ it('regression: partials in a list', function()
+ local llist = {
+ {
+ [type_key]=func_type,
+ value='printf',
+ args={'%s'},
+ dict={v=1},
+ },
+ {},
+ }
+ local list = lua2typvalt(llist)
+ eq(llist, typvalt2lua(list))
+ eq({nil_value, {}}, obj2lua(api.vim_to_object(list)))
+ end)
end)
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index 45fbf8da5c..c3c27e4fed 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -5,10 +5,12 @@ local to_cstr = helpers.to_cstr
local ffi = helpers.ffi
local eq = helpers.eq
-local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h')
+local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h',
+ './src/nvim/hashtab.h')
local null_string = {[true]='NULL string'}
local null_list = {[true]='NULL list'}
+local null_dict = {[true]='NULL dict'}
local type_key = {[true]='type key'}
local list_type = {[true]='list type'}
local dict_type = {[true]='dict type'}
@@ -18,27 +20,28 @@ local flt_type = {[true]='flt type'}
local nil_value = {[true]='nil'}
+local lua2typvalt
+
+local function li_alloc(nogc)
+ local gcfunc = eval.listitem_free
+ if nogc then gcfunc = nil end
+ local li = ffi.gc(eval.listitem_alloc(), gcfunc)
+ li.li_next = nil
+ li.li_prev = nil
+ li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED}
+ return li
+end
+
local function list(...)
local ret = ffi.gc(eval.list_alloc(), eval.list_unref)
eq(0, ret.lv_refcount)
ret.lv_refcount = 1
for i = 1, select('#', ...) do
local val = select(i, ...)
- local typ = type(val)
- if typ == 'string' then
- eval.list_append_string(ret, to_cstr(val))
- elseif typ == 'table' and val == null_string then
- eval.list_append_string(ret, nil)
- elseif typ == 'table' and val == null_list then
- eval.list_append_list(ret, nil)
- elseif typ == 'table' and val[type_key] == list_type then
- local itemlist = ffi.gc(list(table.unpack(val)), nil)
- eq(1, itemlist.lv_refcount)
- itemlist.lv_refcount = 0
- eval.list_append_list(ret, itemlist)
- else
- assert(false, 'Not implemented yet')
- end
+ local li_tv = ffi.gc(lua2typvalt(val), nil)
+ local li = li_alloc(true)
+ li.li_tv = li_tv
+ eval.tv_list_append(ret, li)
end
return ret
end
@@ -49,9 +52,14 @@ local special_tab = {
[eval.kSpecialVarTrue] = true,
}
+local ptr2key = function(ptr)
+ return tostring(ptr)
+end
+
local lst2tbl
local dct2tbl
+local typvalt2lua
local typvalt2lua_tab
typvalt2lua_tab = {
@@ -72,33 +80,88 @@ typvalt2lua_tab = {
return ffi.string(str)
end
end,
- [tonumber(eval.VAR_LIST)] = function(t)
- return lst2tbl(t.vval.v_list)
+ [tonumber(eval.VAR_LIST)] = function(t, processed)
+ return lst2tbl(t.vval.v_list, processed)
end,
- [tonumber(eval.VAR_DICT)] = function(t)
- return dct2tbl(t.vval.v_dict)
+ [tonumber(eval.VAR_DICT)] = function(t, processed)
+ return dct2tbl(t.vval.v_dict, processed)
end,
- [tonumber(eval.VAR_FUNC)] = function(t)
- return {[type_key]=func_type, value=typvalt2lua_tab[eval.VAR_STRING](t)}
+ [tonumber(eval.VAR_FUNC)] = function(t, processed)
+ return {[type_key]=func_type, value=typvalt2lua_tab[eval.VAR_STRING](t, processed or {})}
+ end,
+ [tonumber(eval.VAR_PARTIAL)] = function(t, processed)
+ local p_key = ptr2key(t)
+ if processed[p_key] then
+ return processed[p_key]
+ end
+ local pt = t.vval.v_partial
+ local value, auto, dict, argv = nil, nil, nil, nil
+ if pt ~= nil then
+ value = ffi.string(pt.pt_name)
+ auto = pt.pt_auto and true or nil
+ argv = {}
+ for i = 1, pt.pt_argc do
+ argv[i] = typvalt2lua(pt.pt_argv[i - 1], processed)
+ end
+ if pt.pt_dict ~= nil then
+ dict = dct2tbl(pt.pt_dict)
+ end
+ end
+ return {
+ [type_key]=func_type,
+ value=value,
+ auto=auto,
+ args=argv,
+ dict=dict,
+ }
end,
}
-local typvalt2lua = function(t)
+typvalt2lua = function(t, processed)
return ((typvalt2lua_tab[tonumber(t.v_type)] or function(t_inner)
assert(false, 'Converting ' .. tonumber(t_inner.v_type) .. ' was not implemented yet')
- end)(t))
+ end)(t, processed or {}))
end
-lst2tbl = function(l)
- local ret = {[type_key]=list_type}
+local function list_iter(l)
+ local init_s = {
+ idx=0,
+ li=l.lv_first,
+ }
+ local function f(s, _)
+ -- (listitem_T *) NULL is equal to nil, but yet it is not false.
+ if s.li == nil then
+ return nil
+ end
+ local ret_li = s.li
+ s.li = s.li.li_next
+ s.idx = s.idx + 1
+ return s.idx, ret_li
+ end
+ return f, init_s, nil
+end
+
+local function list_items(l)
+ local ret = {}
+ for i, li in list_iter(l) do
+ ret[i] = li
+ end
+ return ret
+end
+
+lst2tbl = function(l, processed)
if l == nil then
- return ret
+ return null_list
+ end
+ processed = processed or {}
+ local p_key = ptr2key(l)
+ if processed[p_key] then
+ return processed[p_key]
end
- local li = l.lv_first
- -- (listitem_T *) NULL is equal to nil, but yet it is not false.
- while li ~= nil do
- ret[#ret + 1] = typvalt2lua(li.li_tv)
- li = li.li_next
+ local ret = {[type_key]=list_type}
+ processed[p_key] = ret
+ for i, li in list_iter(l) do
+ ret[i] = typvalt2lua(li.li_tv, processed)
end
if ret[1] then
ret[type_key] = nil
@@ -106,16 +169,71 @@ lst2tbl = function(l)
return ret
end
-dct2tbl = function(d)
- local ret = {d=d}
- assert(false, 'Converting dictionaries is not implemented yet')
+local hi_key_removed = eval._hash_key_removed()
+
+local function dict_iter(d, return_hi)
+ local init_s = {
+ todo=d.dv_hashtab.ht_used,
+ hi=d.dv_hashtab.ht_array,
+ }
+ local function f(s, _)
+ if s.todo == 0 then return nil end
+ while s.todo > 0 do
+ if s.hi.hi_key ~= nil and s.hi.hi_key ~= hi_key_removed then
+ local key = ffi.string(s.hi.hi_key)
+ local ret
+ if return_hi then
+ ret = s.hi
+ else
+ ret = ffi.cast('dictitem_T*',
+ s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_key'))
+ end
+ s.todo = s.todo - 1
+ s.hi = s.hi + 1
+ return key, ret
+ end
+ s.hi = s.hi + 1
+ end
+ end
+ return f, init_s, nil
+end
+
+local function first_di(d)
+ local f, init_s, v = dict_iter(d)
+ return select(2, f(init_s, v))
+end
+
+local function dict_items(d)
+ local ret = {[0]=0}
+ for k, hi in dict_iter(d) do
+ ret[k] = hi
+ ret[0] = ret[0] + 1
+ ret[ret[0]] = hi
+ end
return ret
end
-local lua2typvalt
+dct2tbl = function(d, processed)
+ if d == nil then
+ return null_dict
+ end
+ processed = processed or {}
+ local p_key = ptr2key(d)
+ if processed[p_key] then
+ return processed[p_key]
+ end
+ local ret = {}
+ processed[p_key] = ret
+ for k, di in dict_iter(d) do
+ ret[k] = typvalt2lua(di.di_tv, processed)
+ end
+ return ret
+end
local typvalt = function(typ, vval)
- if type(typ) == 'string' then
+ if typ == nil then
+ typ = eval.VAR_UNKNOWN
+ elseif type(typ) == 'string' then
typ = eval[typ]
end
return ffi.gc(ffi.new('typval_T', {v_type=typ, vval=vval}), eval.clear_tv)
@@ -164,12 +282,68 @@ local lua2typvalt_type_tab = {
end
return ret
end,
+ [func_type] = function(l, processed)
+ if processed[l] then
+ processed[l].pt_refcount = processed[l].pt_refcount + 1
+ return typvalt(eval.VAR_PARTIAL, {v_partial=processed[l]})
+ end
+ if l.args or l.dict then
+ local pt = ffi.gc(ffi.cast('partial_T*', eval.xmalloc(ffi.sizeof('partial_T'))), nil)
+ processed[l] = pt
+ local argv = nil
+ if l.args and #l.args > 0 then
+ argv = ffi.gc(ffi.cast('typval_T*', eval.xmalloc(ffi.sizeof('typval_T') * #l.args)), nil)
+ for i, arg in ipairs(l.args) do
+ local arg_tv = ffi.gc(lua2typvalt(arg, processed), nil)
+ eval.copy_tv(arg_tv, argv[i - 1])
+ eval.clear_tv(arg_tv)
+ end
+ end
+ local dict = nil
+ if l.dict then
+ local dict_tv = ffi.gc(lua2typvalt(l.dict, processed), nil)
+ assert(dict_tv.v_type == eval.VAR_DICT)
+ dict = dict_tv.vval.v_dict
+ end
+ pt.pt_refcount = 1
+ pt.pt_name = eval.xmemdupz(to_cstr(l.value), #l.value)
+ pt.pt_auto = not not l.auto
+ pt.pt_argc = l.args and #l.args or 0
+ pt.pt_argv = argv
+ pt.pt_dict = dict
+ return typvalt(eval.VAR_PARTIAL, {v_partial=pt})
+ else
+ return typvalt(eval.VAR_FUNC, {
+ v_string=eval.xmemdupz(to_cstr(l.value), #l.value)
+ })
+ end
+ end,
}
+local special_vals = {
+ [null_string] = {eval.VAR_STRING, {v_string=ffi.cast('char_u*', nil)}},
+ [null_list] = {eval.VAR_LIST, {v_list=ffi.cast('list_T*', nil)}},
+ [null_dict] = {eval.VAR_DICT, {v_dict=ffi.cast('dict_T*', nil)}},
+ [nil_value] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarNull}},
+ [true] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarTrue}},
+ [false] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarFalse}},
+}
+
+for k, v in pairs(special_vals) do
+ local tmp = function(typ, vval)
+ special_vals[k] = function()
+ return typvalt(typ, vval)
+ end
+ end
+ tmp(v[1], v[2])
+end
+
lua2typvalt = function(l, processed)
processed = processed or {}
if l == nil or l == nil_value then
- return typvalt(eval.VAR_SPECIAL, {v_special=eval.kSpecialVarNull})
+ return special_vals[nil_value]()
+ elseif special_vals[l] then
+ return special_vals[l]()
elseif type(l) == 'table' then
if l[type_key] then
return lua2typvalt_type_tab[l[type_key]](l, processed)
@@ -182,18 +356,35 @@ lua2typvalt = function(l, processed)
end
elseif type(l) == 'number' then
return typvalt(eval.VAR_FLOAT, {v_float=l})
- elseif type(l) == 'boolean' then
- return typvalt(eval.VAR_SPECIAL, {
- v_special=(l and eval.kSpecialVarTrue or eval.kSpecialVarFalse)
- })
elseif type(l) == 'string' then
return typvalt(eval.VAR_STRING, {v_string=eval.xmemdupz(to_cstr(l), #l)})
+ elseif type(l) == 'cdata' then
+ local tv = typvalt(eval.VAR_UNKNOWN)
+ eval.tv_copy(l, tv)
+ return tv
end
end
+local function void(ptr)
+ return ffi.cast('void*', ptr)
+end
+
+local alloc_logging_helpers = {
+ list = function(l) return {func='calloc', args={1, ffi.sizeof('list_T')}, ret=void(l)} end,
+ li = function(li) return {func='malloc', args={ffi.sizeof('listitem_T')}, ret=void(li)} end,
+ dict = function(d) return {func='malloc', args={ffi.sizeof('dict_T')}, ret=void(d)} end,
+ di = function(di, size)
+ return {func='malloc', args={ffi.offsetof('dictitem_T', 'di_key') + size + 1}, ret=void(di)}
+ end,
+ str = function(s, size) return {func='malloc', args={size + 1}, ret=void(s)} end,
+
+ freed = function(p) return {func='free', args={p and void(p)}} end,
+}
+
return {
null_string=null_string,
null_list=null_list,
+ null_dict=null_dict,
list_type=list_type,
dict_type=dict_type,
func_type=func_type,
@@ -212,4 +403,15 @@ return {
typvalt2lua=typvalt2lua,
typvalt=typvalt,
+
+ li_alloc=li_alloc,
+
+ dict_iter=dict_iter,
+ list_iter=list_iter,
+ first_di=first_di,
+
+ alloc_logging_helpers=alloc_logging_helpers,
+
+ list_items=list_items,
+ dict_items=dict_items,
}
diff --git a/test/unit/eval/tv_clear_spec.lua b/test/unit/eval/tv_clear_spec.lua
new file mode 100644
index 0000000000..96eccdbd71
--- /dev/null
+++ b/test/unit/eval/tv_clear_spec.lua
@@ -0,0 +1,127 @@
+local helpers = require('test.unit.helpers')
+local eval_helpers = require('test.unit.eval.helpers')
+
+local alloc_log_new = helpers.alloc_log_new
+local cimport = helpers.cimport
+local ffi = helpers.ffi
+local eq = helpers.eq
+
+local a = eval_helpers.alloc_logging_helpers
+local type_key = eval_helpers.type_key
+local list_type = eval_helpers.list_type
+local list_items = eval_helpers.list_items
+local dict_items = eval_helpers.dict_items
+local lua2typvalt = eval_helpers.lua2typvalt
+
+local lib = cimport('./src/nvim/eval_defs.h', './src/nvim/eval.h')
+
+local alloc_log = alloc_log_new()
+
+before_each(function()
+ alloc_log:before_each()
+end)
+
+after_each(function()
+ alloc_log:after_each()
+end)
+
+describe('clear_tv()', function()
+ it('successfully frees all lists in [&l [1], *l, *l]', function()
+ local l_inner = {1}
+ local list = {l_inner, l_inner, l_inner}
+ local list_tv = ffi.gc(lua2typvalt(list), nil)
+ local list_p = list_tv.vval.v_list
+ local lis = list_items(list_p)
+ local list_inner_p = lis[1].li_tv.vval.v_list
+ local lis_inner = list_items(list_inner_p)
+ alloc_log:check({
+ a.list(list_p),
+ a.list(list_inner_p),
+ a.li(lis_inner[1]),
+ a.li(lis[1]),
+ a.li(lis[2]),
+ a.li(lis[3]),
+ })
+ eq(3, list_inner_p.lv_refcount)
+ lib.clear_tv(list_tv)
+ alloc_log:check({
+ a.freed(lis_inner[1]),
+ a.freed(list_inner_p),
+ a.freed(lis[1]),
+ a.freed(lis[2]),
+ a.freed(lis[3]),
+ a.freed(list_p),
+ })
+ end)
+ it('successfully frees all lists in [&l [], *l, *l]', function()
+ local l_inner = {[type_key]=list_type}
+ local list = {l_inner, l_inner, l_inner}
+ local list_tv = ffi.gc(lua2typvalt(list), nil)
+ local list_p = list_tv.vval.v_list
+ local lis = list_items(list_p)
+ local list_inner_p = lis[1].li_tv.vval.v_list
+ alloc_log:check({
+ a.list(list_p),
+ a.list(list_inner_p),
+ a.li(lis[1]),
+ a.li(lis[2]),
+ a.li(lis[3]),
+ })
+ eq(3, list_inner_p.lv_refcount)
+ lib.clear_tv(list_tv)
+ alloc_log:check({
+ a.freed(list_inner_p),
+ a.freed(lis[1]),
+ a.freed(lis[2]),
+ a.freed(lis[3]),
+ a.freed(list_p),
+ })
+ end)
+ it('successfully frees all dictionaries in [&d {}, *d]', function()
+ local d_inner = {}
+ local list = {d_inner, d_inner}
+ local list_tv = ffi.gc(lua2typvalt(list), nil)
+ local list_p = list_tv.vval.v_list
+ local lis = list_items(list_p)
+ local dict_inner_p = lis[1].li_tv.vval.v_dict
+ alloc_log:check({
+ a.list(list_p),
+ a.dict(dict_inner_p),
+ a.li(lis[1]),
+ a.li(lis[2]),
+ })
+ eq(2, dict_inner_p.dv_refcount)
+ lib.clear_tv(list_tv)
+ alloc_log:check({
+ a.freed(dict_inner_p),
+ a.freed(lis[1]),
+ a.freed(lis[2]),
+ a.freed(list_p),
+ })
+ end)
+ it('successfully frees all dictionaries in [&d {a: 1}, *d]', function()
+ local d_inner = {a=1}
+ local list = {d_inner, d_inner}
+ local list_tv = ffi.gc(lua2typvalt(list), nil)
+ local list_p = list_tv.vval.v_list
+ local lis = list_items(list_p)
+ local dict_inner_p = lis[1].li_tv.vval.v_dict
+ local dis = dict_items(dict_inner_p)
+ alloc_log:check({
+ a.list(list_p),
+ a.dict(dict_inner_p),
+ a.di(dis.a, 1),
+ a.li(lis[1]),
+ a.li(lis[2]),
+ })
+ eq(2, dict_inner_p.dv_refcount)
+ lib.clear_tv(list_tv)
+ alloc_log:check({
+ a.freed(dis.a),
+ a.freed(dict_inner_p),
+ a.freed(lis[1]),
+ a.freed(lis[2]),
+ a.freed(list_p),
+ })
+ end)
+end)
diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua
index 00637e0b8d..e288081960 100644
--- a/test/unit/formatc.lua
+++ b/test/unit/formatc.lua
@@ -219,13 +219,7 @@ local function standalone(...) -- luacheck: ignore
Preprocess.add_to_include_path('./../../build/include')
Preprocess.add_to_include_path('./../../.deps/usr/include')
- local input = Preprocess.preprocess_stream(arg[1])
- local raw = input:read('*all')
- input:close()
-
- if raw == nil then
- print("ERROR: Preprocess.preprocess_stream():read() returned empty")
- end
+ local raw = Preprocess.preprocess('', arg[1])
local formatted
if #arg == 2 and arg[2] == 'no' then
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 3564f76442..1bfdd32739 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -9,6 +9,12 @@ local neq = global_helpers.neq
local eq = global_helpers.eq
local ok = global_helpers.ok
+-- C constants.
+local NULL = ffi.cast('void*', 0)
+
+local OK = 1
+local FAIL = 0
+
-- add some standard header locations
for _, p in ipairs(Paths.include_paths) do
Preprocess.add_to_include_path(p)
@@ -45,6 +51,8 @@ local function filter_complex_blocks(body)
return table.concat(result, "\n")
end
+local previous_defines = ''
+
-- 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.
local function cimport(...)
@@ -66,17 +74,8 @@ local function cimport(...)
return libnvim
end
- local body = nil
- for _ = 1, 10 do
- local stream = Preprocess.preprocess_stream(unpack(paths))
- body = stream:read("*a")
- stream:close()
- if body ~= nil then break end
- end
-
- if body == nil then
- print("ERROR: helpers.lua: Preprocess.preprocess_stream():read() returned empty")
- end
+ local body
+ body, previous_defines = Preprocess.preprocess(previous_defines, unpack(paths))
-- format it (so that the lines are "unique" statements), also filter out
-- Objective-C blocks
@@ -125,6 +124,67 @@ local function cppimport(path)
return cimport(Paths.test_include_path .. '/' .. path)
end
+local function alloc_log_new()
+ local log = {
+ log={},
+ lib=cimport('./src/nvim/memory.h'),
+ original_functions={},
+ null={['\0:is_null']=true},
+ }
+ local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'}
+ function log:save_original_functions()
+ for _, funcname in ipairs(allocator_functions) do
+ self.original_functions[funcname] = self.lib['mem_' .. funcname]
+ end
+ end
+ 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={...}}
+ self.log[#self.log + 1] = log_entry
+ if kk == 'free' then
+ self.original_functions[kk](...)
+ else
+ log_entry.ret = self.original_functions[kk](...)
+ end
+ for i, v in ipairs(log_entry.args) do
+ if v == nil then
+ -- XXX This thing thinks that {NULL} ~= {NULL}.
+ log_entry.args[i] = self.null
+ end
+ end
+ if self.hook then self:hook(log_entry) end
+ if log_entry.ret then
+ return log_entry.ret
+ end
+ end
+ end
+ end
+ end
+ function log:clear()
+ self.log = {}
+ end
+ function log:check(exp)
+ eq(exp, self.log)
+ self:clear()
+ end
+ function log:restore_original_functions()
+ for k, v in pairs(self.original_functions) do
+ self.lib['mem_' .. k] = v
+ end
+ end
+ function log:before_each()
+ log:save_original_functions()
+ log:set_mocks()
+ end
+ function log:after_each()
+ log:restore_original_functions()
+ end
+ return log
+end
+
cimport('./src/nvim/types.h')
-- take a pointer to a C-allocated string and return an interned
@@ -136,7 +196,7 @@ end
local cstr = ffi.typeof('char[?]')
local function to_cstr(string)
- return cstr((string.len(string)) + 1, string)
+ return cstr(#string + 1, string)
end
-- initialize some global variables, this is still necessary to unit test
@@ -149,12 +209,6 @@ do
main.event_init()
end
--- C constants.
-local NULL = ffi.cast('void*', 0)
-
-local OK = 1
-local FAIL = 0
-
return {
cimport = cimport,
cppimport = cppimport,
@@ -168,5 +222,6 @@ return {
to_cstr = to_cstr,
NULL = NULL,
OK = OK,
- FAIL = FAIL
+ FAIL = FAIL,
+ alloc_log_new = alloc_log_new,
}
diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua
index 9b76834383..ccaf0228ab 100644
--- a/test/unit/path_spec.lua
+++ b/test/unit/path_spec.lua
@@ -336,6 +336,17 @@ describe('more path function', function()
eq(FAIL, result)
end)
+ it('fails safely if given length is wrong #5737', function()
+ local force_expansion = 1
+ local filename = 'foo/bar/bazzzzzzz/buz/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/a'
+ local too_short_len = 8
+ local buf = cstr(too_short_len, '')
+ local result = path.vim_FullName(filename, buf, too_short_len, force_expansion)
+ local expected = string.sub(filename, 1, (too_short_len - 1))
+ eq(expected, (ffi.string(buf)))
+ eq(FAIL, result)
+ end)
+
it('uses the filename if the filename is a URL', function()
local force_expansion = 1
local filename = 'http://www.neovim.org'
diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua
index 10ba997758..1c9b290462 100644
--- a/test/unit/preprocess.lua
+++ b/test/unit/preprocess.lua
@@ -7,22 +7,22 @@ local ccs = {}
local env_cc = os.getenv("CC")
if env_cc then
- table.insert(ccs, {path = "/usr/bin/env " .. tostring(env_cc), type = "gcc"})
+ table.insert(ccs, {path = {"/usr/bin/env", env_cc}, type = "gcc"})
end
if ffi.os == "Windows" then
- table.insert(ccs, {path = "cl", type = "msvc"})
+ table.insert(ccs, {path = {"cl"}, type = "msvc"})
end
-table.insert(ccs, {path = "/usr/bin/env cc", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc-4.9", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc-4.8", type = "gcc"})
-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"})
+table.insert(ccs, {path = {"/usr/bin/env", "cc"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.9"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.8"}, type = "gcc"})
+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 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, "'", [['"'"']]) .. "'"
@@ -61,12 +61,12 @@ 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}
+-- headerize({"stdio.h", "math.h"}, true)
-- produces:
-- #include <stdio.h>
-- #include <math.h>
--
--- headerize({"vim.h", "memory.h", false}
+-- headerize({"vim.h", "memory.h"}, false)
-- produces:
-- #include "vim.h"
-- #include "memory.h"
@@ -79,8 +79,7 @@ local function headerize(headers, global)
end
local formatted = {}
- for i = 1, #headers do
- local hdr = headers[i]
+ for _, hdr in ipairs(headers) do
formatted[#formatted + 1] = "#include " ..
tostring(pre) ..
tostring(hdr) ..
@@ -91,49 +90,78 @@ local function headerize(headers, global)
end
local Gcc = {
+ preprocessor_extra_flags = {},
+ get_defines_extra_flags = {'-std=c99', '-dM', '-E'},
+ get_declarations_extra_flags = {'-std=c99', '-P', '-E'},
+}
+
+function Gcc:define(name, args, val)
+ local define = '-D' .. name
+ if args ~= nil then
+ define = define .. '(' .. table.concat(args, ',') .. ')'
+ end
+ if val ~= nil then
+ define = 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)
+end
+
+function Gcc:init_defines()
-- preprocessor flags that will hopefully make the compiler produce C
-- declarations that the LuaJIT ffi understands.
- preprocessor_extra_flags = {
- '-D "aligned(ARGS)="',
- '-D "__attribute__(ARGS)="',
- '-D "__asm(ARGS)="',
- '-D "__asm__(ARGS)="',
- '-D "__inline__="',
- '-D "EXTERN=extern"',
- '-D "INIT(...)="',
- '-D_GNU_SOURCE',
- '-DINCLUDE_GENERATED_DECLARATIONS',
-
- -- Needed for FreeBSD
- '-D "_Thread_local="',
-
- -- Needed for macOS Sierra
- '-D "_Nullable="',
- '-D "_Nonnull="',
- '-U__BLOCKS__',
- }
-}
+ self:define('aligned', {'ARGS'}, '')
+ self:define('__attribute__', {'ARGS'}, '')
+ self:define('__asm', {'ARGS'}, '')
+ self:define('__asm__', {'ARGS'}, '')
+ self:define('__inline__', nil, '')
+ self:define('EXTERN', nil, 'extern')
+ self:define('INIT', {'...'}, '')
+ self:define('_GNU_SOURCE')
+ self:define('INCLUDE_GENERATED_DECLARATIONS')
+ self:define('UNIT_TESTING')
+ -- Needed for FreeBSD
+ self:define('_Thread_local', nil, '')
+ -- Needed for macOS Sierra
+ self:define('_Nullable', nil, '')
+ self:define('_Nonnull', nil, '')
+ self:undefine('__BLOCKS__')
+end
function Gcc:new(obj)
obj = obj or {}
setmetatable(obj, self)
self.__index = self
+ self:init_defines()
return obj
end
function Gcc:add_to_include_path(...)
- local paths = {...}
- for i = 1, #paths do
- local path = paths[i]
- local directive = '-I ' .. '"' .. path .. '"'
+ for i = 1, select('#', ...) do
+ local path = select(i, ...)
local ef = self.preprocessor_extra_flags
- ef[#ef + 1] = directive
+ ef[#ef + 1] = '-I' .. 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 out = io.popen(tostring(self.path) .. " -M " .. tostring(hdr) .. " 2>&1")
+ local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1'
+ local out = io.popen(cmd)
local deps = out:read("*a")
out:close()
if deps then
@@ -143,23 +171,51 @@ 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
+
-- 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.
-function Gcc:preprocess_stream(...)
+function Gcc:preprocess(previous_defines, ...)
-- create pseudo-header
local pseudoheader = headerize({...}, false)
- local defines = table.concat(self.preprocessor_extra_flags, ' ')
- local cmd = ("echo $hdr | " ..
- tostring(self.path) ..
- " " ..
- tostring(defines) ..
- " -std=c99 -P -E -"):gsub('$hdr', shell_quote(pseudoheader))
+ local pseudoheader_fname = 'tmp_pseudoheader.h'
+ local pseudoheader_file = 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_call(self.path, self.preprocessor_extra_flags,
+ self.get_defines_extra_flags,
+ {pseudoheader_fname})
+
-- lfs = require("lfs")
-- print("CWD: #{lfs.currentdir!}")
-- print("CMD: #{cmd}")
-- io.stderr\write("CWD: #{lfs.currentdir!}\n")
-- io.stderr\write("CMD: #{cmd}\n")
- return io.popen(cmd)
+
+ local declarations = repeated_call(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()
@@ -197,8 +253,8 @@ return {
includes = function(hdr)
return cc:dependencies(hdr)
end,
- preprocess_stream = function(...)
- return cc:preprocess_stream(...)
+ preprocess = function(...)
+ return cc:preprocess(...)
end,
add_to_include_path = function(...)
return cc:add_to_include_path(...)