golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/gopathwalk/walk_test.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gopathwalk 6 7 import ( 8 "os" 9 "path/filepath" 10 "reflect" 11 "runtime" 12 "sort" 13 "strings" 14 "sync" 15 "testing" 16 ) 17 18 func TestSymlinkTraversal(t *testing.T) { 19 t.Parallel() 20 21 gopath := t.TempDir() 22 23 if err := mapToDir(gopath, map[string]string{ 24 "a/b/c": "LINK:../../a/d", 25 "a/b/pkg/pkg.go": "package pkg", 26 "a/d/e": "LINK:../../a/b", 27 "a/d/pkg/pkg.go": "package pkg", 28 "a/f/loop": "LINK:../f", 29 "a/f/pkg/pkg.go": "package pkg", 30 "a/g/pkg/pkg.go": "LINK:../../f/pkg/pkg.go", 31 "a/self": "LINK:.", 32 }); err != nil { 33 switch runtime.GOOS { 34 case "windows", "plan9": 35 t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS) 36 } 37 t.Fatal(err) 38 } 39 40 pkgc := make(chan []string, 1) 41 pkgc <- nil 42 add := func(root Root, dir string) { 43 rel, err := filepath.Rel(filepath.Join(root.Path, "src"), dir) 44 if err != nil { 45 t.Error(err) 46 } 47 pkgc <- append(<-pkgc, filepath.ToSlash(rel)) 48 } 49 50 Walk([]Root{{Path: gopath, Type: RootGOPATH}}, add, Options{Logf: t.Logf}) 51 52 pkgs := <-pkgc 53 sort.Strings(pkgs) 54 t.Logf("Found packages:\n\t%s", strings.Join(pkgs, "\n\t")) 55 56 got := make(map[string]bool, len(pkgs)) 57 for _, pkg := range pkgs { 58 got[pkg] = true 59 } 60 tests := []struct { 61 path string 62 want bool 63 why string 64 }{ 65 { 66 path: "a/b/pkg", 67 want: true, 68 why: "found via regular directories", 69 }, 70 { 71 path: "a/b/c/pkg", 72 want: true, 73 why: "found via non-cyclic dir link", 74 }, 75 { 76 path: "a/b/c/e/pkg", 77 want: true, 78 why: "found via two non-cyclic dir links", 79 }, 80 { 81 path: "a/d/e/c/pkg", 82 want: true, 83 why: "found via two non-cyclic dir links", 84 }, 85 { 86 path: "a/f/loop/pkg", 87 want: true, 88 why: "found via a single parent-dir link", 89 }, 90 { 91 path: "a/f/loop/loop/pkg", 92 want: false, 93 why: "would follow loop symlink twice", 94 }, 95 { 96 path: "a/self/b/pkg", 97 want: true, 98 why: "follows self-link once", 99 }, 100 { 101 path: "a/self/self/b/pkg", 102 want: false, 103 why: "would follow self-link twice", 104 }, 105 } 106 for _, tc := range tests { 107 if got[tc.path] != tc.want { 108 if tc.want { 109 t.Errorf("MISSING: %s (%s)", tc.path, tc.why) 110 } else { 111 t.Errorf("UNEXPECTED: %s (%s)", tc.path, tc.why) 112 } 113 } 114 } 115 } 116 117 // TestSkip tests that various goimports rules are followed in non-modules mode. 118 func TestSkip(t *testing.T) { 119 t.Parallel() 120 121 dir := t.TempDir() 122 123 if err := mapToDir(dir, map[string]string{ 124 "ignoreme/f.go": "package ignoreme", // ignored by .goimportsignore 125 "node_modules/f.go": "package nodemodules;", // ignored by hardcoded node_modules filter 126 "v/f.go": "package v;", // ignored by hardcoded vgo cache rule 127 "mod/f.go": "package mod;", // ignored by hardcoded vgo cache rule 128 "shouldfind/f.go": "package shouldfind;", // not ignored 129 130 ".goimportsignore": "ignoreme\n", 131 }); err != nil { 132 t.Fatal(err) 133 } 134 135 var found []string 136 var mu sync.Mutex 137 walkDir(Root{filepath.Join(dir, "src"), RootGOPATH}, 138 func(root Root, dir string) { 139 mu.Lock() 140 defer mu.Unlock() 141 found = append(found, dir[len(root.Path)+1:]) 142 }, func(root Root, dir string) bool { 143 return false 144 }, Options{ 145 ModulesEnabled: false, 146 Logf: t.Logf, 147 }) 148 if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) { 149 t.Errorf("expected to find only %v, got %v", want, found) 150 } 151 } 152 153 // TestSkipFunction tests that scan successfully skips directories from user callback. 154 func TestSkipFunction(t *testing.T) { 155 t.Parallel() 156 157 dir := t.TempDir() 158 159 if err := mapToDir(dir, map[string]string{ 160 "ignoreme/f.go": "package ignoreme", // ignored by skip 161 "ignoreme/subignore/f.go": "package subignore", // also ignored by skip 162 "shouldfind/f.go": "package shouldfind;", // not ignored 163 }); err != nil { 164 t.Fatal(err) 165 } 166 167 var found []string 168 var mu sync.Mutex 169 walkDir(Root{filepath.Join(dir, "src"), RootGOPATH}, 170 func(root Root, dir string) { 171 mu.Lock() 172 defer mu.Unlock() 173 found = append(found, dir[len(root.Path)+1:]) 174 }, func(root Root, dir string) bool { 175 return strings.HasSuffix(dir, "ignoreme") 176 }, 177 Options{ 178 ModulesEnabled: false, 179 Logf: t.Logf, 180 }) 181 if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) { 182 t.Errorf("expected to find only %v, got %v", want, found) 183 } 184 } 185 186 // TestWalkSymlinkConcurrentDeletion is a regression test for the panic reported 187 // in https://go.dev/issue/58054#issuecomment-1791513726. 188 func TestWalkSymlinkConcurrentDeletion(t *testing.T) { 189 t.Parallel() 190 191 src := t.TempDir() 192 193 m := map[string]string{ 194 "dir/readme.txt": "dir is not a go package", 195 "dirlink": "LINK:dir", 196 } 197 if err := mapToDir(src, m); err != nil { 198 switch runtime.GOOS { 199 case "windows", "plan9": 200 t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS) 201 } 202 t.Fatal(err) 203 } 204 205 done := make(chan struct{}) 206 go func() { 207 if err := os.RemoveAll(src); err != nil { 208 t.Log(err) 209 } 210 close(done) 211 }() 212 defer func() { 213 <-done 214 }() 215 216 add := func(root Root, dir string) { 217 t.Errorf("unexpected call to add(%q, %q)", root.Path, dir) 218 } 219 Walk([]Root{{Path: src, Type: RootGOPATH}}, add, Options{Logf: t.Logf}) 220 } 221 222 func mapToDir(destDir string, files map[string]string) error { 223 var symlinkPaths []string 224 for path, contents := range files { 225 file := filepath.Join(destDir, "src", path) 226 if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { 227 return err 228 } 229 var err error 230 if strings.HasPrefix(contents, "LINK:") { 231 // To work around https://go.dev/issue/39183, wait to create symlinks 232 // until we have created all non-symlink paths. 233 symlinkPaths = append(symlinkPaths, path) 234 } else { 235 err = os.WriteFile(file, []byte(contents), 0644) 236 } 237 if err != nil { 238 return err 239 } 240 } 241 242 for _, path := range symlinkPaths { 243 file := filepath.Join(destDir, "src", path) 244 target := filepath.FromSlash(strings.TrimPrefix(files[path], "LINK:")) 245 err := os.Symlink(target, file) 246 if err != nil { 247 return err 248 } 249 } 250 251 return nil 252 }