fix IsFile and IsDir panicking on a stat error other than not-exist - #11
Merged
Merged
Conversation
exists guarded only os.IsNotExist, so any other stat failure fell through to info.IsDir() with info nil. A path under a directory the process cannot read, or a path the OS rejects outright, panicked with a nil pointer dereference instead of reporting that the file is not there. Every stat failure now reports false. Nothing that previously returned a value changes, only the cases that panicked. MoveFile gains an unexported moveFile taking the rename call as an argument. The existing fallback test never reached the copy+delete branch: its first rename failed only because the destination directory was missing, and the retry after MkdirAll succeeded. It is renamed to say what it actually covers, and a new case forces rename to fail so the fallback carries the move, asserting the two rename attempts and the moved content. TouchFile tests no longer depend on wall clock. The 100ms sleep is replaced by backdating the file with os.Chtimes, which also makes the assertion independent of filesystem timestamp resolution, and the creation case bounds the modification time from below rather than measuring how fast the runner is.
umputun
approved these changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three related bits of hardening, all behind existing API.
IsFileandIsDirpanicked on a stat error other than not-existos.Statreturns a nilFileInfoon every failure, not only on a missing file, so anything else reachedinfo.IsDir()and dereferenced nil. A file under a directory the process cannot read is the ordinary way to hit it:Every stat failure now reports false. No call that previously returned a value behaves differently, only the ones that panicked.
The copy-and-delete fallback in
MoveFilewas never testedThe subtest named "move with copy fallback" did not reach the fallback. Its destination directory was missing, so the first
os.Renamefailed withENOENT,MkdirAllcreated the directory, and the retry succeeded.CopyFilewas never called.MoveFileis now a one-line wrapper over an unexportedmoveFilethat takes the rename call as an argument. The old subtest keeps its coverage under a name that says what it does, and a new one forces rename to fail so the fallback has to carry the move, asserting both rename attempts happen and the content lands with the source removed. Nothing exported changes.TouchFiletests no longer depend on wall clockThe update case slept 100ms so the modification time would differ, which is both slow and dependent on the filesystem's timestamp resolution. It now backdates the file an hour with
os.Chtimes, so the timestamp has to move regardless of resolution and nothing sleeps.The creation case asserted
time.Since(info.ModTime()) < time.Second, an upper bound that measures how loaded the runner is rather than anything aboutTouchFile. It now bounds the time from below against a moment captured before the call.Verification
The new
TestExistsStatErrorpanics against the oldexistsand passes against the new one, so it is not a vacuous test. Full suite green under-race,golangci-lintclean. The permission-based case skips under uid 0, since root bypasses directory permissions.