Files
neovim/test/functional/vimscript/executable_spec.lua
zeertzjq e3d46a6337 test: start test runners in Xtest_xdg dir (#37964)
This is a better way to prevent parallel tests from interfering with
each other, as there are many ways files can be created and deleted in
tests, so enforcing different file names is hard.

Using $TMPDIR can also work in most cases, but 'backipskip' etc. have
special defaults for $TMPDIR.

Symlink runtime/, src/, test/ and README.md to Xtest_xdg dir to make
tests more convenient (and symlinking test/ is required for busted).

Also, use README.md instead of test/README.md in the Ex mode inccommand
test, as test/README.md no longer contains 'N' char.
2026-02-20 06:53:33 +08:00

269 lines
9.1 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local eq, clear, call, write_file, command = t.eq, n.clear, n.call, t.write_file, n.command
local exc_exec = n.exc_exec
local eval = n.eval
local is_os = t.is_os
describe('executable()', function()
before_each(clear)
it('returns 1 for commands in $PATH', function()
local exe = is_os('win') and 'ping' or 'ls'
eq(1, call('executable', exe))
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
eq(1, call('executable', 'null'))
eq(1, call('executable', 'true'))
eq(1, call('executable', 'false'))
end)
if is_os('win') then
it('exepath respects shellslash', function()
-- test/ cannot be a symlink in this test.
n.api.nvim_set_current_dir(t.paths.test_source_path)
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
eq(
[[test\functional\fixtures\bin\null.CMD]],
call('fnamemodify', call('exepath', 'null'), ':.')
)
command('set shellslash')
eq(
'test/functional/fixtures/bin/null.CMD',
call('fnamemodify', call('exepath', 'null'), ':.')
)
end)
it('stdpath respects shellslash', function()
-- Needs to check paths relative to repo root dir.
n.api.nvim_set_current_dir(t.paths.test_source_path)
t.matches(
[[build\Xtest_xdg[%w_]*\share\nvim%-data]],
call('fnamemodify', call('stdpath', 'data'), ':.')
)
command('set shellslash')
t.matches(
'build/Xtest_xdg[%w_]*/share/nvim%-data',
call('fnamemodify', call('stdpath', 'data'), ':.')
)
end)
end
it('fails for invalid values', function()
for _, input in ipairs({ 'v:null', 'v:true', 'v:false', '{}', '[]' }) do
eq(
'Vim(call):E1174: String required for argument 1',
exc_exec('call executable(' .. input .. ')')
)
end
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
for _, input in ipairs({ 'v:null', 'v:true', 'v:false' }) do
eq(
'Vim(call):E1174: String required for argument 1',
exc_exec('call executable(' .. input .. ')')
)
end
end)
it('returns 0 for empty strings', function()
eq(0, call('executable', '""'))
end)
it('returns 0 for non-existent files', function()
eq(0, call('executable', 'no_such_file_exists_209ufq23f'))
end)
it('sibling to nvim binary', function()
-- Some executable in build/bin/, *not* in $PATH nor CWD.
local sibling_exe = 'printargs-test'
-- Windows: siblings are in Nvim's "pseudo-$PATH".
local expected = is_os('win') and 1 or 0
if is_os('win') then
eq('arg1=lemon;arg2=sky;arg3=tree;', call('system', sibling_exe .. ' lemon sky tree'))
end
eq(expected, call('executable', sibling_exe))
end)
describe('exec-bit', function()
setup(function()
clear()
write_file('Xtest_not_executable', 'non-executable file')
write_file('Xtest_executable', 'executable file (exec-bit set)')
if not is_os('win') then -- N/A for Windows.
call('system', { 'chmod', '-x', 'Xtest_not_executable' })
call('system', { 'chmod', '+x', 'Xtest_executable' })
end
end)
teardown(function()
os.remove('Xtest_not_executable')
os.remove('Xtest_executable')
end)
it('not set', function()
eq(0, call('executable', 'Xtest_not_executable'))
eq(0, call('executable', './Xtest_not_executable'))
end)
it('set, unqualified and not in $PATH', function()
eq(0, call('executable', 'Xtest_executable'))
end)
it('set, qualified as a path', function()
local expected = is_os('win') and 0 or 1
eq(expected, call('executable', './Xtest_executable'))
end)
end)
end)
describe('executable() (Windows)', function()
if not is_os('win') then
pending('N/A for non-windows')
return
end
local exts = { 'bat', 'exe', 'com', 'cmd' }
setup(function()
for _, ext in ipairs(exts) do
write_file('test_executable_' .. ext .. '.' .. ext, '')
end
write_file('test_executable_zzz.zzz', '')
end)
teardown(function()
for _, ext in ipairs(exts) do
os.remove('test_executable_' .. ext .. '.' .. ext)
end
os.remove('test_executable_zzz.zzz')
end)
it('tries default extensions on a filename if $PATHEXT is empty', function()
-- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
clear({ env = { PATHEXT = '' } })
for _, ext in ipairs(exts) do
eq(1, call('executable', 'test_executable_' .. ext))
end
eq(0, call('executable', 'test_executable_zzz'))
end)
it('tries default extensions on a filepath if $PATHEXT is empty', function()
-- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
clear({ env = { PATHEXT = '' } })
for _, ext in ipairs(exts) do
eq(1, call('executable', '.\\test_executable_' .. ext))
end
eq(0, call('executable', '.\\test_executable_zzz'))
end)
it('system([…]), jobstart([…]) use $PATHEXT #9569', function()
-- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
clear({ env = { PATHEXT = '' } })
-- Invoking `cmdscript` should find/execute `cmdscript.cmd`.
eq('much success\n', call('system', { 'test/functional/fixtures/cmdscript' }))
assert(0 < call('jobstart', { 'test/functional/fixtures/cmdscript' }))
end)
it('full path with extension', function()
-- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
clear({ env = { PATHEXT = '' } })
-- Some executable we can expect in the test env.
local exe = 'printargs-test'
local exedir = eval("fnamemodify(v:progpath, ':h')")
local exepath = exedir .. '/' .. exe .. '.exe'
eq(1, call('executable', exepath))
eq('arg1=lemon;arg2=sky;arg3=tree;', call('system', exepath .. ' lemon sky tree'))
end)
it('full path without extension', function()
-- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
clear({ env = { PATHEXT = '' } })
-- Some executable we can expect in the test env.
local exe = 'printargs-test'
local exedir = eval("fnamemodify(v:progpath, ':h')")
local exepath = exedir .. '/' .. exe
eq('arg1=lemon;arg2=sky;arg3=tree;', call('system', exepath .. ' lemon sky tree'))
eq(1, call('executable', exepath))
end)
it('respects $PATHEXT when trying extensions on a filename', function()
clear({ env = { PATHEXT = '.zzz' } })
for _, ext in ipairs(exts) do
eq(0, call('executable', 'test_executable_' .. ext))
end
eq(1, call('executable', 'test_executable_zzz'))
end)
it('respects $PATHEXT when trying extensions on a filepath', function()
clear({ env = { PATHEXT = '.zzz' } })
for _, ext in ipairs(exts) do
eq(0, call('executable', '.\\test_executable_' .. ext))
end
eq(1, call('executable', '.\\test_executable_zzz'))
end)
it('with weird $PATHEXT', function()
clear({ env = { PATHEXT = ';' } })
eq(0, call('executable', '.\\test_executable_zzz'))
clear({ env = { PATHEXT = ';;;.zzz;;' } })
eq(1, call('executable', '.\\test_executable_zzz'))
end)
it("unqualified filename, Unix-style 'shell'", function()
clear({ env = { PATHEXT = '' } })
command('set shell=sh')
for _, ext in ipairs(exts) do
eq(0, call('executable', 'test_executable_' .. ext .. '.' .. ext))
end
eq(0, call('executable', 'test_executable_zzz.zzz'))
end)
it("relative path, Unix-style 'shell' (backslashes)", function()
clear({ env = { PATHEXT = '' } })
command('set shell=bash.exe')
for _, ext in ipairs(exts) do
eq(1, call('executable', '.\\test_executable_' .. ext .. '.' .. ext))
eq(1, call('executable', './test_executable_' .. ext .. '.' .. ext))
end
eq(1, call('executable', '.\\test_executable_zzz.zzz'))
eq(1, call('executable', './test_executable_zzz.zzz'))
end)
it('unqualified filename, $PATHEXT contains dot', function()
clear({ env = { PATHEXT = '.;.zzz' } })
for _, ext in ipairs(exts) do
eq(1, call('executable', 'test_executable_' .. ext .. '.' .. ext))
end
eq(1, call('executable', 'test_executable_zzz.zzz'))
clear({ env = { PATHEXT = '.zzz;.' } })
for _, ext in ipairs(exts) do
eq(1, call('executable', 'test_executable_' .. ext .. '.' .. ext))
end
eq(1, call('executable', 'test_executable_zzz.zzz'))
end)
it('relative path, $PATHEXT contains dot (backslashes)', function()
clear({ env = { PATHEXT = '.;.zzz' } })
for _, ext in ipairs(exts) do
eq(1, call('executable', '.\\test_executable_' .. ext .. '.' .. ext))
eq(1, call('executable', './test_executable_' .. ext .. '.' .. ext))
end
eq(1, call('executable', '.\\test_executable_zzz.zzz'))
eq(1, call('executable', './test_executable_zzz.zzz'))
end)
it('ignores case of extension', function()
clear({ env = { PATHEXT = '.ZZZ' } })
eq(1, call('executable', 'test_executable_zzz.zzz'))
end)
it('relative path does not search $PATH', function()
clear({ env = { PATHEXT = '' } })
eq(0, call('executable', './System32/notepad.exe'))
eq(0, call('executable', '.\\System32\\notepad.exe'))
eq(0, call('executable', '../notepad.exe'))
eq(0, call('executable', '..\\notepad.exe'))
end)
end)