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