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