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