Files
neovim/test/functional/core/startup_spec.lua
Pablo Arias 8e739af064 fix(startup): multiprocess startuptime #26790
Problem:
Since 2448816956, the --startuptime report shows
two blocks of data. The TUI process and its embedded nvim process write to the
file concurrently, which may interleave the two startup sequences into the same
timeline.

Solution:
Report each process as a separate section in the same file.
1. Each process buffers the full report.
2. After startup is finished, the buffer is flushed (appended) to the file.

Fix #23036
Sample report:

    --- Startup times for process: Primary/TUI ---

    times in msec
     clock   self+sourced   self:  sourced script
     clock   elapsed:              other lines

    000.006  000.006: --- NVIM STARTING ---
    000.428  000.422: event init
    000.728  000.301: early init
    ...
    005.880  000.713: init highlight
    005.882  000.002: --- NVIM STARTED ---

    --- Startup times for process: Embedded ---

    times in msec
     clock   self+sourced   self:  sourced script
     clock   elapsed:              other lines

    000.006  000.006: --- NVIM STARTING ---
    000.409  000.403: event init
    000.557  000.148: early init
    000.633  000.077: locale set
    ...
    014.383  000.430: first screen update
    014.387  000.003: --- NVIM STARTED ---
2024-02-09 14:56:52 -08:00

1404 lines
39 KiB
Lua

local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local assert_alive = helpers.assert_alive
local assert_log = helpers.assert_log
local clear = helpers.clear
local command = helpers.command
local ok = helpers.ok
local eq = helpers.eq
local matches = helpers.matches
local eval = helpers.eval
local exec = helpers.exec
local exec_capture = helpers.exec_capture
local exec_lua = helpers.exec_lua
local feed = helpers.feed
local fn = helpers.fn
local pesc = vim.pesc
local mkdir = helpers.mkdir
local mkdir_p = helpers.mkdir_p
local nvim_prog = helpers.nvim_prog
local nvim_set = helpers.nvim_set
local read_file = helpers.read_file
local retry = helpers.retry
local rmdir = helpers.rmdir
local sleep = vim.uv.sleep
local startswith = vim.startswith
local write_file = helpers.write_file
local api = helpers.api
local alter_slashes = helpers.alter_slashes
local is_os = helpers.is_os
local dedent = helpers.dedent
local tbl_map = vim.tbl_map
local tbl_filter = vim.tbl_filter
local endswith = vim.endswith
describe('startup', function()
it('--clean', function()
clear()
ok(
string.find(
alter_slashes(api.nvim_get_option_value('runtimepath', {})),
fn.stdpath('config'),
1,
true
) ~= nil
)
clear('--clean')
ok(
string.find(
alter_slashes(api.nvim_get_option_value('runtimepath', {})),
fn.stdpath('config'),
1,
true
) == nil
)
end)
it('prevents remote UI infinite loop', function()
clear()
local screen
screen = Screen.new(84, 3)
screen:attach()
fn.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' })
screen:expect([[
^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) |
|*2
]])
end)
it('--startuptime', function()
local testfile = 'Xtest_startuptime'
finally(function()
os.remove(testfile)
end)
clear({ args = { '--startuptime', testfile } })
assert_log('Embedded', testfile, 100)
assert_log('sourcing', testfile, 100)
assert_log("require%('vim%._editor'%)", testfile, 100)
end)
it('-D does not hang #12647', function()
clear()
local screen
screen = Screen.new(60, 7)
screen:attach()
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'set noruler',
'-D',
}, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect([[
^ |
|
Entering Debug mode. Type "cont" to continue. |
nvim_exec2() |
cmd: aunmenu * |
> |
|
]])
fn.chansend(id, 'cont\n')
screen:expect([[
^ |
~ |*3
[No Name] |
|*2
]])
end)
end)
describe('startup', function()
before_each(clear)
describe('-l Lua', function()
local function assert_l_out(expected, nvim_args, lua_args, script, input)
local args = { nvim_prog }
vim.list_extend(args, nvim_args or {})
vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') })
vim.list_extend(args, lua_args or {})
local out = fn.system(args, input):gsub('\r\n', '\n')
return eq(dedent(expected), out)
end
it('failure modes', function()
-- nvim -l <empty>
matches('nvim%.?e?x?e?: Argument missing after: "%-l"', fn.system({ nvim_prog, '-l' }))
eq(1, eval('v:shell_error'))
end)
it('os.exit() sets Nvim exitcode', function()
-- tricky: LeakSanitizer triggers on os.exit() and disrupts the return value, disable it
exec_lua [[
local asan_options = os.getenv('ASAN_OPTIONS') or ''
if asan_options ~= '' then
asan_options = asan_options .. ':'
end
vim.uv.os_setenv('ASAN_OPTIONS', asan_options .. ':detect_leaks=0')
]]
-- nvim -l foo.lua -arg1 -- a b c
assert_l_out(
[[
bufs:
nvim args: 7
lua args: { "-arg1", "--exitcode", "73", "--arg2",
[0] = "test/functional/fixtures/startup.lua"
}]],
{},
{ '-arg1', '--exitcode', '73', '--arg2' }
)
eq(73, eval('v:shell_error'))
end)
it('Lua-error sets Nvim exitcode', function()
eq(0, eval('v:shell_error'))
matches(
'E5113: .* my pearls!!',
fn.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' })
)
eq(1, eval('v:shell_error'))
matches(
'E5113: .* %[string "error%("whoa"%)"%]:1: whoa',
fn.system({ nvim_prog, '-l', '-' }, 'error("whoa")')
)
eq(1, eval('v:shell_error'))
end)
it('executes stdin "-"', function()
assert_l_out(
'arg0=- args=2 whoa\n',
nil,
{ 'arg1', 'arg 2' },
'-',
"print(('arg0=%s args=%d %s'):format(_G.arg[0], #_G.arg, 'whoa'))"
)
assert_l_out(
'biiig input: 1000042\n',
nil,
nil,
'-',
('print("biiig input: "..("%s"):len())'):format(string.rep('x', (1000 * 1000) + 42))
)
eq(0, eval('v:shell_error'))
end)
it('does not truncate long print() message', function()
assert_l_out(('k'):rep(1234) .. '\n', nil, nil, '-', "print(('k'):rep(1234))")
end)
it('does not add newline when unnecessary', function()
assert_l_out('', nil, nil, '-', '')
assert_l_out('foobar\n', nil, nil, '-', [[print('foobar\n')]])
end)
it('sets _G.arg', function()
-- nvim -l foo.lua
assert_l_out(
[[
bufs:
nvim args: 3
lua args: {
[0] = "test/functional/fixtures/startup.lua"
}
]],
{},
{}
)
eq(0, eval('v:shell_error'))
-- nvim -l foo.lua [args]
assert_l_out(
[[
bufs:
nvim args: 7
lua args: { "-arg1", "--arg2", "--", "arg3",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{},
{ '-arg1', '--arg2', '--', 'arg3' }
)
eq(0, eval('v:shell_error'))
-- nvim file1 file2 -l foo.lua -arg1 -- file3 file4
assert_l_out(
[[
bufs: file1 file2
nvim args: 10
lua args: { "-arg1", "arg 2", "--", "file3", "file4",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{ 'file1', 'file2' },
{ '-arg1', 'arg 2', '--', 'file3', 'file4' }
)
eq(0, eval('v:shell_error'))
-- nvim -l foo.lua <vim args>
assert_l_out(
[[
bufs:
nvim args: 5
lua args: { "-c", "set wrap?",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{},
{ '-c', 'set wrap?' }
)
eq(0, eval('v:shell_error'))
-- nvim <vim args> -l foo.lua <vim args>
assert_l_out(
-- luacheck: ignore 611 (Line contains only whitespaces)
[[
wrap
bufs:
nvim args: 7
lua args: { "-c", "set wrap?",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{ '-c', 'set wrap?' },
{ '-c', 'set wrap?' }
)
eq(0, eval('v:shell_error'))
end)
it('disables swapfile/shada/config/plugins', function()
assert_l_out(
'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n',
nil,
nil,
'-',
[[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format(
vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]]
)
end)
end)
it('--cmd/-c/+ do not truncate long Lua print() message with --headless', function()
local out = fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--cmd',
'lua print(("A"):rep(1234))',
'-c',
'lua print(("B"):rep(1234))',
'+lua print(("C"):rep(1234))',
'+q',
})
eq(('A'):rep(1234) .. '\r\n' .. ('B'):rep(1234) .. '\r\n' .. ('C'):rep(1234), out)
end)
it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
-- system() puts a pipe at both ends.
local out = fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--cmd',
nvim_set,
'-c',
[[echo has('ttyin') has('ttyout')]],
'+q',
})
eq('0 0', out)
end)
it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
local screen = Screen.new(25, 3)
-- Remote UI connected by --embed.
screen:attach()
command([[echo has('ttyin') has('ttyout')]])
screen:expect([[
^ |
~ |
0 0 |
]])
end)
it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
local screen = Screen.new(25, 4)
screen:attach()
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
nvim_set,
'-c',
'echo has("ttyin") has("ttyout")',
}, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect([[
^ |
~ |
1 1 |
|
]])
end)
it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
os.remove('Xtest_startup_ttyout')
finally(function()
os.remove('Xtest_startup_ttyout')
end)
-- Running in :terminal
fn.termopen(
(
[["%s" -u NONE -i NONE --cmd "%s"]]
.. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
.. [[ -c q | cat -v]]
):format(nvim_prog, nvim_set),
{
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
}
)
retry(nil, 3000, function()
sleep(1)
eq(
'1\n0\n', -- stdin is a TTY, stdout is a pipe
read_file('Xtest_startup_ttyout')
)
end)
end)
it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
os.remove('Xtest_startup_ttyout')
finally(function()
os.remove('Xtest_startup_ttyout')
end)
-- Running in :terminal
fn.termopen(
(
[[echo foo | ]] -- Input from a pipe.
.. [["%s" -u NONE -i NONE --cmd "%s"]]
.. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
.. [[ -c q -- -]]
):format(nvim_prog, nvim_set),
{
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
}
)
retry(nil, 3000, function()
sleep(1)
eq(
'0\n1\n', -- stdin is a pipe, stdout is a TTY
read_file('Xtest_startup_ttyout')
)
end)
end)
it('input from pipe (implicit) #7679', function()
local screen = Screen.new(25, 4)
screen:attach()
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
fn.termopen(
(
[[echo foo | ]]
.. [["%s" -u NONE -i NONE --cmd "%s"]]
.. [[ -c "echo has('ttyin') has('ttyout')"]]
):format(nvim_prog, nvim_set),
{
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
}
)
screen:expect([[
^foo |
~ |
0 1 |
|
]])
end)
it('input from pipe + file args #7679', function()
eq(
'ohyeah\r\n0 0 bufs=3',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'+.print',
"+echo has('ttyin') has('ttyout') 'bufs='.bufnr('$')",
'+qall!',
'-',
'test/functional/fixtures/tty-test.c',
'test/functional/fixtures/shell-test.c',
}, { 'ohyeah', '' })
)
end)
it('if stdin is empty: selects buffer 2, deletes buffer 1 #8561', function()
eq(
'\r\n 2 %a "file1" line 0\r\n 3 "file2" line 0',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'+ls!',
'+qall!',
'-',
'file1',
'file2',
}, { '' })
)
end)
it('stdin with -es/-Es #7679', function()
local input = { 'append', 'line1', 'line2', '.', '%print', '' }
local inputstr = table.concat(input, '\n')
--
-- -Es: read stdin as text
--
eq(
'partylikeits1999\n',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'-Es',
'+.print',
'test/functional/fixtures/tty-test.c',
}, { 'partylikeits1999', '' })
)
eq(inputstr, fn.system({ nvim_prog, '-i', 'NONE', '-Es', '+%print', '-' }, input))
-- with `-u NORC`
eq(
'thepartycontinues\n',
fn.system({ nvim_prog, '-n', '-u', 'NORC', '-Es', '+.print' }, { 'thepartycontinues', '' })
)
-- without `-u`
eq(
'thepartycontinues\n',
fn.system({ nvim_prog, '-n', '-Es', '+.print' }, { 'thepartycontinues', '' })
)
--
-- -es: read stdin as ex-commands
--
eq(
' encoding=utf-8\n',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'-es',
'test/functional/fixtures/tty-test.c',
}, { 'set encoding', '' })
)
eq('line1\nline2\n', fn.system({ nvim_prog, '-i', 'NONE', '-es', '-' }, input))
-- with `-u NORC`
eq(
' encoding=utf-8\n',
fn.system({ nvim_prog, '-n', '-u', 'NORC', '-es' }, { 'set encoding', '' })
)
-- without `-u`
eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' }))
end)
it('-es/-Es disables swapfile, user config #8540', function()
for _, arg in ipairs({ '-es', '-Es' }) do
local out = fn.system({
nvim_prog,
arg,
'+set swapfile? updatecount? shadafile?',
'+put =map(getscriptinfo(), {-> v:val.name})',
'+%print',
})
local line1 = string.match(out, '^.-\n')
-- updatecount=0 means swapfile was disabled.
eq(' swapfile updatecount=0 shadafile=\n', line1)
-- Standard plugins were loaded, but not user config.
ok(string.find(out, 'man.lua') ~= nil)
ok(string.find(out, 'init.vim') == nil)
end
end)
it('fails on --embed with -es/-Es/-l', function()
matches(
'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
fn.system({ nvim_prog, '--embed', '-es' })
)
matches(
'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
fn.system({ nvim_prog, '--embed', '-Es' })
)
matches(
'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
fn.system({ nvim_prog, '--embed', '-l', 'foo.lua' })
)
end)
it('ENTER dismisses early message #7967', function()
local screen
screen = Screen.new(60, 6)
screen:attach()
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'set noruler',
'--cmd',
'let g:foo = g:bar',
}, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect([[
^ |
|
Error detected while processing pre-vimrc command line: |
E121: Undefined variable: g:bar |
Press ENTER or type command to continue |
|
]])
fn.chansend(id, '\n')
screen:expect([[
^ |
~ |*2
[No Name] |
|*2
]])
end)
it('-r works without --headless in PTY #23294', function()
exec([[
func Normalize(data) abort
" Windows: remove ^M and term escape sequences
return map(a:data, 'substitute(substitute(v:val, "\r", "", "g"), "\x1b\\%(\\]\\d\\+;.\\{-}\x07\\|\\[.\\{-}[\x40-\x7E]\\)", "", "g")')
endfunc
func OnOutput(id, data, event) dict
let g:stdout = Normalize(a:data)
endfunc
call jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '-r'], {
\ 'pty': v:true,
\ 'stdout_buffered': v:true,
\ 'on_stdout': function('OnOutput'),
\ })
]])
retry(nil, nil, function()
eq('Swap files found:', eval('g:stdout[0]'))
end)
end)
it('fixed hang issue with --headless (#11386)', function()
local expected = ''
local period = 100
for i = 1, period - 1 do
expected = expected .. i .. '\r\n'
end
expected = expected .. period
eq(
expected,
-- FIXME(codehex): We should really set a timeout for the system function.
-- If this test fails, there will be a waiting input state.
fn.system({
nvim_prog,
'-u',
'NONE',
'-c',
'for i in range(1, 100) | echo i | endfor | quit',
'--headless',
})
)
end)
it('get command line arguments from v:argv', function()
local out = fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--cmd',
nvim_set,
'-c',
[[echo v:argv[-1:] len(v:argv) > 1]],
'+q',
})
eq("['+q'] 1", out)
end)
end)
describe('startup', function()
it('-e/-E interactive #7679', function()
clear('-e')
local screen = Screen.new(25, 3)
screen:attach()
feed("put ='from -e'<CR>")
screen:expect([[
:put ='from -e' |
from -e |
:^ |
]])
clear('-E')
screen = Screen.new(25, 3)
screen:attach()
feed("put ='from -E'<CR>")
screen:expect([[
:put ='from -E' |
from -E |
:^ |
]])
end)
it('-e sets ex mode', function()
local screen = Screen.new(25, 3)
clear('-e')
screen:attach()
-- Verify we set the proper mode both before and after :vi.
feed('put =mode(1)<CR>vi<CR>:put =mode(1)<CR>')
screen:expect([[
cv |
^n |
:put =mode(1) |
]])
eq('cv\n', fn.system({ nvim_prog, '-n', '-es' }, { 'put =mode(1)', 'print', '' }))
end)
it('-d does not diff non-arglist windows #13720 #21289', function()
write_file(
'Xdiff.vim',
[[
let bufnr = nvim_create_buf(0, 1)
let config = {
\ 'relative': 'editor',
\ 'focusable': v:false,
\ 'width': 1,
\ 'height': 1,
\ 'row': 3,
\ 'col': 3
\ }
autocmd WinEnter * call nvim_open_win(bufnr, v:false, config)]]
)
finally(function()
os.remove('Xdiff.vim')
end)
clear { args = { '-u', 'Xdiff.vim', '-d', 'Xdiff.vim', 'Xdiff.vim' } }
eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(1) }))
eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(2) }))
local float_win = fn.win_getid(3)
eq('editor', api.nvim_win_get_config(float_win).relative)
eq(false, api.nvim_get_option_value('diff', { win = float_win }))
end)
it('does not crash if --embed is given twice', function()
clear { args = { '--embed' } }
assert_alive()
end)
it('does not crash when expanding cdpath during early_init', function()
clear { env = { CDPATH = '~doesnotexist' } }
assert_alive()
eq(',~doesnotexist', eval('&cdpath'))
end)
it("sets 'shortmess' when loading other tabs", function()
clear({ args = { '-p', 'a', 'b', 'c' } })
local screen = Screen.new(25, 4)
screen:attach()
screen:expect({
grid = [[
{1: a }{2: b c }{3: }{2:X}|
^ |
{4:~ }|
|
]],
attr_ids = {
[1] = { bold = true },
[2] = { background = Screen.colors.LightGrey, underline = true },
[3] = { reverse = true },
[4] = { bold = true, foreground = Screen.colors.Blue1 },
},
})
end)
end)
describe('startup', function()
local function pack_clear(cmd)
-- add packages after config dir in rtp but before config/after
clear {
args = {
'--cmd',
'set packpath=test/functional/fixtures',
'--cmd',
'let paths=split(&rtp, ",")',
'--cmd',
'let &rtp = paths[0]..",test/functional/fixtures,test/functional/fixtures/middle,"..join(paths[1:],",")',
'--cmd',
cmd,
},
env = { XDG_CONFIG_HOME = 'test/functional/fixtures/' },
args_rm = { 'runtimepath' },
}
end
it('handles &packpath during startup', function()
pack_clear [[
let g:x = bar#test()
let g:y = leftpad#pad("heyya")
]]
eq(-3, eval 'g:x')
eq(' heyya', eval 'g:y')
pack_clear [[ lua _G.y = require'bar'.doit() _G.z = require'leftpad''howdy' ]]
eq({ 9003, '\thowdy' }, exec_lua [[ return { _G.y, _G.z } ]])
end)
it('handles require from &packpath in an async handler', function()
-- NO! you cannot just speed things up by calling async functions during startup!
-- It doesn't make anything actually faster! NOOOO!
pack_clear [[ lua require'async_leftpad'('brrrr', 'async_res') ]]
-- haha, async leftpad go brrrrr
eq('\tbrrrr', exec_lua [[ return _G.async_res ]])
end)
it('handles :packadd during startup', function()
-- control group: opt/bonus is not available by default
pack_clear [[
try
let g:x = bonus#secret()
catch
let g:err = v:exception
endtry
]]
eq('Vim(let):E117: Unknown function: bonus#secret', eval 'g:err')
pack_clear [[ lua _G.test = {pcall(function() require'bonus'.launch() end)} ]]
eq(
{ false, [[[string ":lua"]:1: module 'bonus' not found:]] },
exec_lua [[ _G.test[2] = string.gsub(_G.test[2], '[\r\n].*', '') return _G.test ]]
)
-- ok, time to launch the nukes:
pack_clear [[ packadd! bonus | let g:x = bonus#secret() ]]
eq('halloj', eval 'g:x')
pack_clear [[ packadd! bonus | lua _G.y = require'bonus'.launch() ]]
eq('CPE 1704 TKS', exec_lua [[ return _G.y ]])
end)
it('handles the correct order with start packages and after/', function()
pack_clear [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]]
eq(
{ 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
end)
it('handles the correct order with start packages and after/ after startup', function()
pack_clear [[ lua _G.test_loadorder = {} ]]
command [[ runtime! filen.lua ]]
eq(
{ 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
end)
it('handles the correct order with globpath(&rtp, ...)', function()
pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
command [[
for x in globpath(&rtp, "filen.lua",1,1)
call v:lua.dofile(x)
endfor
]]
eq(
{ 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
local rtp = api.nvim_get_option_value('rtp', {})
ok(
startswith(
rtp,
'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'
),
'startswith(…)',
'rtp=' .. rtp
)
end)
it('handles the correct order with opt packages and after/', function()
pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! superspecial\nruntime! filen.lua" ]]
eq({
'ordinary',
'SuperSpecial',
'FANCY',
'mittel',
'FANCY after',
'SuperSpecial after',
'ordinary after',
}, exec_lua [[ return _G.test_loadorder ]])
end)
it('handles the correct order with opt packages and after/ after startup', function()
pack_clear [[ lua _G.test_loadorder = {} ]]
command [[
packadd! superspecial
runtime! filen.lua
]]
eq({
'ordinary',
'SuperSpecial',
'FANCY',
'mittel',
'FANCY after',
'SuperSpecial after',
'ordinary after',
}, exec_lua [[ return _G.test_loadorder ]])
end)
it('handles the correct order with opt packages and globpath(&rtp, ...)', function()
pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
command [[
packadd! superspecial
for x in globpath(&rtp, "filen.lua",1,1)
call v:lua.dofile(x)
endfor
]]
eq({
'ordinary',
'SuperSpecial',
'FANCY',
'mittel',
'SuperSpecial after',
'FANCY after',
'ordinary after',
}, exec_lua [[ return _G.test_loadorder ]])
end)
it('handles the correct order with a package that changes packpath', function()
pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! funky\nruntime! filen.lua" ]]
eq(
{ 'ordinary', 'funky!', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
eq({ 'ordinary', 'funky!', 'mittel', 'ordinary after' }, exec_lua [[ return _G.nested_order ]])
end)
it('handles the correct order when prepending packpath', function()
clear {
args = {
'--cmd',
'set packpath^=test/functional/fixtures',
'--cmd',
[[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]],
},
env = { XDG_CONFIG_HOME = 'test/functional/fixtures/' },
}
eq(
{ 'ordinary', 'FANCY', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
end)
it('window widths are correct when modelines set &columns with tabpages', function()
write_file('Xtab1.noft', 'vim: columns=81')
write_file('Xtab2.noft', 'vim: columns=81')
finally(function()
os.remove('Xtab1.noft')
os.remove('Xtab2.noft')
end)
clear({ args = { '-p', 'Xtab1.noft', 'Xtab2.noft' } })
eq(81, api.nvim_win_get_width(0))
command('tabnext')
eq(81, api.nvim_win_get_width(0))
end)
end)
describe('sysinit', function()
local xdgdir = 'Xxdg'
local vimdir = 'Xvim'
local xhome = 'Xhome'
local pathsep = helpers.get_pathsep()
before_each(function()
rmdir(xdgdir)
rmdir(vimdir)
rmdir(xhome)
mkdir(xdgdir)
mkdir(xdgdir .. pathsep .. 'nvim')
write_file(
table.concat({ xdgdir, 'nvim', 'sysinit.vim' }, pathsep),
[[
let g:loaded = get(g:, "loaded", 0) + 1
let g:xdg = 1
]]
)
mkdir(vimdir)
write_file(
table.concat({ vimdir, 'sysinit.vim' }, pathsep),
[[
let g:loaded = get(g:, "loaded", 0) + 1
let g:vim = 1
]]
)
mkdir(xhome)
end)
after_each(function()
rmdir(xdgdir)
rmdir(vimdir)
rmdir(xhome)
end)
it('prefers XDG_CONFIG_DIRS over VIM', function()
clear {
args = { '--cmd', 'set nomore undodir=. directory=. belloff=' },
args_rm = { '-u', '--cmd' },
env = { HOME = xhome, XDG_CONFIG_DIRS = xdgdir, VIM = vimdir },
}
eq(
'loaded 1 xdg 1 vim 0',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')
)
end)
it('uses VIM if XDG_CONFIG_DIRS unset', function()
clear {
args = { '--cmd', 'set nomore undodir=. directory=. belloff=' },
args_rm = { '-u', '--cmd' },
env = { HOME = xhome, XDG_CONFIG_DIRS = '', VIM = vimdir },
}
eq(
'loaded 1 xdg 0 vim 1',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')
)
end)
end)
describe('user config init', function()
local xhome = 'Xhome'
local pathsep = helpers.get_pathsep()
local xconfig = xhome .. pathsep .. 'Xconfig'
local xdata = xhome .. pathsep .. 'Xdata'
local init_lua_path = table.concat({ xconfig, 'nvim', 'init.lua' }, pathsep)
local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
before_each(function()
rmdir(xhome)
mkdir_p(xconfig .. pathsep .. 'nvim')
mkdir_p(xdata)
write_file(
init_lua_path,
[[
vim.g.lua_rc = 1
]]
)
end)
after_each(function()
rmdir(xhome)
end)
it('loads init.lua from XDG config home by default', function()
clear { args_rm = { '-u' }, env = xenv }
eq(1, eval('g:lua_rc'))
eq(fn.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC'))
end)
describe('loads existing', function()
local exrc_path = '.exrc'
local xstate = 'Xstate'
local xstateenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_STATE_HOME = xstate }
local function setup_exrc_file(filename)
exrc_path = filename
if string.find(exrc_path, '%.lua$') then
write_file(
exrc_path,
string.format(
[[
vim.g.exrc_file = "%s"
]],
exrc_path
)
)
else
write_file(
exrc_path,
string.format(
[[
let g:exrc_file = "%s"
]],
exrc_path
)
)
end
end
before_each(function()
write_file(
init_lua_path,
[[
vim.o.exrc = true
vim.g.exrc_file = '---'
]]
)
mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
end)
after_each(function()
os.remove(exrc_path)
rmdir(xstate)
end)
for _, filename in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
it(filename .. ' in cwd', function()
setup_exrc_file(filename)
clear { args_rm = { '-u' }, env = xstateenv }
-- The 'exrc' file is not trusted, and the prompt is skipped because there is no UI.
eq('---', eval('g:exrc_file'))
local screen = Screen.new(50, 8)
screen:attach()
fn.termopen({ nvim_prog }, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:') })
-- `i` to enter Terminal mode, `a` to allow
feed('ia')
screen:expect([[
|
~ |*4
[No Name] 0,0-1 All|
|
-- TERMINAL -- |
]])
feed(':echo g:exrc_file<CR>')
screen:expect(string.format(
[[
|
~ |*4
[No Name] 0,0-1 All|
%s%s|
-- TERMINAL -- |
]],
filename,
string.rep(' ', 50 - #filename)
))
clear { args_rm = { '-u' }, env = xstateenv }
-- The 'exrc' file is now trusted.
eq(filename, eval('g:exrc_file'))
end)
end
end)
describe('with explicitly provided config', function()
local custom_lua_path = table.concat({ xhome, 'custom.lua' }, pathsep)
before_each(function()
write_file(
custom_lua_path,
[[
vim.g.custom_lua_rc = 1
]]
)
end)
it('loads custom lua config and does not set $MYVIMRC', function()
clear { args = { '-u', custom_lua_path }, env = xenv }
eq(1, eval('g:custom_lua_rc'))
eq('', eval('$MYVIMRC'))
end)
end)
describe('VIMRC also exists', function()
before_each(function()
write_file(
table.concat({ xconfig, 'nvim', 'init.vim' }, pathsep),
[[
let g:vim_rc = 1
]]
)
end)
it('loads default lua config, but shows an error', function()
clear { args_rm = { '-u' }, env = xenv }
feed('<cr><c-c>') -- Dismiss "Conflicting config …" message.
eq(1, eval('g:lua_rc'))
matches('^E5422: Conflicting configs', exec_capture('messages'))
end)
end)
end)
describe('runtime:', function()
local xhome = 'Xhome'
local pathsep = helpers.get_pathsep()
local xconfig = xhome .. pathsep .. 'Xconfig'
local xdata = xhome .. pathsep .. 'Xdata'
local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
setup(function()
rmdir(xhome)
mkdir_p(xconfig .. pathsep .. 'nvim')
mkdir_p(xdata)
end)
teardown(function()
rmdir(xhome)
end)
it('loads plugin/*.lua from XDG config home', function()
local plugin_folder_path = table.concat({ xconfig, 'nvim', 'plugin' }, pathsep)
local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
mkdir_p(plugin_folder_path)
finally(function()
rmdir(plugin_folder_path)
end)
write_file(plugin_file_path, [[ vim.g.lua_plugin = 1 ]])
clear { args_rm = { '-u' }, env = xenv }
eq(1, eval('g:lua_plugin'))
end)
it('loads plugin/*.lua from start packages', function()
local plugin_path =
table.concat({ xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin' }, pathsep)
local plugin_folder_path = table.concat({ plugin_path, 'plugin' }, pathsep)
local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
local profiler_file = 'test_startuptime.log'
mkdir_p(plugin_folder_path)
finally(function()
os.remove(profiler_file)
rmdir(plugin_path)
end)
write_file(plugin_file_path, [[vim.g.lua_plugin = 2]])
clear { args_rm = { '-u' }, args = { '--startuptime', profiler_file }, env = xenv }
eq(2, eval('g:lua_plugin'))
-- Check if plugin_file_path is listed in getscriptinfo()
local scripts = tbl_map(function(s)
return s.name
end, fn.getscriptinfo())
ok(#tbl_filter(function(s)
return endswith(s, plugin_file_path)
end, scripts) > 0)
-- Check if plugin_file_path is listed in startup profile
local profile_reader = io.open(profiler_file, 'r')
local profile_log = profile_reader:read('*a')
profile_reader:close()
ok(profile_log:find(plugin_file_path) ~= nil)
end)
it('loads plugin/*.lua from site packages', function()
local nvimdata = is_os('win') and 'nvim-data' or 'nvim'
local plugin_path =
table.concat({ xdata, nvimdata, 'site', 'pack', 'xa', 'start', 'yb' }, pathsep)
local plugin_folder_path = table.concat({ plugin_path, 'plugin' }, pathsep)
local plugin_after_path = table.concat({ plugin_path, 'after', 'plugin' }, pathsep)
local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
local plugin_after_file_path = table.concat({ plugin_after_path, 'helloo.lua' }, pathsep)
mkdir_p(plugin_folder_path)
mkdir_p(plugin_after_path)
finally(function()
rmdir(plugin_path)
end)
write_file(plugin_file_path, [[table.insert(_G.lista, "unos")]])
write_file(plugin_after_file_path, [[table.insert(_G.lista, "dos")]])
clear { args_rm = { '-u' }, args = { '--cmd', 'lua _G.lista = {}' }, env = xenv }
eq({ 'unos', 'dos' }, exec_lua 'return _G.lista')
end)
it('no crash setting &rtp in plugins with :packloadall called before #18315', function()
local plugin_folder_path = table.concat({ xconfig, 'nvim', 'plugin' }, pathsep)
mkdir_p(plugin_folder_path)
finally(function()
rmdir(plugin_folder_path)
end)
write_file(
table.concat({ plugin_folder_path, 'plugin.vim' }, pathsep),
[[
let &runtimepath = &runtimepath
let g:vim_plugin = 1
]]
)
write_file(
table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep),
[[
vim.o.runtimepath = vim.o.runtimepath
vim.g.lua_plugin = 1
]]
)
clear { args_rm = { '-u' }, args = { '--cmd', 'packloadall' }, env = xenv }
eq(1, eval('g:vim_plugin'))
eq(1, eval('g:lua_plugin'))
end)
it("loads ftdetect/*.{vim,lua} respecting 'rtp' order", function()
local ftdetect_folder = table.concat({ xconfig, 'nvim', 'ftdetect' }, pathsep)
local after_ftdetect_folder = table.concat({ xconfig, 'nvim', 'after', 'ftdetect' }, pathsep)
mkdir_p(ftdetect_folder)
mkdir_p(after_ftdetect_folder)
finally(function()
rmdir(ftdetect_folder)
rmdir(after_ftdetect_folder)
end)
-- A .lua file is loaded after a .vim file if they only differ in extension.
-- All files in after/ftdetect/ are loaded after all files in ftdetect/.
write_file(table.concat({ ftdetect_folder, 'new-ft.vim' }, pathsep), [[let g:seq ..= 'A']])
write_file(
table.concat({ ftdetect_folder, 'new-ft.lua' }, pathsep),
[[vim.g.seq = vim.g.seq .. 'B']]
)
write_file(
table.concat({ after_ftdetect_folder, 'new-ft.vim' }, pathsep),
[[let g:seq ..= 'a']]
)
write_file(
table.concat({ after_ftdetect_folder, 'new-ft.lua' }, pathsep),
[[vim.g.seq = vim.g.seq .. 'b']]
)
clear { args_rm = { '-u' }, args = { '--cmd', 'let g:seq = ""' }, env = xenv }
eq('ABab', eval('g:seq'))
end)
end)
describe('user session', function()
local xhome = 'Xhome'
local pathsep = helpers.get_pathsep()
local session_file = table.concat({ xhome, 'session.lua' }, pathsep)
before_each(function()
rmdir(xhome)
mkdir(xhome)
write_file(
session_file,
[[
vim.g.lua_session = 1
]]
)
end)
after_each(function()
rmdir(xhome)
end)
it('loads session from the provided lua file', function()
clear { args = { '-S', session_file }, env = { HOME = xhome } }
eq(1, eval('g:lua_session'))
end)
end)
describe('inccommand on ex mode', function()
it('should not preview', function()
clear()
local screen
screen = Screen.new(60, 10)
screen:attach()
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'-c',
'set termguicolors background=dark',
'-E',
'test/README.md',
}, {
env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
})
fn.chansend(id, '%s/N')
screen:expect {
grid = [[
{1:^ }|
{1: }|*6
{1:Entering Ex mode. Type "visual" to go to Normal mode. }|
{1::%s/N }|
|
]],
attr_ids = {
[1] = {
background = Screen.colors.NvimDarkGrey2,
foreground = Screen.colors.NvimLightGrey2,
},
},
}
end)
end)