Files
kjol/tools/tsgo/internal/fswatch/walkdir_test.go
2026-07-09 16:50:43 -04:00

223 lines
6.2 KiB
Go

package fswatch
import (
"errors"
"os"
"path/filepath"
"runtime"
"testing"
)
type walkDirFunc = func(dir string, recursive bool, fn func(string, bool) error) error
func runWalkDirTest(t *testing.T, fn func(t *testing.T, walk walkDirFunc)) {
t.Helper()
t.Parallel()
for _, rt := range []struct {
name string
fn walkDirFunc
}{
{"native", walkDir},
{"generic", walkDirGeneric},
} {
t.Run(rt.name, func(t *testing.T) {
t.Parallel()
fn(t, rt.fn)
})
}
}
func TestWalkDirDoesNotFollowSymlinkedDir(t *testing.T) { //nolint:paralleltest // runWalkDirTest calls t.Parallel.
runWalkDirTest(t, testWalkDirDoesNotFollowSymlinkedDir)
}
func TestWalkDirDoesNotFollowRootSymlinkedDir(t *testing.T) { //nolint:paralleltest // runWalkDirTest calls t.Parallel.
runWalkDirTest(t, testWalkDirDoesNotFollowRootSymlinkedDir)
}
func testWalkDirDoesNotFollowRootSymlinkedDir(t *testing.T, walk walkDirFunc) {
root := newTmpDir(t)
target := filepath.Join(t.TempDir(), "target")
if err := os.Mkdir(target, 0o755); err != nil {
t.Fatal(err)
}
link := filepath.Join(root, "link")
makeDirSymlink(t, target, link)
if err := walk(link, true, nil); err == nil {
t.Fatal("expected error for root symlinked directory")
}
}
func testWalkDirDoesNotFollowSymlinkedDir(t *testing.T, walk walkDirFunc) {
root := newTmpDir(t)
target := filepath.Join(t.TempDir(), "target")
if err := os.Mkdir(target, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(target, "child"), []byte("hidden"), 0o644); err != nil {
t.Fatal(err)
}
link := filepath.Join(root, "link")
makeDirSymlink(t, target, link)
found := map[string]bool{}
if err := walk(root, true, func(path string, isDir bool) error {
found[path] = isDir
return nil
}); err != nil {
t.Fatal(err)
}
isDir, ok := found[link]
if !ok {
t.Fatalf("symlink %q missing from walk", link)
}
if isDir {
t.Fatalf("symlink %q was treated as a directory", link)
}
if _, ok := found[filepath.Join(link, "child")]; ok {
t.Fatal("walkDir followed symlinked directory")
}
}
func TestWalkDirIgnoresUnreadableSubdir(t *testing.T) { //nolint:paralleltest // runWalkDirTest calls t.Parallel.
runWalkDirTest(t, testWalkDirIgnoresUnreadableSubdir)
}
func testWalkDirIgnoresUnreadableSubdir(t *testing.T, walk walkDirFunc) {
if runtime.GOOS == "windows" {
t.Skip("Windows does not enforce POSIX directory permission bits")
}
if os.Geteuid() == 0 {
t.Skip("root can read directories regardless of mode bits")
}
root := newTmpDir(t)
denied := filepath.Join(root, "denied")
if err := os.Mkdir(denied, 0o700); err != nil {
t.Fatal(err)
}
child := filepath.Join(denied, "child")
if err := os.WriteFile(child, []byte("hidden"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.Chmod(denied, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(denied, 0o700) })
found := map[string]bool{}
if err := walk(root, true, func(path string, isDir bool) error {
found[path] = isDir
return nil
}); err != nil {
t.Fatal(err)
}
if _, ok := found[denied]; ok {
t.Fatalf("unreadable directory should be ignored, found %q", denied)
}
if _, ok := found[child]; ok {
t.Fatalf("unreadable child should be ignored, found %q", child)
}
}
func TestWalkDirMissingDir(t *testing.T) { runWalkDirTest(t, testWalkDirMissingDir) } //nolint:paralleltest // runWalkDirTest calls t.Parallel.
func testWalkDirMissingDir(t *testing.T, walk walkDirFunc) {
dir := filepath.Join(t.TempDir(), "nonexistent")
if err := walk(dir, true, nil); err == nil {
t.Fatal("expected error for missing directory")
}
}
func TestWalkDirNotADir(t *testing.T) { runWalkDirTest(t, testWalkDirNotADir) } //nolint:paralleltest // runWalkDirTest calls t.Parallel.
func testWalkDirNotADir(t *testing.T, walk walkDirFunc) {
f := filepath.Join(t.TempDir(), "file")
if err := os.WriteFile(f, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := walk(f, true, nil); err == nil {
t.Fatal("expected error for non-directory")
}
}
func TestWalkDirEntries(t *testing.T) { runWalkDirTest(t, testWalkDirEntries) } //nolint:paralleltest // runWalkDirTest calls t.Parallel.
func testWalkDirEntries(t *testing.T, walk walkDirFunc) {
root := newTmpDir(t)
if err := os.WriteFile(filepath.Join(root, "a.txt"), []byte("a"), 0o644); err != nil {
t.Fatal(err)
}
sub := filepath.Join(root, "sub")
if err := os.Mkdir(sub, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sub, "b.txt"), []byte("b"), 0o644); err != nil {
t.Fatal(err)
}
found := map[string]bool{}
if err := walk(root, true, func(path string, isDir bool) error {
found[path] = isDir
return nil
}); err != nil {
t.Fatal(err)
}
if _, ok := found[filepath.Join(root, "a.txt")]; !ok {
t.Fatal("missing a.txt")
}
if _, ok := found[sub]; !ok {
t.Fatal("missing sub/")
}
if _, ok := found[filepath.Join(sub, "b.txt")]; !ok {
t.Fatal("missing sub/b.txt")
}
}
func TestWalkDirCallback(t *testing.T) { runWalkDirTest(t, testWalkDirCallback) } //nolint:paralleltest // runWalkDirTest calls t.Parallel.
func testWalkDirCallback(t *testing.T, walk walkDirFunc) {
root := newTmpDir(t)
sub := filepath.Join(root, "sub")
if err := os.Mkdir(sub, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sub, "f.txt"), []byte("f"), 0o644); err != nil {
t.Fatal(err)
}
var dirs, files []string
err := walk(root, true, func(path string, isDir bool) error {
if isDir {
dirs = append(dirs, path)
} else {
files = append(files, path)
}
return nil
})
if err != nil {
t.Fatal(err)
}
if len(dirs) < 2 {
t.Fatalf("expected at least 2 dirs (root + sub), got %d: %v", len(dirs), dirs)
}
if len(files) < 1 {
t.Fatalf("expected at least 1 file, got %d", len(files))
}
}
func TestWalkDirCallbackError(t *testing.T) { runWalkDirTest(t, testWalkDirCallbackError) } //nolint:paralleltest // runWalkDirTest calls t.Parallel.
func testWalkDirCallbackError(t *testing.T, walk walkDirFunc) {
root := newTmpDir(t)
if err := os.WriteFile(filepath.Join(root, "a.txt"), []byte("a"), 0o644); err != nil {
t.Fatal(err)
}
sentinel := errors.New("stop")
err := walk(root, true, func(path string, isDir bool) error {
return sentinel
})
if !errors.Is(err, sentinel) {
t.Fatalf("expected sentinel error, got %v", err)
}
}