1 | // Copyright 2016 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 fastwalk_test |
6 | |
7 | import ( |
8 | "bytes" |
9 | "flag" |
10 | "fmt" |
11 | "io/ioutil" |
12 | "os" |
13 | "path/filepath" |
14 | "reflect" |
15 | "runtime" |
16 | "sort" |
17 | "strings" |
18 | "sync" |
19 | "testing" |
20 | |
21 | "golang.org/x/tools/internal/fastwalk" |
22 | ) |
23 | |
24 | func formatFileModes(m map[string]os.FileMode) string { |
25 | var keys []string |
26 | for k := range m { |
27 | keys = append(keys, k) |
28 | } |
29 | sort.Strings(keys) |
30 | var buf bytes.Buffer |
31 | for _, k := range keys { |
32 | fmt.Fprintf(&buf, "%-20s: %v\n", k, m[k]) |
33 | } |
34 | return buf.String() |
35 | } |
36 | |
37 | func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) { |
38 | tempdir, err := ioutil.TempDir("", "test-fast-walk") |
39 | if err != nil { |
40 | t.Fatal(err) |
41 | } |
42 | defer os.RemoveAll(tempdir) |
43 | |
44 | symlinks := map[string]string{} |
45 | for path, contents := range files { |
46 | file := filepath.Join(tempdir, "/src", path) |
47 | if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { |
48 | t.Fatal(err) |
49 | } |
50 | var err error |
51 | if strings.HasPrefix(contents, "LINK:") { |
52 | symlinks[file] = filepath.FromSlash(strings.TrimPrefix(contents, "LINK:")) |
53 | } else { |
54 | err = ioutil.WriteFile(file, []byte(contents), 0644) |
55 | } |
56 | if err != nil { |
57 | t.Fatal(err) |
58 | } |
59 | } |
60 | |
61 | // Create symlinks after all other files. Otherwise, directory symlinks on |
62 | // Windows are unusable (see https://golang.org/issue/39183). |
63 | for file, dst := range symlinks { |
64 | err = os.Symlink(dst, file) |
65 | if err != nil { |
66 | if writeErr := ioutil.WriteFile(file, []byte(dst), 0644); writeErr == nil { |
67 | // Couldn't create symlink, but could write the file. |
68 | // Probably this filesystem doesn't support symlinks. |
69 | // (Perhaps we are on an older Windows and not running as administrator.) |
70 | t.Skipf("skipping because symlinks appear to be unsupported: %v", err) |
71 | } |
72 | } |
73 | } |
74 | |
75 | got := map[string]os.FileMode{} |
76 | var mu sync.Mutex |
77 | err = fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error { |
78 | mu.Lock() |
79 | defer mu.Unlock() |
80 | if !strings.HasPrefix(path, tempdir) { |
81 | t.Errorf("bogus prefix on %q, expect %q", path, tempdir) |
82 | } |
83 | key := filepath.ToSlash(strings.TrimPrefix(path, tempdir)) |
84 | if old, dup := got[key]; dup { |
85 | t.Errorf("callback called twice for key %q: %v -> %v", key, old, typ) |
86 | } |
87 | got[key] = typ |
88 | return callback(path, typ) |
89 | }) |
90 | |
91 | if err != nil { |
92 | t.Fatalf("callback returned: %v", err) |
93 | } |
94 | if !reflect.DeepEqual(got, want) { |
95 | t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want)) |
96 | } |
97 | } |
98 | |
99 | func TestFastWalk_Basic(t *testing.T) { |
100 | testFastWalk(t, map[string]string{ |
101 | "foo/foo.go": "one", |
102 | "bar/bar.go": "two", |
103 | "skip/skip.go": "skip", |
104 | }, |
105 | func(path string, typ os.FileMode) error { |
106 | return nil |
107 | }, |
108 | map[string]os.FileMode{ |
109 | "": os.ModeDir, |
110 | "/src": os.ModeDir, |
111 | "/src/bar": os.ModeDir, |
112 | "/src/bar/bar.go": 0, |
113 | "/src/foo": os.ModeDir, |
114 | "/src/foo/foo.go": 0, |
115 | "/src/skip": os.ModeDir, |
116 | "/src/skip/skip.go": 0, |
117 | }) |
118 | } |
119 | |
120 | func TestFastWalk_LongFileName(t *testing.T) { |
121 | longFileName := strings.Repeat("x", 255) |
122 | |
123 | testFastWalk(t, map[string]string{ |
124 | longFileName: "one", |
125 | }, |
126 | func(path string, typ os.FileMode) error { |
127 | return nil |
128 | }, |
129 | map[string]os.FileMode{ |
130 | "": os.ModeDir, |
131 | "/src": os.ModeDir, |
132 | "/src/" + longFileName: 0, |
133 | }, |
134 | ) |
135 | } |
136 | |
137 | func TestFastWalk_Symlink(t *testing.T) { |
138 | testFastWalk(t, map[string]string{ |
139 | "foo/foo.go": "one", |
140 | "bar/bar.go": "LINK:../foo/foo.go", |
141 | "symdir": "LINK:foo", |
142 | "broken/broken.go": "LINK:../nonexistent", |
143 | }, |
144 | func(path string, typ os.FileMode) error { |
145 | return nil |
146 | }, |
147 | map[string]os.FileMode{ |
148 | "": os.ModeDir, |
149 | "/src": os.ModeDir, |
150 | "/src/bar": os.ModeDir, |
151 | "/src/bar/bar.go": os.ModeSymlink, |
152 | "/src/foo": os.ModeDir, |
153 | "/src/foo/foo.go": 0, |
154 | "/src/symdir": os.ModeSymlink, |
155 | "/src/broken": os.ModeDir, |
156 | "/src/broken/broken.go": os.ModeSymlink, |
157 | }) |
158 | } |
159 | |
160 | func TestFastWalk_SkipDir(t *testing.T) { |
161 | testFastWalk(t, map[string]string{ |
162 | "foo/foo.go": "one", |
163 | "bar/bar.go": "two", |
164 | "skip/skip.go": "skip", |
165 | }, |
166 | func(path string, typ os.FileMode) error { |
167 | if typ == os.ModeDir && strings.HasSuffix(path, "skip") { |
168 | return filepath.SkipDir |
169 | } |
170 | return nil |
171 | }, |
172 | map[string]os.FileMode{ |
173 | "": os.ModeDir, |
174 | "/src": os.ModeDir, |
175 | "/src/bar": os.ModeDir, |
176 | "/src/bar/bar.go": 0, |
177 | "/src/foo": os.ModeDir, |
178 | "/src/foo/foo.go": 0, |
179 | "/src/skip": os.ModeDir, |
180 | }) |
181 | } |
182 | |
183 | func TestFastWalk_SkipFiles(t *testing.T) { |
184 | // Directory iteration order is undefined, so there's no way to know |
185 | // which file to expect until the walk happens. Rather than mess |
186 | // with the test infrastructure, just mutate want. |
187 | var mu sync.Mutex |
188 | want := map[string]os.FileMode{ |
189 | "": os.ModeDir, |
190 | "/src": os.ModeDir, |
191 | "/src/zzz": os.ModeDir, |
192 | "/src/zzz/c.go": 0, |
193 | } |
194 | |
195 | testFastWalk(t, map[string]string{ |
196 | "a_skipfiles.go": "a", |
197 | "b_skipfiles.go": "b", |
198 | "zzz/c.go": "c", |
199 | }, |
200 | func(path string, typ os.FileMode) error { |
201 | if strings.HasSuffix(path, "_skipfiles.go") { |
202 | mu.Lock() |
203 | defer mu.Unlock() |
204 | want["/src/"+filepath.Base(path)] = 0 |
205 | return fastwalk.ErrSkipFiles |
206 | } |
207 | return nil |
208 | }, |
209 | want) |
210 | if len(want) != 5 { |
211 | t.Errorf("saw too many files: wanted 5, got %v (%v)", len(want), want) |
212 | } |
213 | } |
214 | |
215 | func TestFastWalk_TraverseSymlink(t *testing.T) { |
216 | testFastWalk(t, map[string]string{ |
217 | "foo/foo.go": "one", |
218 | "bar/bar.go": "two", |
219 | "skip/skip.go": "skip", |
220 | "symdir": "LINK:foo", |
221 | }, |
222 | func(path string, typ os.FileMode) error { |
223 | if typ == os.ModeSymlink { |
224 | return fastwalk.ErrTraverseLink |
225 | } |
226 | return nil |
227 | }, |
228 | map[string]os.FileMode{ |
229 | "": os.ModeDir, |
230 | "/src": os.ModeDir, |
231 | "/src/bar": os.ModeDir, |
232 | "/src/bar/bar.go": 0, |
233 | "/src/foo": os.ModeDir, |
234 | "/src/foo/foo.go": 0, |
235 | "/src/skip": os.ModeDir, |
236 | "/src/skip/skip.go": 0, |
237 | "/src/symdir": os.ModeSymlink, |
238 | "/src/symdir/foo.go": 0, |
239 | }) |
240 | } |
241 | |
242 | var benchDir = flag.String("benchdir", runtime.GOROOT(), "The directory to scan for BenchmarkFastWalk") |
243 | |
244 | func BenchmarkFastWalk(b *testing.B) { |
245 | b.ReportAllocs() |
246 | for i := 0; i < b.N; i++ { |
247 | err := fastwalk.Walk(*benchDir, func(path string, typ os.FileMode) error { return nil }) |
248 | if err != nil { |
249 | b.Fatal(err) |
250 | } |
251 | } |
252 | } |
253 |
Members