1
2
3
4
5 package filepath_test
6
7 import (
8 "errors"
9 "fmt"
10 "internal/testenv"
11 "io/fs"
12 "os"
13 "path/filepath"
14 "reflect"
15 "runtime"
16 "sort"
17 "strings"
18 "syscall"
19 "testing"
20 )
21
22 type PathTest struct {
23 path, result string
24 }
25
26 var cleantests = []PathTest{
27
28 {"abc", "abc"},
29 {"abc/def", "abc/def"},
30 {"a/b/c", "a/b/c"},
31 {".", "."},
32 {"..", ".."},
33 {"../..", "../.."},
34 {"../../abc", "../../abc"},
35 {"/abc", "/abc"},
36 {"/", "/"},
37
38
39 {"", "."},
40
41
42 {"abc/", "abc"},
43 {"abc/def/", "abc/def"},
44 {"a/b/c/", "a/b/c"},
45 {"./", "."},
46 {"../", ".."},
47 {"../../", "../.."},
48 {"/abc/", "/abc"},
49
50
51 {"abc//def//ghi", "abc/def/ghi"},
52 {"abc//", "abc"},
53
54
55 {"abc/./def", "abc/def"},
56 {"/./abc/def", "/abc/def"},
57 {"abc/.", "abc"},
58
59
60 {"abc/def/ghi/../jkl", "abc/def/jkl"},
61 {"abc/def/../ghi/../jkl", "abc/jkl"},
62 {"abc/def/..", "abc"},
63 {"abc/def/../..", "."},
64 {"/abc/def/../..", "/"},
65 {"abc/def/../../..", ".."},
66 {"/abc/def/../../..", "/"},
67 {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
68 {"/../abc", "/abc"},
69
70
71 {"abc/./../def", "def"},
72 {"abc//./../def", "def"},
73 {"abc/../../././../def", "../../def"},
74 }
75
76 var nonwincleantests = []PathTest{
77
78 {"//abc", "/abc"},
79 {"///abc", "/abc"},
80 {"//abc//", "/abc"},
81 }
82
83 var wincleantests = []PathTest{
84 {`c:`, `c:.`},
85 {`c:\`, `c:\`},
86 {`c:\abc`, `c:\abc`},
87 {`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
88 {`c:\abc\def\..\..`, `c:\`},
89 {`c:\..\abc`, `c:\abc`},
90 {`c:..\abc`, `c:..\abc`},
91 {`\`, `\`},
92 {`/`, `\`},
93 {`\\i\..\c$`, `\\i\..\c$`},
94 {`\\i\..\i\c$`, `\\i\..\i\c$`},
95 {`\\i\..\I\c$`, `\\i\..\I\c$`},
96 {`\\host\share\foo\..\bar`, `\\host\share\bar`},
97 {`//host/share/foo/../baz`, `\\host\share\baz`},
98 {`\\host\share\foo\..\..\..\..\bar`, `\\host\share\bar`},
99 {`\\.\C:\a\..\..\..\..\bar`, `\\.\C:\bar`},
100 {`\\.\C:\\\\a`, `\\.\C:\a`},
101 {`\\a\b\..\c`, `\\a\b\c`},
102 {`\\a\b`, `\\a\b`},
103 {`.\c:`, `.\c:`},
104 {`.\c:\foo`, `.\c:\foo`},
105 {`.\c:foo`, `.\c:foo`},
106 {`//abc`, `\\abc`},
107 {`///abc`, `\\\abc`},
108 {`//abc//`, `\\abc\\`},
109 }
110
111 func TestClean(t *testing.T) {
112 tests := cleantests
113 if runtime.GOOS == "windows" {
114 for i := range tests {
115 tests[i].result = filepath.FromSlash(tests[i].result)
116 }
117 tests = append(tests, wincleantests...)
118 } else {
119 tests = append(tests, nonwincleantests...)
120 }
121 for _, test := range tests {
122 if s := filepath.Clean(test.path); s != test.result {
123 t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
124 }
125 if s := filepath.Clean(test.result); s != test.result {
126 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
127 }
128 }
129
130 if testing.Short() {
131 t.Skip("skipping malloc count in short mode")
132 }
133 if runtime.GOMAXPROCS(0) > 1 {
134 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
135 return
136 }
137
138 for _, test := range tests {
139 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
140 if allocs > 0 {
141 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
142 }
143 }
144 }
145
146 type IsLocalTest struct {
147 path string
148 isLocal bool
149 }
150
151 var islocaltests = []IsLocalTest{
152 {"", false},
153 {".", true},
154 {"..", false},
155 {"../a", false},
156 {"/", false},
157 {"/a", false},
158 {"/a/../..", false},
159 {"a", true},
160 {"a/../a", true},
161 {"a/", true},
162 {"a/.", true},
163 {"a/./b/./c", true},
164 }
165
166 var winislocaltests = []IsLocalTest{
167 {"NUL", false},
168 {"nul", false},
169 {"nul.", false},
170 {"com1", false},
171 {"./nul", false},
172 {`\`, false},
173 {`\a`, false},
174 {`C:`, false},
175 {`C:\a`, false},
176 {`..\a`, false},
177 {`CONIN$`, false},
178 {`conin$`, false},
179 {`CONOUT$`, false},
180 {`conout$`, false},
181 {`dollar$`, true},
182 }
183
184 var plan9islocaltests = []IsLocalTest{
185 {"#a", false},
186 }
187
188 func TestIsLocal(t *testing.T) {
189 tests := islocaltests
190 if runtime.GOOS == "windows" {
191 tests = append(tests, winislocaltests...)
192 }
193 if runtime.GOOS == "plan9" {
194 tests = append(tests, plan9islocaltests...)
195 }
196 for _, test := range tests {
197 if got := filepath.IsLocal(test.path); got != test.isLocal {
198 t.Errorf("IsLocal(%q) = %v, want %v", test.path, got, test.isLocal)
199 }
200 }
201 }
202
203 const sep = filepath.Separator
204
205 var slashtests = []PathTest{
206 {"", ""},
207 {"/", string(sep)},
208 {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
209 {"a//b", string([]byte{'a', sep, sep, 'b'})},
210 }
211
212 func TestFromAndToSlash(t *testing.T) {
213 for _, test := range slashtests {
214 if s := filepath.FromSlash(test.path); s != test.result {
215 t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
216 }
217 if s := filepath.ToSlash(test.result); s != test.path {
218 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
219 }
220 }
221 }
222
223 type SplitListTest struct {
224 list string
225 result []string
226 }
227
228 const lsep = filepath.ListSeparator
229
230 var splitlisttests = []SplitListTest{
231 {"", []string{}},
232 {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
233 {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
234 }
235
236 var winsplitlisttests = []SplitListTest{
237
238 {`"a"`, []string{`a`}},
239
240
241 {`";"`, []string{`;`}},
242 {`"a;b"`, []string{`a;b`}},
243 {`";";`, []string{`;`, ``}},
244 {`;";"`, []string{``, `;`}},
245
246
247 {`a";"b`, []string{`a;b`}},
248 {`a; ""b`, []string{`a`, ` b`}},
249 {`"a;b`, []string{`a;b`}},
250 {`""a;b`, []string{`a`, `b`}},
251 {`"""a;b`, []string{`a;b`}},
252 {`""""a;b`, []string{`a`, `b`}},
253 {`a";b`, []string{`a;b`}},
254 {`a;b";c`, []string{`a`, `b;c`}},
255 {`"a";b";c`, []string{`a`, `b;c`}},
256 }
257
258 func TestSplitList(t *testing.T) {
259 tests := splitlisttests
260 if runtime.GOOS == "windows" {
261 tests = append(tests, winsplitlisttests...)
262 }
263 for _, test := range tests {
264 if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
265 t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
266 }
267 }
268 }
269
270 type SplitTest struct {
271 path, dir, file string
272 }
273
274 var unixsplittests = []SplitTest{
275 {"a/b", "a/", "b"},
276 {"a/b/", "a/b/", ""},
277 {"a/", "a/", ""},
278 {"a", "", "a"},
279 {"/", "/", ""},
280 }
281
282 var winsplittests = []SplitTest{
283 {`c:`, `c:`, ``},
284 {`c:/`, `c:/`, ``},
285 {`c:/foo`, `c:/`, `foo`},
286 {`c:/foo/bar`, `c:/foo/`, `bar`},
287 {`//host/share`, `//host/share`, ``},
288 {`//host/share/`, `//host/share/`, ``},
289 {`//host/share/foo`, `//host/share/`, `foo`},
290 {`\\host\share`, `\\host\share`, ``},
291 {`\\host\share\`, `\\host\share\`, ``},
292 {`\\host\share\foo`, `\\host\share\`, `foo`},
293 }
294
295 func TestSplit(t *testing.T) {
296 var splittests []SplitTest
297 splittests = unixsplittests
298 if runtime.GOOS == "windows" {
299 splittests = append(splittests, winsplittests...)
300 }
301 for _, test := range splittests {
302 if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
303 t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
304 }
305 }
306 }
307
308 type JoinTest struct {
309 elem []string
310 path string
311 }
312
313 var jointests = []JoinTest{
314
315 {[]string{}, ""},
316
317
318 {[]string{""}, ""},
319 {[]string{"/"}, "/"},
320 {[]string{"a"}, "a"},
321
322
323 {[]string{"a", "b"}, "a/b"},
324 {[]string{"a", ""}, "a"},
325 {[]string{"", "b"}, "b"},
326 {[]string{"/", "a"}, "/a"},
327 {[]string{"/", "a/b"}, "/a/b"},
328 {[]string{"/", ""}, "/"},
329 {[]string{"/a", "b"}, "/a/b"},
330 {[]string{"a", "/b"}, "a/b"},
331 {[]string{"/a", "/b"}, "/a/b"},
332 {[]string{"a/", "b"}, "a/b"},
333 {[]string{"a/", ""}, "a"},
334 {[]string{"", ""}, ""},
335
336
337 {[]string{"/", "a", "b"}, "/a/b"},
338 }
339
340 var nonwinjointests = []JoinTest{
341 {[]string{"//", "a"}, "/a"},
342 }
343
344 var winjointests = []JoinTest{
345 {[]string{`directory`, `file`}, `directory\file`},
346 {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
347 {[]string{`C:\Windows\`, ``}, `C:\Windows`},
348 {[]string{`C:\`, `Windows`}, `C:\Windows`},
349 {[]string{`C:`, `a`}, `C:a`},
350 {[]string{`C:`, `a\b`}, `C:a\b`},
351 {[]string{`C:`, `a`, `b`}, `C:a\b`},
352 {[]string{`C:`, ``, `b`}, `C:b`},
353 {[]string{`C:`, ``, ``, `b`}, `C:b`},
354 {[]string{`C:`, ``}, `C:.`},
355 {[]string{`C:`, ``, ``}, `C:.`},
356 {[]string{`C:`, `\a`}, `C:\a`},
357 {[]string{`C:`, ``, `\a`}, `C:\a`},
358 {[]string{`C:.`, `a`}, `C:a`},
359 {[]string{`C:a`, `b`}, `C:a\b`},
360 {[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
361 {[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
362 {[]string{`\\host\share\foo`}, `\\host\share\foo`},
363 {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
364 {[]string{`\`}, `\`},
365 {[]string{`\`, ``}, `\`},
366 {[]string{`\`, `a`}, `\a`},
367 {[]string{`\\`, `a`}, `\\a`},
368 {[]string{`\`, `a`, `b`}, `\a\b`},
369 {[]string{`\\`, `a`, `b`}, `\\a\b`},
370 {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
371 {[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
372 {[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
373 {[]string{`//`, `a`}, `\\a`},
374 }
375
376 func TestJoin(t *testing.T) {
377 if runtime.GOOS == "windows" {
378 jointests = append(jointests, winjointests...)
379 } else {
380 jointests = append(jointests, nonwinjointests...)
381 }
382 for _, test := range jointests {
383 expected := filepath.FromSlash(test.path)
384 if p := filepath.Join(test.elem...); p != expected {
385 t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
386 }
387 }
388 }
389
390 type ExtTest struct {
391 path, ext string
392 }
393
394 var exttests = []ExtTest{
395 {"path.go", ".go"},
396 {"path.pb.go", ".go"},
397 {"a.dir/b", ""},
398 {"a.dir/b.go", ".go"},
399 {"a.dir/", ""},
400 }
401
402 func TestExt(t *testing.T) {
403 for _, test := range exttests {
404 if x := filepath.Ext(test.path); x != test.ext {
405 t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
406 }
407 }
408 }
409
410 type Node struct {
411 name string
412 entries []*Node
413 mark int
414 }
415
416 var tree = &Node{
417 "testdata",
418 []*Node{
419 {"a", nil, 0},
420 {"b", []*Node{}, 0},
421 {"c", nil, 0},
422 {
423 "d",
424 []*Node{
425 {"x", nil, 0},
426 {"y", []*Node{}, 0},
427 {
428 "z",
429 []*Node{
430 {"u", nil, 0},
431 {"v", nil, 0},
432 },
433 0,
434 },
435 },
436 0,
437 },
438 },
439 0,
440 }
441
442 func walkTree(n *Node, path string, f func(path string, n *Node)) {
443 f(path, n)
444 for _, e := range n.entries {
445 walkTree(e, filepath.Join(path, e.name), f)
446 }
447 }
448
449 func makeTree(t *testing.T) {
450 walkTree(tree, tree.name, func(path string, n *Node) {
451 if n.entries == nil {
452 fd, err := os.Create(path)
453 if err != nil {
454 t.Errorf("makeTree: %v", err)
455 return
456 }
457 fd.Close()
458 } else {
459 os.Mkdir(path, 0770)
460 }
461 })
462 }
463
464 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
465
466 func checkMarks(t *testing.T, report bool) {
467 walkTree(tree, tree.name, func(path string, n *Node) {
468 if n.mark != 1 && report {
469 t.Errorf("node %s mark = %d; expected 1", path, n.mark)
470 }
471 n.mark = 0
472 })
473 }
474
475
476
477
478 func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
479 name := d.Name()
480 walkTree(tree, tree.name, func(path string, n *Node) {
481 if n.name == name {
482 n.mark++
483 }
484 })
485 if err != nil {
486 *errors = append(*errors, err)
487 if clear {
488 return nil
489 }
490 return err
491 }
492 return nil
493 }
494
495
496
497 func chdir(t *testing.T, dir string) {
498 olddir, err := os.Getwd()
499 if err != nil {
500 t.Fatalf("getwd %s: %v", dir, err)
501 }
502 if err := os.Chdir(dir); err != nil {
503 t.Fatalf("chdir %s: %v", dir, err)
504 }
505
506 t.Cleanup(func() {
507 if err := os.Chdir(olddir); err != nil {
508 t.Errorf("restore original working directory %s: %v", olddir, err)
509 os.Exit(1)
510 }
511 })
512 }
513
514 func chtmpdir(t *testing.T) (restore func()) {
515 oldwd, err := os.Getwd()
516 if err != nil {
517 t.Fatalf("chtmpdir: %v", err)
518 }
519 d, err := os.MkdirTemp("", "test")
520 if err != nil {
521 t.Fatalf("chtmpdir: %v", err)
522 }
523 if err := os.Chdir(d); err != nil {
524 t.Fatalf("chtmpdir: %v", err)
525 }
526 return func() {
527 if err := os.Chdir(oldwd); err != nil {
528 t.Fatalf("chtmpdir: %v", err)
529 }
530 os.RemoveAll(d)
531 }
532 }
533
534
535
536 func tempDirCanonical(t *testing.T) string {
537 dir := t.TempDir()
538
539 cdir, err := filepath.EvalSymlinks(dir)
540 if err != nil {
541 t.Errorf("tempDirCanonical: %v", err)
542 }
543
544 return cdir
545 }
546
547 func TestWalk(t *testing.T) {
548 walk := func(root string, fn fs.WalkDirFunc) error {
549 return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
550 return fn(path, &statDirEntry{info}, err)
551 })
552 }
553 testWalk(t, walk, 1)
554 }
555
556 type statDirEntry struct {
557 info fs.FileInfo
558 }
559
560 func (d *statDirEntry) Name() string { return d.info.Name() }
561 func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
562 func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
563 func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
564
565 func TestWalkDir(t *testing.T) {
566 testWalk(t, filepath.WalkDir, 2)
567 }
568
569 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
570 if runtime.GOOS == "ios" {
571 restore := chtmpdir(t)
572 defer restore()
573 }
574
575 tmpDir := t.TempDir()
576
577 origDir, err := os.Getwd()
578 if err != nil {
579 t.Fatal("finding working dir:", err)
580 }
581 if err = os.Chdir(tmpDir); err != nil {
582 t.Fatal("entering temp dir:", err)
583 }
584 defer os.Chdir(origDir)
585
586 makeTree(t)
587 errors := make([]error, 0, 10)
588 clear := true
589 markFn := func(path string, d fs.DirEntry, err error) error {
590 return mark(d, err, &errors, clear)
591 }
592
593 err = walk(tree.name, markFn)
594 if err != nil {
595 t.Fatalf("no error expected, found: %s", err)
596 }
597 if len(errors) != 0 {
598 t.Fatalf("unexpected errors: %s", errors)
599 }
600 checkMarks(t, true)
601 errors = errors[0:0]
602
603 t.Run("PermErr", func(t *testing.T) {
604
605
606
607 if runtime.GOOS == "windows" {
608 t.Skip("skipping on Windows")
609 }
610 if os.Getuid() == 0 {
611 t.Skip("skipping as root")
612 }
613 if testing.Short() {
614 t.Skip("skipping in short mode")
615 }
616
617
618 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
619 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
620
621
622
623 markTree(tree.entries[1])
624 markTree(tree.entries[3])
625
626 tree.entries[1].mark -= errVisit
627 tree.entries[3].mark -= errVisit
628 err := walk(tree.name, markFn)
629 if err != nil {
630 t.Fatalf("expected no error return from Walk, got %s", err)
631 }
632 if len(errors) != 2 {
633 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
634 }
635
636 checkMarks(t, true)
637 errors = errors[0:0]
638
639
640
641 markTree(tree.entries[1])
642 markTree(tree.entries[3])
643
644 tree.entries[1].mark -= errVisit
645 tree.entries[3].mark -= errVisit
646 clear = false
647 err = walk(tree.name, markFn)
648 if err == nil {
649 t.Fatalf("expected error return from Walk")
650 }
651 if len(errors) != 1 {
652 t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
653 }
654
655 checkMarks(t, false)
656 errors = errors[0:0]
657
658
659 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
660 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
661 })
662 }
663
664 func touch(t *testing.T, name string) {
665 f, err := os.Create(name)
666 if err != nil {
667 t.Fatal(err)
668 }
669 if err := f.Close(); err != nil {
670 t.Fatal(err)
671 }
672 }
673
674 func TestWalkSkipDirOnFile(t *testing.T) {
675 td := t.TempDir()
676
677 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
678 t.Fatal(err)
679 }
680 touch(t, filepath.Join(td, "dir/foo1"))
681 touch(t, filepath.Join(td, "dir/foo2"))
682
683 sawFoo2 := false
684 walker := func(path string) error {
685 if strings.HasSuffix(path, "foo2") {
686 sawFoo2 = true
687 }
688 if strings.HasSuffix(path, "foo1") {
689 return filepath.SkipDir
690 }
691 return nil
692 }
693 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
694 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
695
696 check := func(t *testing.T, walk func(root string) error, root string) {
697 t.Helper()
698 sawFoo2 = false
699 err := walk(root)
700 if err != nil {
701 t.Fatal(err)
702 }
703 if sawFoo2 {
704 t.Errorf("SkipDir on file foo1 did not block processing of foo2")
705 }
706 }
707
708 t.Run("Walk", func(t *testing.T) {
709 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
710 check(t, Walk, td)
711 check(t, Walk, filepath.Join(td, "dir"))
712 })
713 t.Run("WalkDir", func(t *testing.T) {
714 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
715 check(t, WalkDir, td)
716 check(t, WalkDir, filepath.Join(td, "dir"))
717 })
718 }
719
720 func TestWalkSkipAllOnFile(t *testing.T) {
721 td := t.TempDir()
722
723 if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
724 t.Fatal(err)
725 }
726 if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
727 t.Fatal(err)
728 }
729
730 touch(t, filepath.Join(td, "dir", "foo1"))
731 touch(t, filepath.Join(td, "dir", "foo2"))
732 touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
733 touch(t, filepath.Join(td, "dir", "foo4"))
734 touch(t, filepath.Join(td, "dir2", "bar"))
735 touch(t, filepath.Join(td, "last"))
736
737 remainingWereSkipped := true
738 walker := func(path string) error {
739 if strings.HasSuffix(path, "foo2") {
740 return filepath.SkipAll
741 }
742
743 if strings.HasSuffix(path, "foo3") ||
744 strings.HasSuffix(path, "foo4") ||
745 strings.HasSuffix(path, "bar") ||
746 strings.HasSuffix(path, "last") {
747 remainingWereSkipped = false
748 }
749 return nil
750 }
751
752 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
753 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
754
755 check := func(t *testing.T, walk func(root string) error, root string) {
756 t.Helper()
757 remainingWereSkipped = true
758 if err := walk(root); err != nil {
759 t.Fatal(err)
760 }
761 if !remainingWereSkipped {
762 t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
763 }
764 }
765
766 t.Run("Walk", func(t *testing.T) {
767 Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
768 check(t, Walk, td)
769 check(t, Walk, filepath.Join(td, "dir"))
770 })
771 t.Run("WalkDir", func(t *testing.T) {
772 WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
773 check(t, WalkDir, td)
774 check(t, WalkDir, filepath.Join(td, "dir"))
775 })
776 }
777
778 func TestWalkFileError(t *testing.T) {
779 td := t.TempDir()
780
781 touch(t, filepath.Join(td, "foo"))
782 touch(t, filepath.Join(td, "bar"))
783 dir := filepath.Join(td, "dir")
784 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
785 t.Fatal(err)
786 }
787 touch(t, filepath.Join(dir, "baz"))
788 touch(t, filepath.Join(dir, "stat-error"))
789 defer func() {
790 *filepath.LstatP = os.Lstat
791 }()
792 statErr := errors.New("some stat error")
793 *filepath.LstatP = func(path string) (fs.FileInfo, error) {
794 if strings.HasSuffix(path, "stat-error") {
795 return nil, statErr
796 }
797 return os.Lstat(path)
798 }
799 got := map[string]error{}
800 err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
801 rel, _ := filepath.Rel(td, path)
802 got[filepath.ToSlash(rel)] = err
803 return nil
804 })
805 if err != nil {
806 t.Errorf("Walk error: %v", err)
807 }
808 want := map[string]error{
809 ".": nil,
810 "foo": nil,
811 "bar": nil,
812 "dir": nil,
813 "dir/baz": nil,
814 "dir/stat-error": statErr,
815 }
816 if !reflect.DeepEqual(got, want) {
817 t.Errorf("Walked %#v; want %#v", got, want)
818 }
819 }
820
821 var basetests = []PathTest{
822 {"", "."},
823 {".", "."},
824 {"/.", "."},
825 {"/", "/"},
826 {"////", "/"},
827 {"x/", "x"},
828 {"abc", "abc"},
829 {"abc/def", "def"},
830 {"a/b/.x", ".x"},
831 {"a/b/c.", "c."},
832 {"a/b/c.x", "c.x"},
833 }
834
835 var winbasetests = []PathTest{
836 {`c:\`, `\`},
837 {`c:.`, `.`},
838 {`c:\a\b`, `b`},
839 {`c:a\b`, `b`},
840 {`c:a\b\c`, `c`},
841 {`\\host\share\`, `\`},
842 {`\\host\share\a`, `a`},
843 {`\\host\share\a\b`, `b`},
844 }
845
846 func TestBase(t *testing.T) {
847 tests := basetests
848 if runtime.GOOS == "windows" {
849
850 for i := range tests {
851 tests[i].result = filepath.Clean(tests[i].result)
852 }
853
854 tests = append(tests, winbasetests...)
855 }
856 for _, test := range tests {
857 if s := filepath.Base(test.path); s != test.result {
858 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
859 }
860 }
861 }
862
863 var dirtests = []PathTest{
864 {"", "."},
865 {".", "."},
866 {"/.", "/"},
867 {"/", "/"},
868 {"/foo", "/"},
869 {"x/", "x"},
870 {"abc", "."},
871 {"abc/def", "abc"},
872 {"a/b/.x", "a/b"},
873 {"a/b/c.", "a/b"},
874 {"a/b/c.x", "a/b"},
875 }
876
877 var nonwindirtests = []PathTest{
878 {"////", "/"},
879 }
880
881 var windirtests = []PathTest{
882 {`c:\`, `c:\`},
883 {`c:.`, `c:.`},
884 {`c:\a\b`, `c:\a`},
885 {`c:a\b`, `c:a`},
886 {`c:a\b\c`, `c:a\b`},
887 {`\\host\share`, `\\host\share`},
888 {`\\host\share\`, `\\host\share\`},
889 {`\\host\share\a`, `\\host\share\`},
890 {`\\host\share\a\b`, `\\host\share\a`},
891 {`\\\\`, `\\\\`},
892 }
893
894 func TestDir(t *testing.T) {
895 tests := dirtests
896 if runtime.GOOS == "windows" {
897
898 for i := range tests {
899 tests[i].result = filepath.Clean(tests[i].result)
900 }
901
902 tests = append(tests, windirtests...)
903 } else {
904 tests = append(tests, nonwindirtests...)
905 }
906 for _, test := range tests {
907 if s := filepath.Dir(test.path); s != test.result {
908 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
909 }
910 }
911 }
912
913 type IsAbsTest struct {
914 path string
915 isAbs bool
916 }
917
918 var isabstests = []IsAbsTest{
919 {"", false},
920 {"/", true},
921 {"/usr/bin/gcc", true},
922 {"..", false},
923 {"/a/../bb", true},
924 {".", false},
925 {"./", false},
926 {"lala", false},
927 }
928
929 var winisabstests = []IsAbsTest{
930 {`C:\`, true},
931 {`c\`, false},
932 {`c::`, false},
933 {`c:`, false},
934 {`/`, false},
935 {`\`, false},
936 {`\Windows`, false},
937 {`c:a\b`, false},
938 {`c:\a\b`, true},
939 {`c:/a/b`, true},
940 {`\\host\share`, true},
941 {`\\host\share\`, true},
942 {`\\host\share\foo`, true},
943 {`//host/share/foo/bar`, true},
944 }
945
946 func TestIsAbs(t *testing.T) {
947 var tests []IsAbsTest
948 if runtime.GOOS == "windows" {
949 tests = append(tests, winisabstests...)
950
951 for _, test := range isabstests {
952 tests = append(tests, IsAbsTest{test.path, false})
953 }
954
955 for _, test := range isabstests {
956 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
957 }
958 } else {
959 tests = isabstests
960 }
961
962 for _, test := range tests {
963 if r := filepath.IsAbs(test.path); r != test.isAbs {
964 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
965 }
966 }
967 }
968
969 type EvalSymlinksTest struct {
970
971 path, dest string
972 }
973
974 var EvalSymlinksTestDirs = []EvalSymlinksTest{
975 {"test", ""},
976 {"test/dir", ""},
977 {"test/dir/link3", "../../"},
978 {"test/link1", "../test"},
979 {"test/link2", "dir"},
980 {"test/linkabs", "/"},
981 {"test/link4", "../test2"},
982 {"test2", "test/dir"},
983
984 {"src", ""},
985 {"src/pool", ""},
986 {"src/pool/test", ""},
987 {"src/versions", ""},
988 {"src/versions/current", "../../version"},
989 {"src/versions/v1", ""},
990 {"src/versions/v1/modules", ""},
991 {"src/versions/v1/modules/test", "../../../pool/test"},
992 {"version", "src/versions/v1"},
993 }
994
995 var EvalSymlinksTests = []EvalSymlinksTest{
996 {"test", "test"},
997 {"test/dir", "test/dir"},
998 {"test/dir/../..", "."},
999 {"test/link1", "test"},
1000 {"test/link2", "test/dir"},
1001 {"test/link1/dir", "test/dir"},
1002 {"test/link2/..", "test"},
1003 {"test/dir/link3", "."},
1004 {"test/link2/link3/test", "test"},
1005 {"test/linkabs", "/"},
1006 {"test/link4/..", "test"},
1007 {"src/versions/current/modules/test", "src/pool/test"},
1008 }
1009
1010
1011
1012 func simpleJoin(dir, path string) string {
1013 return dir + string(filepath.Separator) + path
1014 }
1015
1016 func testEvalSymlinks(t *testing.T, path, want string) {
1017 have, err := filepath.EvalSymlinks(path)
1018 if err != nil {
1019 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1020 return
1021 }
1022 if filepath.Clean(have) != filepath.Clean(want) {
1023 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1024 }
1025 }
1026
1027 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1028 cwd, err := os.Getwd()
1029 if err != nil {
1030 t.Fatal(err)
1031 }
1032 defer func() {
1033 err := os.Chdir(cwd)
1034 if err != nil {
1035 t.Fatal(err)
1036 }
1037 }()
1038
1039 err = os.Chdir(wd)
1040 if err != nil {
1041 t.Fatal(err)
1042 }
1043
1044 have, err := filepath.EvalSymlinks(path)
1045 if err != nil {
1046 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1047 return
1048 }
1049 if filepath.Clean(have) != filepath.Clean(want) {
1050 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1051 }
1052 }
1053
1054 func TestEvalSymlinks(t *testing.T) {
1055 testenv.MustHaveSymlink(t)
1056
1057 tmpDir := t.TempDir()
1058
1059
1060
1061 var err error
1062 tmpDir, err = filepath.EvalSymlinks(tmpDir)
1063 if err != nil {
1064 t.Fatal("eval symlink for tmp dir:", err)
1065 }
1066
1067
1068 for _, d := range EvalSymlinksTestDirs {
1069 var err error
1070 path := simpleJoin(tmpDir, d.path)
1071 if d.dest == "" {
1072 err = os.Mkdir(path, 0755)
1073 } else {
1074 err = os.Symlink(d.dest, path)
1075 }
1076 if err != nil {
1077 t.Fatal(err)
1078 }
1079 }
1080
1081
1082 for _, test := range EvalSymlinksTests {
1083 path := simpleJoin(tmpDir, test.path)
1084
1085 dest := simpleJoin(tmpDir, test.dest)
1086 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1087 dest = test.dest
1088 }
1089 testEvalSymlinks(t, path, dest)
1090
1091
1092 testEvalSymlinksAfterChdir(t, path, ".", ".")
1093
1094
1095 if runtime.GOOS == "windows" {
1096 volDot := filepath.VolumeName(tmpDir) + "."
1097 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1098 }
1099
1100
1101 dotdotPath := simpleJoin("..", test.dest)
1102 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1103 dotdotPath = test.dest
1104 }
1105 testEvalSymlinksAfterChdir(t,
1106 simpleJoin(tmpDir, "test"),
1107 simpleJoin("..", test.path),
1108 dotdotPath)
1109
1110
1111 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1112 }
1113 }
1114
1115 func TestEvalSymlinksIsNotExist(t *testing.T) {
1116 testenv.MustHaveSymlink(t)
1117
1118 defer chtmpdir(t)()
1119
1120 _, err := filepath.EvalSymlinks("notexist")
1121 if !os.IsNotExist(err) {
1122 t.Errorf("expected the file is not found, got %v\n", err)
1123 }
1124
1125 err = os.Symlink("notexist", "link")
1126 if err != nil {
1127 t.Fatal(err)
1128 }
1129 defer os.Remove("link")
1130
1131 _, err = filepath.EvalSymlinks("link")
1132 if !os.IsNotExist(err) {
1133 t.Errorf("expected the file is not found, got %v\n", err)
1134 }
1135 }
1136
1137 func TestIssue13582(t *testing.T) {
1138 testenv.MustHaveSymlink(t)
1139
1140 tmpDir := t.TempDir()
1141
1142 dir := filepath.Join(tmpDir, "dir")
1143 err := os.Mkdir(dir, 0755)
1144 if err != nil {
1145 t.Fatal(err)
1146 }
1147 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1148 err = os.Symlink(dir, linkToDir)
1149 if err != nil {
1150 t.Fatal(err)
1151 }
1152 file := filepath.Join(linkToDir, "file")
1153 err = os.WriteFile(file, nil, 0644)
1154 if err != nil {
1155 t.Fatal(err)
1156 }
1157 link1 := filepath.Join(linkToDir, "link1")
1158 err = os.Symlink(file, link1)
1159 if err != nil {
1160 t.Fatal(err)
1161 }
1162 link2 := filepath.Join(linkToDir, "link2")
1163 err = os.Symlink(link1, link2)
1164 if err != nil {
1165 t.Fatal(err)
1166 }
1167
1168
1169 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1170 if err != nil {
1171 t.Fatal(err)
1172 }
1173 realDir := filepath.Join(realTmpDir, "dir")
1174 realFile := filepath.Join(realDir, "file")
1175
1176 tests := []struct {
1177 path, want string
1178 }{
1179 {dir, realDir},
1180 {linkToDir, realDir},
1181 {file, realFile},
1182 {link1, realFile},
1183 {link2, realFile},
1184 }
1185 for i, test := range tests {
1186 have, err := filepath.EvalSymlinks(test.path)
1187 if err != nil {
1188 t.Fatal(err)
1189 }
1190 if have != test.want {
1191 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1192 }
1193 }
1194 }
1195
1196
1197
1198 var absTestDirs = []string{
1199 "a",
1200 "a/b",
1201 "a/b/c",
1202 }
1203
1204
1205
1206
1207 var absTests = []string{
1208 ".",
1209 "b",
1210 "b/",
1211 "../a",
1212 "../a/b",
1213 "../a/b/./c/../../.././a",
1214 "../a/b/./c/../../.././a/",
1215 "$",
1216 "$/.",
1217 "$/a/../a/b",
1218 "$/a/b/c/../../.././a",
1219 "$/a/b/c/../../.././a/",
1220 }
1221
1222 func TestAbs(t *testing.T) {
1223 root := t.TempDir()
1224 wd, err := os.Getwd()
1225 if err != nil {
1226 t.Fatal("getwd failed: ", err)
1227 }
1228 err = os.Chdir(root)
1229 if err != nil {
1230 t.Fatal("chdir failed: ", err)
1231 }
1232 defer os.Chdir(wd)
1233
1234 for _, dir := range absTestDirs {
1235 err = os.Mkdir(dir, 0777)
1236 if err != nil {
1237 t.Fatal("Mkdir failed: ", err)
1238 }
1239 }
1240
1241 if runtime.GOOS == "windows" {
1242 vol := filepath.VolumeName(root)
1243 var extra []string
1244 for _, path := range absTests {
1245 if strings.Contains(path, "$") {
1246 continue
1247 }
1248 path = vol + path
1249 extra = append(extra, path)
1250 }
1251 absTests = append(absTests, extra...)
1252 }
1253
1254 err = os.Chdir(absTestDirs[0])
1255 if err != nil {
1256 t.Fatal("chdir failed: ", err)
1257 }
1258
1259 for _, path := range absTests {
1260 path = strings.ReplaceAll(path, "$", root)
1261 info, err := os.Stat(path)
1262 if err != nil {
1263 t.Errorf("%s: %s", path, err)
1264 continue
1265 }
1266
1267 abspath, err := filepath.Abs(path)
1268 if err != nil {
1269 t.Errorf("Abs(%q) error: %v", path, err)
1270 continue
1271 }
1272 absinfo, err := os.Stat(abspath)
1273 if err != nil || !os.SameFile(absinfo, info) {
1274 t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1275 }
1276 if !filepath.IsAbs(abspath) {
1277 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1278 }
1279 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1280 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1281 }
1282 }
1283 }
1284
1285
1286
1287
1288 func TestAbsEmptyString(t *testing.T) {
1289 root := t.TempDir()
1290
1291 wd, err := os.Getwd()
1292 if err != nil {
1293 t.Fatal("getwd failed: ", err)
1294 }
1295 err = os.Chdir(root)
1296 if err != nil {
1297 t.Fatal("chdir failed: ", err)
1298 }
1299 defer os.Chdir(wd)
1300
1301 info, err := os.Stat(root)
1302 if err != nil {
1303 t.Fatalf("%s: %s", root, err)
1304 }
1305
1306 abspath, err := filepath.Abs("")
1307 if err != nil {
1308 t.Fatalf(`Abs("") error: %v`, err)
1309 }
1310 absinfo, err := os.Stat(abspath)
1311 if err != nil || !os.SameFile(absinfo, info) {
1312 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1313 }
1314 if !filepath.IsAbs(abspath) {
1315 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1316 }
1317 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1318 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1319 }
1320 }
1321
1322 type RelTests struct {
1323 root, path, want string
1324 }
1325
1326 var reltests = []RelTests{
1327 {"a/b", "a/b", "."},
1328 {"a/b/.", "a/b", "."},
1329 {"a/b", "a/b/.", "."},
1330 {"./a/b", "a/b", "."},
1331 {"a/b", "./a/b", "."},
1332 {"ab/cd", "ab/cde", "../cde"},
1333 {"ab/cd", "ab/c", "../c"},
1334 {"a/b", "a/b/c/d", "c/d"},
1335 {"a/b", "a/b/../c", "../c"},
1336 {"a/b/../c", "a/b", "../b"},
1337 {"a/b/c", "a/c/d", "../../c/d"},
1338 {"a/b", "c/d", "../../c/d"},
1339 {"a/b/c/d", "a/b", "../.."},
1340 {"a/b/c/d", "a/b/", "../.."},
1341 {"a/b/c/d/", "a/b", "../.."},
1342 {"a/b/c/d/", "a/b/", "../.."},
1343 {"../../a/b", "../../a/b/c/d", "c/d"},
1344 {"/a/b", "/a/b", "."},
1345 {"/a/b/.", "/a/b", "."},
1346 {"/a/b", "/a/b/.", "."},
1347 {"/ab/cd", "/ab/cde", "../cde"},
1348 {"/ab/cd", "/ab/c", "../c"},
1349 {"/a/b", "/a/b/c/d", "c/d"},
1350 {"/a/b", "/a/b/../c", "../c"},
1351 {"/a/b/../c", "/a/b", "../b"},
1352 {"/a/b/c", "/a/c/d", "../../c/d"},
1353 {"/a/b", "/c/d", "../../c/d"},
1354 {"/a/b/c/d", "/a/b", "../.."},
1355 {"/a/b/c/d", "/a/b/", "../.."},
1356 {"/a/b/c/d/", "/a/b", "../.."},
1357 {"/a/b/c/d/", "/a/b/", "../.."},
1358 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1359 {".", "a/b", "a/b"},
1360 {".", "..", ".."},
1361
1362
1363 {"..", ".", "err"},
1364 {"..", "a", "err"},
1365 {"../..", "..", "err"},
1366 {"a", "/a", "err"},
1367 {"/a", "a", "err"},
1368 }
1369
1370 var winreltests = []RelTests{
1371 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1372 {`C:\`, `D:\`, `err`},
1373 {`C:`, `D:`, `err`},
1374 {`C:\Projects`, `c:\projects\src`, `src`},
1375 {`C:\Projects`, `c:\projects`, `.`},
1376 {`C:\Projects\a\..`, `c:\projects`, `.`},
1377 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1378 }
1379
1380 func TestRel(t *testing.T) {
1381 tests := append([]RelTests{}, reltests...)
1382 if runtime.GOOS == "windows" {
1383 for i := range tests {
1384 tests[i].want = filepath.FromSlash(tests[i].want)
1385 }
1386 tests = append(tests, winreltests...)
1387 }
1388 for _, test := range tests {
1389 got, err := filepath.Rel(test.root, test.path)
1390 if test.want == "err" {
1391 if err == nil {
1392 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1393 }
1394 continue
1395 }
1396 if err != nil {
1397 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1398 }
1399 if got != test.want {
1400 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1401 }
1402 }
1403 }
1404
1405 type VolumeNameTest struct {
1406 path string
1407 vol string
1408 }
1409
1410 var volumenametests = []VolumeNameTest{
1411 {`c:/foo/bar`, `c:`},
1412 {`c:`, `c:`},
1413 {`2:`, ``},
1414 {``, ``},
1415 {`\\\host`, `\\\host`},
1416 {`\\\host\`, `\\\host`},
1417 {`\\\host\share`, `\\\host`},
1418 {`\\\host\\share`, `\\\host`},
1419 {`\\host`, `\\host`},
1420 {`//host`, `\\host`},
1421 {`\\host\`, `\\host\`},
1422 {`//host/`, `\\host\`},
1423 {`\\host\share`, `\\host\share`},
1424 {`//host/share`, `\\host\share`},
1425 {`\\host\share\`, `\\host\share`},
1426 {`//host/share/`, `\\host\share`},
1427 {`\\host\share\foo`, `\\host\share`},
1428 {`//host/share/foo`, `\\host\share`},
1429 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1430 {`//host/share//foo///bar////baz`, `\\host\share`},
1431 {`\\host\share\foo\..\bar`, `\\host\share`},
1432 {`//host/share/foo/../bar`, `\\host\share`},
1433 {`//./NUL`, `\\.\NUL`},
1434 {`//?/NUL`, `\\?\NUL`},
1435 {`//./C:`, `\\.\C:`},
1436 {`//./C:/a/b/c`, `\\.\C:`},
1437 {`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
1438 {`//./UNC/host`, `\\.\UNC\host`},
1439 }
1440
1441 func TestVolumeName(t *testing.T) {
1442 if runtime.GOOS != "windows" {
1443 return
1444 }
1445 for _, v := range volumenametests {
1446 if vol := filepath.VolumeName(v.path); vol != v.vol {
1447 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1448 }
1449 }
1450 }
1451
1452 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1453 if runtime.GOOS != "windows" {
1454 return
1455 }
1456 wd, _ := os.Getwd()
1457 if len(wd) < 3 {
1458 t.Errorf("Current directory path %q is too short", wd)
1459 }
1460 lp := strings.ToLower(wd)
1461 up := strings.ToUpper(wd)
1462 flp, err := filepath.EvalSymlinks(lp)
1463 if err != nil {
1464 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1465 }
1466 fup, err := filepath.EvalSymlinks(up)
1467 if err != nil {
1468 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1469 }
1470 if flp != fup {
1471 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1472 }
1473 }
1474
1475 func TestBug3486(t *testing.T) {
1476 if runtime.GOOS == "ios" {
1477 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1478 }
1479 root, err := filepath.EvalSymlinks(testenv.GOROOT(t) + "/test")
1480 if err != nil {
1481 t.Fatal(err)
1482 }
1483 bugs := filepath.Join(root, "fixedbugs")
1484 ken := filepath.Join(root, "ken")
1485 seenBugs := false
1486 seenKen := false
1487 err = filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1488 if err != nil {
1489 t.Fatal(err)
1490 }
1491
1492 switch pth {
1493 case bugs:
1494 seenBugs = true
1495 return filepath.SkipDir
1496 case ken:
1497 if !seenBugs {
1498 t.Fatal("filepath.Walk out of order - ken before fixedbugs")
1499 }
1500 seenKen = true
1501 }
1502 return nil
1503 })
1504 if err != nil {
1505 t.Fatal(err)
1506 }
1507 if !seenKen {
1508 t.Fatalf("%q not seen", ken)
1509 }
1510 }
1511
1512 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1513 tmpdir := t.TempDir()
1514
1515 wd, err := os.Getwd()
1516 if err != nil {
1517 t.Fatal(err)
1518 }
1519 defer os.Chdir(wd)
1520
1521 err = os.Chdir(tmpdir)
1522 if err != nil {
1523 t.Fatal(err)
1524 }
1525
1526 err = mklink(tmpdir, "link")
1527 if err != nil {
1528 t.Fatal(err)
1529 }
1530
1531 var visited []string
1532 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1533 if err != nil {
1534 t.Fatal(err)
1535 }
1536 rel, err := filepath.Rel(tmpdir, path)
1537 if err != nil {
1538 t.Fatal(err)
1539 }
1540 visited = append(visited, rel)
1541 return nil
1542 })
1543 if err != nil {
1544 t.Fatal(err)
1545 }
1546 sort.Strings(visited)
1547 want := []string{".", "link"}
1548 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1549 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1550 }
1551 }
1552
1553 func TestWalkSymlink(t *testing.T) {
1554 testenv.MustHaveSymlink(t)
1555 testWalkSymlink(t, os.Symlink)
1556 }
1557
1558 func TestIssue29372(t *testing.T) {
1559 tmpDir := t.TempDir()
1560
1561 path := filepath.Join(tmpDir, "file.txt")
1562 err := os.WriteFile(path, nil, 0644)
1563 if err != nil {
1564 t.Fatal(err)
1565 }
1566
1567 pathSeparator := string(filepath.Separator)
1568 tests := []string{
1569 path + strings.Repeat(pathSeparator, 1),
1570 path + strings.Repeat(pathSeparator, 2),
1571 path + strings.Repeat(pathSeparator, 1) + ".",
1572 path + strings.Repeat(pathSeparator, 2) + ".",
1573 path + strings.Repeat(pathSeparator, 1) + "..",
1574 path + strings.Repeat(pathSeparator, 2) + "..",
1575 }
1576
1577 for i, test := range tests {
1578 _, err = filepath.EvalSymlinks(test)
1579 if err != syscall.ENOTDIR {
1580 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1581 }
1582 }
1583 }
1584
1585
1586 func TestEvalSymlinksAboveRoot(t *testing.T) {
1587 testenv.MustHaveSymlink(t)
1588
1589 t.Parallel()
1590
1591 tmpDir := t.TempDir()
1592
1593 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1594 if err != nil {
1595 t.Fatal(err)
1596 }
1597
1598 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1599 t.Fatal(err)
1600 }
1601 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1602 t.Fatal(err)
1603 }
1604 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1605 t.Fatal(err)
1606 }
1607
1608
1609 vol := filepath.VolumeName(evalTmpDir)
1610 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1611 var dd []string
1612 for i := 0; i < c+2; i++ {
1613 dd = append(dd, "..")
1614 }
1615
1616 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1617
1618
1619 for _, i := range []int{c, c + 1, c + 2} {
1620 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1621 resolved, err := filepath.EvalSymlinks(check)
1622 switch {
1623 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1624
1625 testenv.SkipFlaky(t, 37910)
1626 case err != nil:
1627 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1628 case !strings.HasSuffix(resolved, wantSuffix):
1629 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1630 default:
1631 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1632 }
1633 }
1634 }
1635
1636
1637 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1638 testenv.MustHaveSymlink(t)
1639
1640 tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1641 if err != nil {
1642 t.Fatal(err)
1643 }
1644 defer os.RemoveAll(tmpDir)
1645 chdir(t, tmpDir)
1646
1647 subdir := filepath.Join("a", "b")
1648 if err := os.MkdirAll(subdir, 0777); err != nil {
1649 t.Fatal(err)
1650 }
1651 if err := os.Symlink(subdir, "c"); err != nil {
1652 t.Fatal(err)
1653 }
1654 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1655 t.Fatal(err)
1656 }
1657
1658 subdir = filepath.Join("d", "e", "f")
1659 if err := os.MkdirAll(subdir, 0777); err != nil {
1660 t.Fatal(err)
1661 }
1662 if err := os.Chdir(subdir); err != nil {
1663 t.Fatal(err)
1664 }
1665
1666 check := filepath.Join("..", "..", "..", "c", "file")
1667 wantSuffix := filepath.Join("a", "b", "file")
1668 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1669 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1670 } else if !strings.HasSuffix(resolved, wantSuffix) {
1671 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1672 } else {
1673 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1674 }
1675 }
1676
1677 func TestIssue51617(t *testing.T) {
1678 dir := t.TempDir()
1679 for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
1680 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
1681 t.Fatal(err)
1682 }
1683 }
1684 bad := filepath.Join(dir, "a", "bad")
1685 if err := os.Chmod(bad, 0); err != nil {
1686 t.Fatal(err)
1687 }
1688 defer os.Chmod(bad, 0700)
1689 var saw []string
1690 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1691 if err != nil {
1692 return filepath.SkipDir
1693 }
1694 if d.IsDir() {
1695 rel, err := filepath.Rel(dir, path)
1696 if err != nil {
1697 t.Fatal(err)
1698 }
1699 saw = append(saw, rel)
1700 }
1701 return nil
1702 })
1703 if err != nil {
1704 t.Fatal(err)
1705 }
1706 want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
1707 if !reflect.DeepEqual(saw, want) {
1708 t.Errorf("got directories %v, want %v", saw, want)
1709 }
1710 }
1711
View as plain text