1
2
3
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
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",
125 "node_modules/f.go": "package nodemodules;",
126 "v/f.go": "package v;",
127 "mod/f.go": "package mod;",
128 "shouldfind/f.go": "package shouldfind;",
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
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",
161 "ignoreme/subignore/f.go": "package subignore",
162 "shouldfind/f.go": "package shouldfind;",
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
187
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
232
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 }
253
View as plain text