mirror of
https://github.com/neovim/neovim.git
synced 2026-07-11 20:09:44 +00:00
feat(vim.fs): dir(), find() error-reporting
Problem:
vim.fs.dir() and vim.fs.find() drop errors returned by uv.fs_scandir().
Solution:
- vim.fs.dir():
- Return root scan failures as a secondary return value.
- Propagate recursive scan failures through the iterator. This allows
callers to distinguish unreadable directories from empty ones.
- vim.fs.find(): Collect errors during search, and return the list as
a second retval.
This commit is contained in:
committed by
Justin M. Keyes
parent
0d81f257af
commit
4e04dff228
@@ -287,6 +287,58 @@ describe('vim.fs', function()
|
||||
eq('file', result['test.txt'])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('error handling', function()
|
||||
before_each(function()
|
||||
mkdir('testdir')
|
||||
mkdir('testdir/noaccess')
|
||||
mkdir('testdir/a')
|
||||
mkdir('testdir/a/noaccess')
|
||||
end)
|
||||
after_each(function()
|
||||
rmdir('testdir')
|
||||
end)
|
||||
|
||||
it('returns error for unreadable root directory', function()
|
||||
local result = exec_lua(function()
|
||||
local _, err = vim.fs.dir('does-not-exist')
|
||||
return err
|
||||
end)
|
||||
eq('ENOENT: no such file or directory: does-not-exist', result)
|
||||
end)
|
||||
|
||||
it('returns iterator errors for unreadable child directories', function()
|
||||
local result = exec_lua(function()
|
||||
-- Stub fs_scandir since chmod doesn't work reliably on Windows.
|
||||
local orig_scandir = vim.uv.fs_scandir
|
||||
vim.uv.fs_scandir = function(path, ...)
|
||||
if path == 'testdir/noaccess' then
|
||||
return nil, 'EACCES: permission denied: testdir/noaccess'
|
||||
end
|
||||
return orig_scandir(path, ...)
|
||||
end
|
||||
|
||||
local ok, errors_or_err = pcall(function()
|
||||
local errors = {} ---@type table<string, string>
|
||||
local it = vim.fs.dir('testdir', { depth = 2 })
|
||||
for f, _, err in it do
|
||||
errors[f] = err
|
||||
end
|
||||
return errors
|
||||
end)
|
||||
|
||||
vim.uv.fs_scandir = orig_scandir
|
||||
|
||||
if not ok then
|
||||
error(errors_or_err)
|
||||
end
|
||||
return errors_or_err
|
||||
end)
|
||||
eq('EACCES: permission denied: testdir/noaccess', result['noaccess'])
|
||||
-- This should be nil because with a depth of 2 we shouldn't be scanning it
|
||||
eq(nil, result['a/noaccess'])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('find()', function()
|
||||
@@ -394,6 +446,113 @@ describe('vim.fs', function()
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
describe('error handling', function()
|
||||
before_each(function()
|
||||
mkdir('testdir')
|
||||
mkdir('testdir/noaccess')
|
||||
mkdir('testdir/a')
|
||||
mkdir('testdir/a/noaccess')
|
||||
t.write_file('testdir/a/match.lua', '')
|
||||
|
||||
exec_lua(function()
|
||||
-- Stub fs_scandir since chmod doesn't work reliably on Windows.
|
||||
local blocked_prefixes = { 'testdir/noaccess', 'testdir/a/noaccess' }
|
||||
local function is_blocked(path)
|
||||
for _, prefix in ipairs(blocked_prefixes) do
|
||||
if path == prefix or vim.startswith(path, prefix .. '/') then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
_G.__orig_fs_scandir = vim.uv.fs_scandir
|
||||
vim.uv.fs_scandir = function(path, ...)
|
||||
if is_blocked(path) then
|
||||
return nil, 'EACCES: permission denied: ' .. path
|
||||
end
|
||||
return _G.__orig_fs_scandir(path, ...)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
exec_lua(function()
|
||||
vim.uv.fs_scandir = _G.__orig_fs_scandir
|
||||
_G.__orig_fs_scandir = nil
|
||||
end)
|
||||
rmdir('testdir')
|
||||
end)
|
||||
|
||||
it('returns an error for a nonexistent root path', function()
|
||||
local result = exec_lua(function()
|
||||
local matches, errors = vim.fs.find('foo', { path = 'does-not-exist' })
|
||||
return { matches = matches, errors = errors }
|
||||
end)
|
||||
eq({}, result.matches)
|
||||
eq(1, #result.errors)
|
||||
eq('does-not-exist', result.errors[1].path)
|
||||
eq('ENOENT: no such file or directory: does-not-exist', result.errors[1].err)
|
||||
end)
|
||||
|
||||
it('returns an error for an unreadable root path', function()
|
||||
local result = exec_lua(function()
|
||||
local matches, errors = vim.fs.find('foo', { path = 'testdir/noaccess' })
|
||||
return { matches = matches, errors = errors }
|
||||
end)
|
||||
eq({}, result.matches)
|
||||
eq(1, #result.errors)
|
||||
eq('testdir/noaccess', result.errors[1].path)
|
||||
eq('EACCES: permission denied: testdir/noaccess', result.errors[1].err)
|
||||
end)
|
||||
|
||||
it('collects errors for unreadable child directories during downward search', function()
|
||||
local result = exec_lua(function()
|
||||
local matches, errors = vim.fs.find('match.lua', {
|
||||
path = 'testdir',
|
||||
limit = math.huge,
|
||||
type = 'file',
|
||||
})
|
||||
--- @type table<string, string>
|
||||
local err_by_path = {}
|
||||
for _, e in ipairs(errors) do
|
||||
err_by_path[e.path] = e.err
|
||||
end
|
||||
return { matches = matches, errors = err_by_path }
|
||||
end)
|
||||
eq(1, #result.matches)
|
||||
eq('EACCES: permission denied: testdir/noaccess', result.errors['testdir/noaccess'])
|
||||
eq('EACCES: permission denied: testdir/a/noaccess', result.errors['testdir/a/noaccess'])
|
||||
end)
|
||||
|
||||
it('still returns matches found despite errors elsewhere in the tree', function()
|
||||
local result = exec_lua(function()
|
||||
local matches, errors = vim.fs.find('match.lua', {
|
||||
path = 'testdir',
|
||||
limit = math.huge,
|
||||
type = 'file',
|
||||
})
|
||||
return { matches = matches, has_errors = #errors > 0 }
|
||||
end)
|
||||
eq(1, #result.matches)
|
||||
eq(true, result.has_errors)
|
||||
end)
|
||||
|
||||
it('reports errors for unreadable directories during upward search', function()
|
||||
local result = exec_lua(function()
|
||||
local matches, errors = vim.fs.find('match.lua', {
|
||||
path = 'testdir/noaccess/x',
|
||||
upward = true,
|
||||
stop = 'testdir',
|
||||
})
|
||||
return { matches = matches, errors = errors }
|
||||
end)
|
||||
eq('EACCES: permission denied: testdir/noaccess/x', result.errors[1].err)
|
||||
eq('EACCES: permission denied: testdir/noaccess', result.errors[2].err)
|
||||
eq('testdir/noaccess/x', result.errors[1].path)
|
||||
eq('testdir/noaccess', result.errors[2].path)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('root()', function()
|
||||
|
||||
Reference in New Issue
Block a user