diff --git a/api/filesys/filesystem_test.go b/api/filesys/filesystem_test.go new file mode 100644 index 000000000..fd8ab2506 --- /dev/null +++ b/api/filesys/filesystem_test.go @@ -0,0 +1,40 @@ +package filesys + +import ( + "os" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +var filesysBuilders = map[string]func() FileSystem{ + "MakeFsInMemory": MakeFsInMemory, + "MakeFsOnDisk": MakeFsOnDisk, + "MakeEmptyDirInMemory": func() FileSystem { return MakeEmptyDirInMemory() }, +} + +func TestNotExistErr(t *testing.T) { + for name, builder := range filesysBuilders { + t.Run(name, func(t *testing.T) { + testNotExistErr(t, builder()) + }) + } +} + +func testNotExistErr(t *testing.T, fs FileSystem) { + const path = "bad-dir/file.txt" + + err := fs.RemoveAll(path) + assert.Falsef(t, errors.Is(err, os.ErrNotExist), "RemoveAll should not return ErrNotExist, got %v", err) + _, err = fs.Open(path) + assert.Truef(t, errors.Is(err, os.ErrNotExist), "Open should return ErrNotExist, got %v", err) + _, err = fs.ReadDir(path) + assert.Truef(t, errors.Is(err, os.ErrNotExist), "ReadDir should return ErrNotExist, got %v", err) + _, _, err = fs.CleanedAbs(path) + assert.Truef(t, errors.Is(err, os.ErrNotExist), "CleanedAbs should return ErrNotExist, got %v", err) + _, err = fs.ReadFile(path) + assert.Truef(t, errors.Is(err, os.ErrNotExist), "ReadFile should return ErrNotExist, got %v", err) + err = fs.Walk(path, func(_ string, _ os.FileInfo, err error) error { return err }) + assert.Truef(t, errors.Is(err, os.ErrNotExist), "Walk should return ErrNotExist, got %v", err) +} diff --git a/api/filesys/fsnode.go b/api/filesys/fsnode.go index 13b0a2298..fbede9ed7 100644 --- a/api/filesys/fsnode.go +++ b/api/filesys/fsnode.go @@ -236,7 +236,7 @@ func (n *fsNode) CleanedAbs(path string) (ConfirmedDir, string, error) { return "", "", errors.Wrap(err, "unable to clean") } if node == nil { - return "", "", fmt.Errorf("'%s' doesn't exist", path) + return "", "", notExistError(path) } if node.isNodeADir() { return ConfirmedDir(node.Path()), "", nil @@ -309,7 +309,8 @@ func (n *fsNode) RemoveAll(path string) error { return err } if result == nil { - return fmt.Errorf("cannot find '%s' to remove it", path) + // If the path doesn't exist, no need to remove anything. + return nil } return result.Remove() } @@ -351,6 +352,9 @@ func (n *fsNode) IsDir(path string) bool { // ReadDir implements FileSystem. func (n *fsNode) ReadDir(path string) ([]string, error) { + if !n.Exists(path) { + return nil, notExistError(path) + } if !n.IsDir(path) { return nil, fmt.Errorf("%s is not a directory", path) } @@ -398,7 +402,7 @@ func (n *fsNode) Open(path string) (File, error) { return nil, err } if result == nil { - return nil, fmt.Errorf("cannot find '%s' to open it", path) + return nil, notExistError(path) } if result.offset != nil { return nil, fmt.Errorf("cannot open previously opened file '%s'", path) @@ -423,7 +427,7 @@ func (n *fsNode) ReadFile(path string) (c []byte, err error) { return nil, err } if result == nil { - return nil, fmt.Errorf("cannot find '%s' to read it", path) + return nil, notExistError(path) } if result.isNodeADir() { return nil, fmt.Errorf("cannot read content from non-file '%s'", n.Path()) @@ -490,7 +494,7 @@ func (n *fsNode) Walk(path string, walkFn filepath.WalkFunc) error { return err } if result == nil { - return fmt.Errorf("cannot find '%s' to walk it", path) + return notExistError(path) } return result.WalkMe(walkFn) } @@ -629,3 +633,10 @@ func (n *fsNode) Glob(pattern string) ([]string, error) { sort.Strings(result) return result, nil } + +// notExistError indicates that a file or directory does not exist. +// Unwrapping returns os.ErrNotExist so errors.Is(err, os.ErrNotExist) works correctly. +type notExistError string + +func (err notExistError) Error() string { return fmt.Sprintf("'%s' doesn't exist", string(err)) } +func (err notExistError) Unwrap() error { return os.ErrNotExist } diff --git a/api/filesys/fsondisk.go b/api/filesys/fsondisk.go index 86cd26d75..d5c07f381 100644 --- a/api/filesys/fsondisk.go +++ b/api/filesys/fsondisk.go @@ -57,7 +57,7 @@ func (x fsOnDisk) CleanedAbs( deLinked, err := filepath.EvalSymlinks(absRoot) if err != nil { return "", "", fmt.Errorf( - "evalsymlink failure on '%s' : %v", path, err) + "evalsymlink failure on '%s' : %w", path, err) } if x.IsDir(deLinked) { return ConfirmedDir(deLinked), "", nil