github.com/charlievieth/fastwalk@v1.0.3/adapters_test.go (about) 1 package fastwalk_test 2 3 import ( 4 "errors" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "reflect" 9 "runtime" 10 "sort" 11 "strings" 12 "sync" 13 "sync/atomic" 14 "testing" 15 16 "github.com/charlievieth/fastwalk" 17 ) 18 19 func TestIgnoreDuplicateDirs(t *testing.T) { 20 tempdir, err := os.MkdirTemp("", "test-fast-walk") 21 if err != nil { 22 t.Fatal(err) 23 } 24 // on macOS the tempdir is a symlink 25 tempdir, err = filepath.EvalSymlinks(tempdir) 26 if err != nil { 27 t.Fatal(err) 28 } 29 defer cleanupOrLogTempDir(t, tempdir) 30 31 files := map[string]string{ 32 "bar/bar.go": "one", 33 "foo/foo.go": "two", 34 "skip/baz.go": "three", // we skip "skip", but visit "baz.go" via "symdir" 35 "symdir": "LINK:skip", 36 "bar/symdir": "LINK:../foo/", 37 "bar/loop": "LINK:../bar/", // symlink loop 38 } 39 testCreateFiles(t, tempdir, files) 40 41 want := map[string]os.FileMode{ 42 "": os.ModeDir, 43 "/src": os.ModeDir, 44 "/src/bar": os.ModeDir, 45 "/src/bar/bar.go": 0, 46 "/src/bar/symdir": os.ModeSymlink, 47 "/src/bar/loop": os.ModeSymlink, 48 "/src/foo": os.ModeDir, 49 "/src/foo/foo.go": 0, 50 "/src/symdir": os.ModeSymlink, 51 "/src/symdir/baz.go": 0, 52 "/src/skip": os.ModeDir, 53 } 54 55 runTest := func(t *testing.T, conf *fastwalk.Config) { 56 var mu sync.Mutex 57 got := make(map[string]os.FileMode) 58 walkFn := fastwalk.IgnoreDuplicateDirs(func(path string, de fs.DirEntry, err error) error { 59 requireNoError(t, err) 60 if err != nil { 61 return err 62 } 63 64 // Resolve links for regular files since we don't know which directory 65 // or link we traversed to visit them. Exclude "baz.go" because we want 66 // to test that we visited it through it's link. 67 if de.Type().IsRegular() && de.Name() != "baz.go" { 68 realpath, err := filepath.EvalSymlinks(path) 69 if err != nil { 70 t.Error(err) 71 return err 72 } 73 path = realpath 74 } 75 if !strings.HasPrefix(path, tempdir) { 76 t.Errorf("Path %q not a child of TMPDIR %q", path, tempdir) 77 return errors.New("abort") 78 } 79 key := filepath.ToSlash(strings.TrimPrefix(path, tempdir)) 80 81 mu.Lock() 82 defer mu.Unlock() 83 got[key] = de.Type().Type() 84 85 if de.Name() == "skip" { 86 return filepath.SkipDir 87 } 88 return nil 89 }) 90 if err := fastwalk.Walk(conf, tempdir, walkFn); err != nil { 91 t.Error("fastwalk:", err) 92 } 93 if !reflect.DeepEqual(want, got) { 94 t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want)) 95 diffFileModes(t, got, want) 96 } 97 } 98 99 t.Run("NoFollow", func(t *testing.T) { 100 runTest(t, &fastwalk.Config{Follow: false}) 101 }) 102 103 // Test that setting Follow to true has no impact on the behavior 104 t.Run("Follow", func(t *testing.T) { 105 runTest(t, &fastwalk.Config{Follow: true}) 106 }) 107 108 t.Run("Error", func(t *testing.T) { 109 tempdir := t.TempDir() 110 if err := os.WriteFile(tempdir+"/error_test", []byte("error"), 0644); err != nil { 111 t.Fatal(err) 112 } 113 want := errors.New("my error") 114 var callCount int32 115 walkFn := fastwalk.IgnoreDuplicateDirs(func(path string, de fs.DirEntry, err error) error { 116 atomic.AddInt32(&callCount, 1) 117 return want 118 }) 119 err := fastwalk.Walk(nil, tempdir, walkFn) 120 if !errors.Is(err, want) { 121 t.Errorf("Error: want: %v got: %v", want, err) 122 } 123 124 }) 125 } 126 127 func TestIgnoreDuplicateFiles(t *testing.T) { 128 tempdir := t.TempDir() 129 files := map[string]string{ 130 "foo/foo.go": "one", 131 "bar/bar.go": "LINK:../foo/foo.go", 132 "bar/baz.go": "two", 133 "broken/broken.go": "LINK:../nonexistent", 134 "bar/loop": "LINK:../bar/", // symlink loop 135 "file.go": "three", 136 137 // Use multiple symdirs to increase the chance that one 138 // of these and not "foo" is followed first. 139 "symdir1": "LINK:foo", 140 "symdir2": "LINK:foo", 141 "symdir3": "LINK:foo", 142 "symdir4": "LINK:foo", 143 } 144 if runtime.GOOS == "windows" { 145 delete(files, "broken/broken.go") 146 } 147 testCreateFiles(t, tempdir, files) 148 149 var expectedContents []string 150 for _, contents := range files { 151 if !strings.HasPrefix(contents, "LINK:") { 152 expectedContents = append(expectedContents, contents) 153 } 154 } 155 sort.Strings(expectedContents) 156 157 var ( 158 mu sync.Mutex 159 seen []os.FileInfo 160 contents []string 161 ) 162 walkFn := fastwalk.IgnoreDuplicateFiles(func(path string, de fs.DirEntry, err error) error { 163 requireNoError(t, err) 164 fi1, err := fastwalk.StatDirEntry(path, de) 165 if err != nil { 166 t.Error(err) 167 return err 168 } 169 mu.Lock() 170 defer mu.Unlock() 171 for _, fi2 := range seen { 172 if os.SameFile(fi1, fi2) { 173 t.Errorf("Visited file twice: %q (%s) and %q (%s)", 174 path, fi1.Mode(), fi2.Name(), fi2.Mode()) 175 } 176 } 177 seen = append(seen, fi1) 178 if fi1.Mode().IsRegular() { 179 data, err := os.ReadFile(path) 180 if err != nil { 181 return err 182 } 183 contents = append(contents, string(data)) 184 } 185 return nil 186 }) 187 if err := fastwalk.Walk(nil, tempdir, walkFn); err != nil { 188 t.Fatal(err) 189 } 190 191 sort.Strings(contents) 192 if !reflect.DeepEqual(expectedContents, contents) { 193 t.Errorf("File contents want: %q got: %q", expectedContents, contents) 194 } 195 } 196 197 func TestIgnorePermissionErrors(t *testing.T) { 198 var called bool 199 fn := fastwalk.IgnorePermissionErrors(func(path string, _ fs.DirEntry, err error) error { 200 called = true 201 if err != nil { 202 t.Fatal(err) 203 } 204 return nil 205 }) 206 207 t.Run("PermissionError", func(t *testing.T) { 208 err := fn("", nil, &os.PathError{Op: "open", Path: "foo.go", Err: os.ErrPermission}) 209 if err != nil { 210 t.Fatal(err) 211 } 212 if called { 213 t.Fatal("walkFn should not have been called with os.ErrPermission") 214 } 215 }) 216 217 t.Run("NilError", func(t *testing.T) { 218 called = false 219 if err := fn("", nil, nil); err != nil { 220 t.Fatal(err) 221 } 222 if !called { 223 t.Fatal("walkFn should have been called with nil error") 224 } 225 }) 226 227 t.Run("OtherError", func(t *testing.T) { 228 fn := fastwalk.IgnorePermissionErrors(func(path string, _ fs.DirEntry, err error) error { 229 return err 230 }) 231 want := &os.PathError{Op: "open", Path: "foo.go", Err: os.ErrExist} 232 if got := fn("", nil, want); got != want { 233 t.Fatalf("want error: %v got: %v", want, got) 234 } 235 }) 236 }