diff options
| author | Rudrajeet Pal <rudrajeetpal64@gmail.com> | 2026-06-04 16:33:03 +0530 |
|---|---|---|
| committer | Justin M. Keyes <justinkz@gmail.com> | 2026-07-01 12:05:41 +0200 |
| commit | 4e04dff2287eed576cdb1063d5b7c00bf91b9fad (patch) | |
| tree | c0f6733b71c81bbf34307abfbcb8288bab49c5f7 /test/functional/lua/fs_spec.lua | |
| parent | 0d81f257afd24373144873e8f030bf929c1900e2 (diff) | |
| download | rneovim-4e04dff2287eed576cdb1063d5b7c00bf91b9fad.tar.gz rneovim-4e04dff2287eed576cdb1063d5b7c00bf91b9fad.tar.bz2 rneovim-4e04dff2287eed576cdb1063d5b7c00bf91b9fad.zip | |
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.
Diffstat (limited to 'test/functional/lua/fs_spec.lua')
| -rw-r--r-- | test/functional/lua/fs_spec.lua | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 8434a36373..302d1445ac 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -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() |