--- /dev/null
+pkg io/fs, var SkipAll error #47209
+pkg path/filepath, var SkipAll error #47209
}
}
+func TestWalkSkipAll(t *testing.T) {
+ initOverlay(t, `
+{
+ "Replace": {
+ "dir/subdir1/foo1": "dummy.txt",
+ "dir/subdir1/foo2": "dummy.txt",
+ "dir/subdir1/foo3": "dummy.txt",
+ "dir/subdir2/foo4": "dummy.txt",
+ "dir/zzlast": "dummy.txt"
+ }
+}
+-- dummy.txt --
+`)
+
+ var seen []string
+ Walk("dir", func(path string, info fs.FileInfo, err error) error {
+ seen = append(seen, filepath.ToSlash(path))
+ if info.Name() == "foo2" {
+ return filepath.SkipAll
+ }
+ return nil
+ })
+
+ wantSeen := []string{"dir", "dir/subdir1", "dir/subdir1/foo1", "dir/subdir1/foo2"}
+
+ if len(seen) != len(wantSeen) {
+ t.Errorf("paths seen in walk: got %v entries; want %v entries", len(seen), len(wantSeen))
+ }
+
+ for i := 0; i < len(seen) && i < len(wantSeen); i++ {
+ if seen[i] != wantSeen[i] {
+ t.Errorf("path %#v seen walking tree: got %q, want %q", i, seen[i], wantSeen[i])
+ }
+ }
+}
+
func TestWalkError(t *testing.T) {
initOverlay(t, "{}")
// as an error by any function.
var SkipDir = errors.New("skip this directory")
+// SkipAll is used as a return value from WalkDirFuncs to indicate that
+// all remaining files and directories are to be skipped. It is not returned
+// as an error by any function.
+var SkipAll = errors.New("skip everything and stop the walk")
+
// WalkDirFunc is the type of the function called by WalkDir to visit
// each file or directory.
//
// The error result returned by the function controls how WalkDir
// continues. If the function returns the special value SkipDir, WalkDir
// skips the current directory (path if d.IsDir() is true, otherwise
-// path's parent directory). Otherwise, if the function returns a non-nil
-// error, WalkDir stops entirely and returns that error.
+// path's parent directory). If the function returns the special value
+// SkipAll, WalkDir skips all remaining files and directories. Otherwise,
+// if the function returns a non-nil error, WalkDir stops entirely and
+// returns that error.
//
// The err argument reports an error related to path, signaling that
// WalkDir will not walk into that directory. The function can decide how
// ReadDir. In this second case, the function is called twice with the
// path of the directory: the first call is before the directory read is
// attempted and has err set to nil, giving the function a chance to
-// return SkipDir and avoid the ReadDir entirely. The second call is
-// after a failed ReadDir and reports the error from ReadDir.
+// return SkipDir or SkipAll and avoid the ReadDir entirely. The second call
+// is after a failed ReadDir and reports the error from ReadDir.
// (If ReadDir succeeds, there is no second call.)
//
// The differences between WalkDirFunc compared to filepath.WalkFunc are:
//
// - The second argument has type fs.DirEntry instead of fs.FileInfo.
// - The function is called before reading a directory, to allow SkipDir
-// to bypass the directory read entirely.
+// or SkipAll to bypass the directory read entirely or skip all remaining
+// files and directories respectively.
// - If a directory read fails, the function is called a second time
// for that directory to report the error.
type WalkDirFunc func(path string, d DirEntry, err error) error
} else {
err = walkDir(fsys, root, &statDirEntry{info}, fn)
}
- if err == SkipDir {
+ if err == SkipDir || err == SkipAll {
return nil
}
return err
// as an error by any function.
var SkipDir error = fs.SkipDir
+// SkipAll is used as a return value from WalkFuncs to indicate that
+// all remaining files and directories are to be skipped. It is not returned
+// as an error by any function.
+var SkipAll error = fs.SkipAll
+
// WalkFunc is the type of the function called by Walk to visit each
// file or directory.
//
// The error result returned by the function controls how Walk continues.
// If the function returns the special value SkipDir, Walk skips the
// current directory (path if info.IsDir() is true, otherwise path's
-// parent directory). Otherwise, if the function returns a non-nil error,
-// Walk stops entirely and returns that error.
+// parent directory). If the function returns the special value SkipAll,
+// Walk skips all remaining files and directories. Otherwise, if the function
+// returns a non-nil error, Walk stops entirely and returns that error.
//
// The err argument reports an error related to path, signaling that Walk
// will not walk into that directory. The function can decide how to
if err != nil || err1 != nil {
// The caller's behavior is controlled by the return value, which is decided
// by walkFn. walkFn may ignore err and return nil.
- // If walkFn returns SkipDir, it will be handled by the caller.
+ // If walkFn returns SkipDir or SkipAll, it will be handled by the caller.
// So walk should return whatever walkFn returns.
return err1
}
} else {
err = walkDir(root, &statDirEntry{info}, fn)
}
- if err == SkipDir {
+ if err == SkipDir || err == SkipAll {
return nil
}
return err
} else {
err = walk(root, info, fn)
}
- if err == SkipDir {
+ if err == SkipDir || err == SkipAll {
return nil
}
return err
})
}
+func TestWalkSkipAllOnFile(t *testing.T) {
+ td := t.TempDir()
+
+ if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ touch(t, filepath.Join(td, "dir", "foo1"))
+ touch(t, filepath.Join(td, "dir", "foo2"))
+ touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
+ touch(t, filepath.Join(td, "dir", "foo4"))
+ touch(t, filepath.Join(td, "dir2", "bar"))
+ touch(t, filepath.Join(td, "last"))
+
+ remainingWereSkipped := true
+ walker := func(path string) error {
+ if strings.HasSuffix(path, "foo2") {
+ return filepath.SkipAll
+ }
+
+ if strings.HasSuffix(path, "foo3") ||
+ strings.HasSuffix(path, "foo4") ||
+ strings.HasSuffix(path, "bar") ||
+ strings.HasSuffix(path, "last") {
+ remainingWereSkipped = false
+ }
+ return nil
+ }
+
+ walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
+ walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
+
+ check := func(t *testing.T, walk func(root string) error, root string) {
+ t.Helper()
+ remainingWereSkipped = true
+ if err := walk(root); err != nil {
+ t.Fatal(err)
+ }
+ if !remainingWereSkipped {
+ t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
+ }
+ }
+
+ t.Run("Walk", func(t *testing.T) {
+ Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
+ check(t, Walk, td)
+ check(t, Walk, filepath.Join(td, "dir"))
+ })
+ t.Run("WalkDir", func(t *testing.T) {
+ WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
+ check(t, WalkDir, td)
+ check(t, WalkDir, filepath.Join(td, "dir"))
+ })
+}
+
func TestWalkFileError(t *testing.T) {
td := t.TempDir()