github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/internal/walkdir_iterate_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package internal 16 17 import ( 18 "errors" 19 "io" 20 "io/fs" 21 "os" 22 pathpkg "path" 23 "path/filepath" 24 "reflect" 25 "sort" 26 "testing" 27 "testing/fstest" 28 "time" 29 30 scalibrfs "github.com/google/osv-scalibr/fs" 31 ) 32 33 // These are the same tests as in io/fs/walk_test.go, but ignoring the order of walking. 34 35 type Node struct { 36 name string 37 entries []*Node // nil if the entry is a file 38 mark int 39 } 40 41 var tree = &Node{ 42 "testdata", 43 []*Node{ 44 {"a", nil, 0}, 45 {"b", []*Node{}, 0}, 46 {"c", nil, 0}, 47 { 48 "d", 49 []*Node{ 50 {"x", nil, 0}, 51 {"y", []*Node{}, 0}, 52 { 53 "z", 54 []*Node{ 55 {"u", nil, 0}, 56 {"v", nil, 0}, 57 }, 58 0, 59 }, 60 }, 61 0, 62 }, 63 }, 64 0, 65 } 66 67 func walkTree(n *Node, path string, f func(path string, n *Node)) { 68 f(path, n) 69 for _, e := range n.entries { 70 walkTree(e, pathpkg.Join(path, e.name), f) 71 } 72 } 73 74 func makeTree() scalibrfs.FS { 75 fsys := fstest.MapFS{} 76 walkTree(tree, tree.name, func(path string, n *Node) { 77 if n.entries == nil { 78 fsys[path] = &fstest.MapFile{} 79 } else { 80 fsys[path] = &fstest.MapFile{Mode: fs.ModeDir} 81 } 82 }) 83 return fsys 84 } 85 86 // Assumes that each node name is unique. Good enough for a test. 87 // If clearErr is true, any incoming error is cleared before return. The errors 88 // are always accumulated, though. 89 func mark(tree *Node, entry fs.DirEntry, err error, errors *[]error, clearErr bool) error { 90 name := entry.Name() 91 walkTree(tree, tree.name, func(path string, n *Node) { 92 if n.name == name { 93 n.mark++ 94 } 95 }) 96 if err != nil { 97 *errors = append(*errors, err) 98 if clearErr { 99 return nil 100 } 101 return err 102 } 103 return nil 104 } 105 106 func TestWalkDir(t *testing.T) { 107 t.Chdir(t.TempDir()) 108 109 fsys := makeTree() 110 errors := make([]error, 0, 10) 111 clearErr := true 112 markFn := func(path string, entry fs.DirEntry, err error) error { 113 return mark(tree, entry, err, &errors, clearErr) 114 } 115 // Expect no errors. 116 err := WalkDirUnsorted(fsys, ".", markFn, nil) 117 if err != nil { 118 t.Fatalf("no error expected, found: %s", err) 119 } 120 if len(errors) != 0 { 121 t.Fatalf("unexpected errors: %s", errors) 122 } 123 walkTree(tree, tree.name, func(path string, n *Node) { 124 if n.mark != 1 { 125 t.Errorf("node %s mark = %d; expected 1", path, n.mark) 126 } 127 n.mark = 0 128 }) 129 } 130 131 func TestIssue51617(t *testing.T) { 132 dir := t.TempDir() 133 for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} { 134 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil { 135 t.Fatal(err) 136 } 137 } 138 bad := filepath.Join(dir, "a", "bad") 139 if err := os.Chmod(bad, 0); err != nil { 140 t.Fatal(err) 141 } 142 //nolint:errcheck 143 defer os.Chmod(bad, 0700) // avoid errors on cleanup 144 var saw []string 145 err := WalkDirUnsorted(scalibrfs.DirFS(dir), ".", func(path string, d fs.DirEntry, err error) error { 146 if err != nil { 147 return filepath.SkipDir 148 } 149 if d.IsDir() { 150 saw = append(saw, path) 151 } 152 return nil 153 }, nil) 154 if err != nil { 155 t.Fatal(err) 156 } 157 want := []string{".", "a", "a/bad", "a/next"} 158 sort.Strings(saw) 159 if !reflect.DeepEqual(saw, want) { 160 t.Errorf("got directories %v, want %v", saw, want) 161 } 162 } 163 164 // FS implementation that doesn't implement ReadDirFile on the sub-directories. 165 type fakeFS struct{} 166 167 func (f fakeFS) Open(name string) (fs.File, error) { 168 return &fakeFile{}, nil 169 } 170 func (fakeFS) ReadDir(name string) ([]fs.DirEntry, error) { 171 if name == "." { // root dir 172 return []fs.DirEntry{ 173 &fakeDirEntry{name: "file1.txt", isDir: false}, 174 &fakeDirEntry{name: "dir", isDir: true}, 175 }, nil 176 } else if name == "dir" { 177 return []fs.DirEntry{&fakeDirEntry{name: "file2.txt", isDir: false}}, nil 178 } 179 return nil, errors.New("file not found") 180 } 181 func (fakeFS) Stat(name string) (fs.FileInfo, error) { 182 return &fakeDirEntry{name: name, isDir: name == "." || name == "dir"}, nil 183 } 184 185 type fakeFile struct{} 186 187 func (f *fakeFile) Stat() (fs.FileInfo, error) { return nil, nil } 188 func (f *fakeFile) Read(buffer []byte) (count int, err error) { return 0, io.EOF } 189 func (*fakeFile) Close() error { return nil } 190 191 var fakeFSTree = &Node{ 192 ".", 193 []*Node{ 194 {"file1.txt", nil, 0}, 195 { 196 "dir", 197 []*Node{{"file2.txt", nil, 0}}, 198 0, 199 }, 200 }, 201 0, 202 } 203 204 type fakeDirEntry struct { 205 name string 206 isDir bool 207 } 208 209 func (f *fakeDirEntry) Name() string { return f.name } 210 func (f *fakeDirEntry) Size() int64 { return 0 } 211 func (f *fakeDirEntry) Mode() fs.FileMode { return 0 } 212 func (f *fakeDirEntry) ModTime() time.Time { return time.Time{} } 213 func (f *fakeDirEntry) IsDir() bool { return f.isDir } 214 func (f *fakeDirEntry) Type() fs.FileMode { return 0 } 215 func (f *fakeDirEntry) Info() (fs.FileInfo, error) { return nil, errors.New("not implemented") } 216 func (f *fakeDirEntry) Sys() any { return nil } 217 218 func TestWalkDirFallbackToDirFS(t *testing.T) { 219 fsys := &fakeFS{} 220 errors := make([]error, 0, 10) 221 clearErr := true 222 markFn := func(path string, entry fs.DirEntry, err error) error { 223 return mark(fakeFSTree, entry, err, &errors, clearErr) 224 } 225 // Expect no errors. 226 if err := WalkDirUnsorted(fsys, ".", markFn, nil); err != nil { 227 t.Fatalf("no error expected, found: %s", err) 228 } 229 if len(errors) != 0 { 230 t.Fatalf("unexpected errors: %s", errors) 231 } 232 walkTree(fakeFSTree, fakeFSTree.name, func(path string, n *Node) { 233 if n.mark != 1 { 234 t.Errorf("node %s mark = %d; expected 1", path, n.mark) 235 } 236 n.mark = 0 237 }) 238 }