github.com/3JoB/vfs@v1.0.0/walk_test.go (about) 1 package vfs 2 3 import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "reflect" 8 "runtime" 9 "strings" 10 "testing" 11 ) 12 13 type Node struct { 14 name string 15 entries []*Node // nil if the entry is a file 16 mark int 17 } 18 19 var tree = &Node{ 20 name: "testdata", 21 entries: []*Node{ 22 {"a", nil, 0}, 23 {"b", []*Node{}, 0}, 24 {"c", nil, 0}, 25 { 26 "d", 27 []*Node{ 28 {"x", nil, 0}, 29 {"y", []*Node{}, 0}, 30 { 31 "z", 32 []*Node{ 33 {"u", nil, 0}, 34 {"v", nil, 0}, 35 }, 36 0, 37 }, 38 }, 39 0, 40 }, 41 }, 42 mark: 0, 43 } 44 45 func walkTree(n *Node, path string, f func(path string, n *Node)) { 46 f(path, n) 47 for _, e := range n.entries { 48 walkTree(e, filepath.Join(path, e.name), f) 49 } 50 } 51 52 func makeTree(t *testing.T, fs Filesystem) { 53 walkTree(tree, tree.name, func(path string, n *Node) { 54 if n.entries == nil { 55 fd, err := fs.OpenFile(path, os.O_CREATE, os.ModePerm) 56 if err != nil { 57 t.Errorf("makeTree: %v", err) 58 return 59 } 60 fd.Close() 61 } else { 62 fs.Mkdir(path, 0770) 63 } 64 }) 65 } 66 67 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } 68 69 func checkMarks(t *testing.T, report bool) { 70 walkTree(tree, tree.name, func(path string, n *Node) { 71 if n.mark != 1 && report { 72 t.Errorf("node %s mark = %d; expected 1", path, n.mark) 73 } 74 n.mark = 0 75 }) 76 } 77 78 // Assumes that each node name is unique. Good enough for a test. 79 // If clear is true, any incoming error is cleared before return. The errors 80 // are always accumulated, though. 81 func mark(info os.FileInfo, err error, errors *[]error, clear bool) error { 82 name := info.Name() 83 walkTree(tree, tree.name, func(path string, n *Node) { 84 if n.name == name { 85 n.mark++ 86 } 87 }) 88 if err != nil { 89 *errors = append(*errors, err) 90 if clear { 91 return nil 92 } 93 return err 94 } 95 return nil 96 } 97 98 func chtmpdir(t *testing.T) (restore func()) { 99 oldwd, err := os.Getwd() 100 if err != nil { 101 t.Fatalf("chtmpdir: %v", err) 102 } 103 d, err := os.MkdirTemp("", "test") 104 if err != nil { 105 t.Fatalf("chtmpdir: %v", err) 106 } 107 if err := os.Chdir(d); err != nil { 108 t.Fatalf("chtmpdir: %v", err) 109 } 110 return func() { 111 if err := os.Chdir(oldwd); err != nil { 112 t.Fatalf("chtmpdir: %v", err) 113 } 114 os.RemoveAll(d) 115 } 116 } 117 118 func TestWalk(t *testing.T) { 119 if runtime.GOOS == "darwin" { 120 switch runtime.GOARCH { 121 case "arm", "arm64": 122 restore := chtmpdir(t) 123 defer restore() 124 } 125 } 126 127 tmpDir, err := os.MkdirTemp("", "TestWalk") 128 if err != nil { 129 t.Fatal("creating temp dir:", err) 130 } 131 defer os.RemoveAll(tmpDir) 132 133 origDir, err := os.Getwd() 134 if err != nil { 135 t.Fatal("finding working dir:", err) 136 } 137 if err = os.Chdir(tmpDir); err != nil { 138 t.Fatal("entering temp dir:", err) 139 } 140 defer os.Chdir(origDir) 141 142 fs := OS() 143 144 makeTree(t, fs) 145 errors := make([]error, 0, 10) 146 clear := true 147 markFn := func(path string, info os.FileInfo, err error) error { 148 return mark(info, err, &errors, clear) 149 } 150 // Expect no errors. 151 err = Walk(fs, tree.name, markFn) 152 if err != nil { 153 t.Fatalf("no error expected, found: %s", err) 154 } 155 if len(errors) != 0 { 156 t.Fatalf("unexpected errors: %s", errors) 157 } 158 checkMarks(t, true) 159 errors = errors[0:0] 160 161 // Test permission errors. Only possible if we're not root 162 // and only on some file systems (AFS, FAT). To avoid errors during 163 // all.bash on those file systems, skip during go test -short. 164 if os.Getuid() > 0 && !testing.Short() { 165 // introduce 2 errors: chmod top-level directories to 0 166 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) 167 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) 168 169 // 3) capture errors, expect two. 170 // mark respective subtrees manually 171 markTree(tree.entries[1]) 172 markTree(tree.entries[3]) 173 // correct double-marking of directory itself 174 tree.entries[1].mark-- 175 tree.entries[3].mark-- 176 err := Walk(fs, tree.name, markFn) 177 if err != nil { 178 t.Fatalf("expected no error return from Walk, got %s", err) 179 } 180 if len(errors) != 2 { 181 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) 182 } 183 // the inaccessible subtrees were marked manually 184 checkMarks(t, true) 185 errors = errors[0:0] 186 187 // 4) capture errors, stop after first error. 188 // mark respective subtrees manually 189 markTree(tree.entries[1]) 190 markTree(tree.entries[3]) 191 // correct double-marking of directory itself 192 tree.entries[1].mark-- 193 tree.entries[3].mark-- 194 clear = false // error will stop processing 195 err = Walk(fs, tree.name, markFn) 196 if err == nil { 197 t.Fatalf("expected error return from Walk") 198 } 199 if len(errors) != 1 { 200 t.Errorf("expected 1 error, got %d: %s", len(errors), errors) 201 } 202 // the inaccessible subtrees were marked manually 203 checkMarks(t, false) 204 errors = errors[0:0] 205 206 // restore permissions 207 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) 208 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) 209 } 210 } 211 212 func touch(t *testing.T, fs Filesystem, name string) { 213 f, err := fs.OpenFile(name, os.O_CREATE, os.ModePerm) 214 if err != nil { 215 t.Fatal(err) 216 } 217 if err := f.Close(); err != nil { 218 t.Fatal(err) 219 } 220 } 221 222 func TestWalkSkipDirOnFile(t *testing.T) { 223 td, err := os.MkdirTemp("", "walktest") 224 if err != nil { 225 t.Fatal(err) 226 } 227 defer os.RemoveAll(td) 228 229 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil { 230 t.Fatal(err) 231 } 232 233 fs := OS() 234 235 touch(t, fs, filepath.Join(td, "dir/foo1")) 236 touch(t, fs, filepath.Join(td, "dir/foo2")) 237 238 sawFoo2 := false 239 walker := func(path string, info os.FileInfo, err error) error { 240 if strings.HasSuffix(path, "foo2") { 241 sawFoo2 = true 242 } 243 if strings.HasSuffix(path, "foo1") { 244 return filepath.SkipDir 245 } 246 return nil 247 } 248 249 err = Walk(fs, td, walker) 250 if err != nil { 251 t.Fatal(err) 252 } 253 if sawFoo2 { 254 t.Errorf("SkipDir on file foo1 did not block processing of foo2") 255 } 256 257 err = Walk(fs, filepath.Join(td, "dir"), walker) 258 if err != nil { 259 t.Fatal(err) 260 } 261 if sawFoo2 { 262 t.Errorf("SkipDir on file foo1 did not block processing of foo2") 263 } 264 } 265 266 type statWrapper struct { 267 Filesystem 268 269 statErr error 270 } 271 272 func (s *statWrapper) Lstat(path string) (os.FileInfo, error) { 273 if strings.HasSuffix(path, "stat-error") { 274 return nil, s.statErr 275 } 276 return os.Lstat(path) 277 } 278 279 func TestWalkFileError(t *testing.T) { 280 td, err := os.MkdirTemp("", "walktest") 281 if err != nil { 282 t.Fatal(err) 283 } 284 defer os.RemoveAll(td) 285 286 fs := Filesystem(OS()) 287 288 touch(t, fs, filepath.Join(td, "foo")) 289 touch(t, fs, filepath.Join(td, "bar")) 290 dir := filepath.Join(td, "dir") 291 if err := MkdirAll(fs, filepath.Join(td, "dir"), 0755); err != nil { 292 t.Fatal(err) 293 } 294 touch(t, fs, filepath.Join(dir, "baz")) 295 touch(t, fs, filepath.Join(dir, "stat-error")) 296 statErr := errors.New("some stat error") 297 298 fs = &statWrapper{Filesystem: fs, statErr: statErr} 299 300 got := map[string]error{} 301 err = Walk(fs, td, func(path string, fi os.FileInfo, err error) error { 302 rel, _ := filepath.Rel(td, path) 303 got[filepath.ToSlash(rel)] = err 304 return nil 305 }) 306 if err != nil { 307 t.Errorf("Walk error: %v", err) 308 } 309 want := map[string]error{ 310 ".": nil, 311 "foo": nil, 312 "bar": nil, 313 "dir": nil, 314 "dir/baz": nil, 315 "dir/stat-error": statErr, 316 } 317 if !reflect.DeepEqual(got, want) { 318 t.Errorf("Walked %#v; want %#v", got, want) 319 } 320 }