1
2
3
4
5 package filepath_test
6
7 import (
8 "fmt"
9 "internal/testenv"
10 "os"
11 . "path/filepath"
12 "runtime"
13 "slices"
14 "strings"
15 "testing"
16 )
17
18 type MatchTest struct {
19 pattern, s string
20 match bool
21 err error
22 }
23
24 var matchTests = []MatchTest{
25 {"abc", "abc", true, nil},
26 {"*", "abc", true, nil},
27 {"*c", "abc", true, nil},
28 {"a*", "a", true, nil},
29 {"a*", "abc", true, nil},
30 {"a*", "ab/c", false, nil},
31 {"a*/b", "abc/b", true, nil},
32 {"a*/b", "a/c/b", false, nil},
33 {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
34 {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
35 {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
36 {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
37 {"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
38 {"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
39 {"ab[c]", "abc", true, nil},
40 {"ab[b-d]", "abc", true, nil},
41 {"ab[e-g]", "abc", false, nil},
42 {"ab[^c]", "abc", false, nil},
43 {"ab[^b-d]", "abc", false, nil},
44 {"ab[^e-g]", "abc", true, nil},
45 {"a\\*b", "a*b", true, nil},
46 {"a\\*b", "ab", false, nil},
47 {"a?b", "a☺b", true, nil},
48 {"a[^a]b", "a☺b", true, nil},
49 {"a???b", "a☺b", false, nil},
50 {"a[^a][^a][^a]b", "a☺b", false, nil},
51 {"[a-ζ]*", "α", true, nil},
52 {"*[a-ζ]", "A", false, nil},
53 {"a?b", "a/b", false, nil},
54 {"a*b", "a/b", false, nil},
55 {"[\\]a]", "]", true, nil},
56 {"[\\-]", "-", true, nil},
57 {"[x\\-]", "x", true, nil},
58 {"[x\\-]", "-", true, nil},
59 {"[x\\-]", "z", false, nil},
60 {"[\\-x]", "x", true, nil},
61 {"[\\-x]", "-", true, nil},
62 {"[\\-x]", "a", false, nil},
63 {"[]a]", "]", false, ErrBadPattern},
64 {"[-]", "-", false, ErrBadPattern},
65 {"[x-]", "x", false, ErrBadPattern},
66 {"[x-]", "-", false, ErrBadPattern},
67 {"[x-]", "z", false, ErrBadPattern},
68 {"[-x]", "x", false, ErrBadPattern},
69 {"[-x]", "-", false, ErrBadPattern},
70 {"[-x]", "a", false, ErrBadPattern},
71 {"\\", "a", false, ErrBadPattern},
72 {"[a-b-c]", "a", false, ErrBadPattern},
73 {"[", "a", false, ErrBadPattern},
74 {"[^", "a", false, ErrBadPattern},
75 {"[^bc", "a", false, ErrBadPattern},
76 {"a[", "a", false, ErrBadPattern},
77 {"a[", "ab", false, ErrBadPattern},
78 {"a[", "x", false, ErrBadPattern},
79 {"a/b[", "x", false, ErrBadPattern},
80 {"*x", "xxx", true, nil},
81 }
82
83 func errp(e error) string {
84 if e == nil {
85 return "<nil>"
86 }
87 return e.Error()
88 }
89
90 func TestMatch(t *testing.T) {
91 for _, tt := range matchTests {
92 pattern := tt.pattern
93 s := tt.s
94 if runtime.GOOS == "windows" {
95 if strings.Contains(pattern, "\\") {
96
97 continue
98 }
99 pattern = Clean(pattern)
100 s = Clean(s)
101 }
102 ok, err := Match(pattern, s)
103 if ok != tt.match || err != tt.err {
104 t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
105 }
106 }
107 }
108
109 func BenchmarkMatch(b *testing.B) {
110 for _, tt := range matchTests {
111 name := fmt.Sprintf("%q %q", tt.pattern, tt.s)
112 b.Run(name, func(b *testing.B) {
113 b.ReportAllocs()
114 for range b.N {
115 bSink, errSink = Match(tt.pattern, tt.s)
116 }
117 })
118 }
119 }
120
121 var (
122 bSink bool
123 errSink error
124 )
125
126 var globTests = []struct {
127 pattern, result string
128 }{
129 {"match.go", "match.go"},
130 {"mat?h.go", "match.go"},
131 {"*", "match.go"},
132 {"../*/match.go", "../filepath/match.go"},
133 }
134
135 func TestGlob(t *testing.T) {
136 for _, tt := range globTests {
137 pattern := tt.pattern
138 result := tt.result
139 if runtime.GOOS == "windows" {
140 pattern = Clean(pattern)
141 result = Clean(result)
142 }
143 matches, err := Glob(pattern)
144 if err != nil {
145 t.Errorf("Glob error for %q: %s", pattern, err)
146 continue
147 }
148 if !slices.Contains(matches, result) {
149 t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
150 }
151 }
152 for _, pattern := range []string{"no_match", "../*/no_match"} {
153 matches, err := Glob(pattern)
154 if err != nil {
155 t.Errorf("Glob error for %q: %s", pattern, err)
156 continue
157 }
158 if len(matches) != 0 {
159 t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
160 }
161 }
162 }
163
164 func TestCVE202230632(t *testing.T) {
165
166
167
168 _, err := Glob("/*" + strings.Repeat("/", 10001))
169 if err != ErrBadPattern {
170 t.Fatalf("Glob returned err=%v, want ErrBadPattern", err)
171 }
172 }
173
174 func TestGlobError(t *testing.T) {
175 bad := []string{`[]`, `nonexist/[]`}
176 for _, pattern := range bad {
177 if _, err := Glob(pattern); err != ErrBadPattern {
178 t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err)
179 }
180 }
181 }
182
183 func TestGlobUNC(t *testing.T) {
184
185
186 Glob(`\\?\C:\*`)
187 }
188
189 var globSymlinkTests = []struct {
190 path, dest string
191 brokenLink bool
192 }{
193 {"test1", "link1", false},
194 {"test2", "link2", true},
195 }
196
197 func TestGlobSymlink(t *testing.T) {
198 testenv.MustHaveSymlink(t)
199
200 tmpDir := t.TempDir()
201 for _, tt := range globSymlinkTests {
202 path := Join(tmpDir, tt.path)
203 dest := Join(tmpDir, tt.dest)
204 f, err := os.Create(path)
205 if err != nil {
206 t.Fatal(err)
207 }
208 if err := f.Close(); err != nil {
209 t.Fatal(err)
210 }
211 err = os.Symlink(path, dest)
212 if err != nil {
213 t.Fatal(err)
214 }
215 if tt.brokenLink {
216
217 os.Remove(path)
218 }
219 matches, err := Glob(dest)
220 if err != nil {
221 t.Errorf("GlobSymlink error for %q: %s", dest, err)
222 }
223 if !slices.Contains(matches, dest) {
224 t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest)
225 }
226 }
227 }
228
229 type globTest struct {
230 pattern string
231 matches []string
232 }
233
234 func (test *globTest) buildWant(root string) []string {
235 want := make([]string, 0)
236 for _, m := range test.matches {
237 want = append(want, root+FromSlash(m))
238 }
239 slices.Sort(want)
240 return want
241 }
242
243 func (test *globTest) globAbs(root, rootPattern string) error {
244 p := FromSlash(rootPattern + `\` + test.pattern)
245 have, err := Glob(p)
246 if err != nil {
247 return err
248 }
249 slices.Sort(have)
250 want := test.buildWant(root + `\`)
251 if slices.Equal(want, have) {
252 return nil
253 }
254 return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
255 }
256
257 func (test *globTest) globRel(root string) error {
258 p := root + FromSlash(test.pattern)
259 have, err := Glob(p)
260 if err != nil {
261 return err
262 }
263 slices.Sort(have)
264 want := test.buildWant(root)
265 if slices.Equal(want, have) {
266 return nil
267 }
268
269 wantWithNoRoot := test.buildWant("")
270 if slices.Equal(wantWithNoRoot, have) {
271 return nil
272 }
273 return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
274 }
275
276 func TestWindowsGlob(t *testing.T) {
277 if runtime.GOOS != "windows" {
278 t.Skipf("skipping windows specific test")
279 }
280
281 tmpDir := tempDirCanonical(t)
282 if len(tmpDir) < 3 {
283 t.Fatalf("tmpDir path %q is too short", tmpDir)
284 }
285 if tmpDir[1] != ':' {
286 t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
287 }
288
289 dirs := []string{
290 "a",
291 "b",
292 "dir/d/bin",
293 }
294 files := []string{
295 "dir/d/bin/git.exe",
296 }
297 for _, dir := range dirs {
298 err := os.MkdirAll(Join(tmpDir, dir), 0777)
299 if err != nil {
300 t.Fatal(err)
301 }
302 }
303 for _, file := range files {
304 err := os.WriteFile(Join(tmpDir, file), nil, 0666)
305 if err != nil {
306 t.Fatal(err)
307 }
308 }
309
310 tests := []globTest{
311 {"a", []string{"a"}},
312 {"b", []string{"b"}},
313 {"c", []string{}},
314 {"*", []string{"a", "b", "dir"}},
315 {"d*", []string{"dir"}},
316 {"*i*", []string{"dir"}},
317 {"*r", []string{"dir"}},
318 {"?ir", []string{"dir"}},
319 {"?r", []string{}},
320 {"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}},
321 }
322
323
324 for _, test := range tests {
325 var p string
326 if err := test.globAbs(tmpDir, tmpDir); err != nil {
327 t.Error(err)
328 }
329
330 p = tmpDir
331 p = strings.Replace(p, `:\`, `:\*`, 1)
332 if err := test.globAbs(tmpDir, p); err != nil {
333 t.Error(err)
334 }
335
336 p = tmpDir
337 p = strings.Replace(p, `:\`, `:`, 1)
338 p = strings.Replace(p, `\`, `*\`, 1)
339 p = strings.Replace(p, `:`, `:\`, 1)
340 if err := test.globAbs(tmpDir, p); err != nil {
341 t.Error(err)
342 }
343 }
344
345
346 t.Chdir(tmpDir)
347 for _, test := range tests {
348 err := test.globRel("")
349 if err != nil {
350 t.Error(err)
351 }
352 err = test.globRel(`.\`)
353 if err != nil {
354 t.Error(err)
355 }
356 err = test.globRel(tmpDir[:2])
357 if err != nil {
358 t.Error(err)
359 }
360 }
361 }
362
363 func TestNonWindowsGlobEscape(t *testing.T) {
364 if runtime.GOOS == "windows" {
365 t.Skipf("skipping non-windows specific test")
366 }
367 pattern := `\match.go`
368 want := []string{"match.go"}
369 matches, err := Glob(pattern)
370 if err != nil {
371 t.Fatalf("Glob error for %q: %s", pattern, err)
372 }
373 if !slices.Equal(matches, want) {
374 t.Fatalf("Glob(%#q) = %v want %v", pattern, matches, want)
375 }
376 }
377
View as plain text