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); !slices.Equal(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 tempDirCanonical(t *testing.T) string {
603 dir := t.TempDir()
604
605 cdir, err := filepath.EvalSymlinks(dir)
606 if err != nil {
607 t.Errorf("tempDirCanonical: %v", err)
608 }
609
610 return cdir
611 }
612
613 func TestWalk(t *testing.T) {
614 walk := func(root string, fn fs.WalkDirFunc) error {
615 return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
616 return fn(path, fs.FileInfoToDirEntry(info), err)
617 })
618 }
619 testWalk(t, walk, 1)
620 }
621
622 func TestWalkDir(t *testing.T) {
623 testWalk(t, filepath.WalkDir, 2)
624 }
625
626 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
627 t.Chdir(t.TempDir())
628
629 makeTree(t)
630 errors := make([]error, 0, 10)
631 clear := true
632 markFn := func(path string, d fs.DirEntry, err error) error {
633 return mark(d, err, &errors, clear)
634 }
635
636 err := walk(tree.name, markFn)
637 if err != nil {
638 t.Fatalf("no error expected, found: %s", err)
639 }
640 if len(errors) != 0 {
641 t.Fatalf("unexpected errors: %s", errors)
642 }
643 checkMarks(t, true)
644 errors = errors[0:0]
645
646 t.Run("PermErr", func(t *testing.T) {
647
648
649
650
651 if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
652 t.Skip("skipping on " + runtime.GOOS)
653 }
654 if os.Getuid() == 0 {
655 t.Skip("skipping as root")
656 }
657 if testing.Short() {
658 t.Skip("skipping in short mode")
659 }
660
661
662 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
663 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
664
665
666
667 markTree(tree.entries[1])
668 markTree(tree.entries[3])
669
670 tree.entries[1].mark -= errVisit
671 tree.entries[3].mark -= errVisit
672 err := walk(tree.name, markFn)
673 if err != nil {
674 t.Fatalf("expected no error return from Walk, got %s", err)
675 }
676 if len(errors) != 2 {
677 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
678 }
679
680 checkMarks(t, true)
681 errors = errors[0:0]
682
683
684
685 markTree(tree.entries[1])
686 markTree(tree.entries[3])
687
688 tree.entries[1].mark -= errVisit
689 tree.entries[3].mark -= errVisit
690 clear = false
691 err = walk(tree.name, markFn)
692 if err == nil {
693 t.Fatalf("expected error return from Walk")
694 }
695 if len(errors) != 1 {
696 t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
697 }
698
699 checkMarks(t, false)
700 errors = errors[0:0]
701
702
703 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
704 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
705 })
706 }
707
708 func touch(t *testing.T, name string) {
709 f, err := os.Create(name)
710 if err != nil {
711 t.Fatal(err)
712 }
713 if err := f.Close(); err != nil {
714 t.Fatal(err)
715 }
716 }
717
718 func TestWalkSkipDirOnFile(t *testing.T) {
719 td := t.TempDir()
720
721 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
722 t.Fatal(err)
723 }
724 touch(t, filepath.Join(td, "dir/foo1"))
725 touch(t, filepath.Join(td, "dir/foo2"))
726
727 sawFoo2 := false
728 walker := func(path string) error {
729 if strings.HasSuffix(path, "foo2") {
730 sawFoo2 = true
731 }
732 if strings.HasSuffix(path, "foo1") {
733 return filepath.SkipDir
734 }
735 return nil
736 }
737 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
738 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
739
740 check := func(t *testing.T, walk func(root string) error, root string) {
741 t.Helper()
742 sawFoo2 = false
743 err := walk(root)
744 if err != nil {
745 t.Fatal(err)
746 }
747 if sawFoo2 {
748 t.Errorf("SkipDir on file foo1 did not block processing of foo2")
749 }
750 }
751
752 t.Run("Walk", func(t *testing.T) {
753 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
754 check(t, Walk, td)
755 check(t, Walk, filepath.Join(td, "dir"))
756 })
757 t.Run("WalkDir", func(t *testing.T) {
758 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
759 check(t, WalkDir, td)
760 check(t, WalkDir, filepath.Join(td, "dir"))
761 })
762 }
763
764 func TestWalkSkipAllOnFile(t *testing.T) {
765 td := t.TempDir()
766
767 if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
768 t.Fatal(err)
769 }
770 if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
771 t.Fatal(err)
772 }
773
774 touch(t, filepath.Join(td, "dir", "foo1"))
775 touch(t, filepath.Join(td, "dir", "foo2"))
776 touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
777 touch(t, filepath.Join(td, "dir", "foo4"))
778 touch(t, filepath.Join(td, "dir2", "bar"))
779 touch(t, filepath.Join(td, "last"))
780
781 remainingWereSkipped := true
782 walker := func(path string) error {
783 if strings.HasSuffix(path, "foo2") {
784 return filepath.SkipAll
785 }
786
787 if strings.HasSuffix(path, "foo3") ||
788 strings.HasSuffix(path, "foo4") ||
789 strings.HasSuffix(path, "bar") ||
790 strings.HasSuffix(path, "last") {
791 remainingWereSkipped = false
792 }
793 return nil
794 }
795
796 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
797 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
798
799 check := func(t *testing.T, walk func(root string) error, root string) {
800 t.Helper()
801 remainingWereSkipped = true
802 if err := walk(root); err != nil {
803 t.Fatal(err)
804 }
805 if !remainingWereSkipped {
806 t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
807 }
808 }
809
810 t.Run("Walk", func(t *testing.T) {
811 Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
812 check(t, Walk, td)
813 check(t, Walk, filepath.Join(td, "dir"))
814 })
815 t.Run("WalkDir", func(t *testing.T) {
816 WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
817 check(t, WalkDir, td)
818 check(t, WalkDir, filepath.Join(td, "dir"))
819 })
820 }
821
822 func TestWalkFileError(t *testing.T) {
823 td := t.TempDir()
824
825 touch(t, filepath.Join(td, "foo"))
826 touch(t, filepath.Join(td, "bar"))
827 dir := filepath.Join(td, "dir")
828 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
829 t.Fatal(err)
830 }
831 touch(t, filepath.Join(dir, "baz"))
832 touch(t, filepath.Join(dir, "stat-error"))
833 defer func() {
834 *filepath.LstatP = os.Lstat
835 }()
836 statErr := errors.New("some stat error")
837 *filepath.LstatP = func(path string) (fs.FileInfo, error) {
838 if strings.HasSuffix(path, "stat-error") {
839 return nil, statErr
840 }
841 return os.Lstat(path)
842 }
843 got := map[string]error{}
844 err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
845 rel, _ := filepath.Rel(td, path)
846 got[filepath.ToSlash(rel)] = err
847 return nil
848 })
849 if err != nil {
850 t.Errorf("Walk error: %v", err)
851 }
852 want := map[string]error{
853 ".": nil,
854 "foo": nil,
855 "bar": nil,
856 "dir": nil,
857 "dir/baz": nil,
858 "dir/stat-error": statErr,
859 }
860 if !reflect.DeepEqual(got, want) {
861 t.Errorf("Walked %#v; want %#v", got, want)
862 }
863 }
864
865 func TestWalkSymlinkRoot(t *testing.T) {
866 testenv.MustHaveSymlink(t)
867
868 td := t.TempDir()
869 dir := filepath.Join(td, "dir")
870 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
871 t.Fatal(err)
872 }
873 touch(t, filepath.Join(dir, "foo"))
874
875 link := filepath.Join(td, "link")
876 if err := os.Symlink("dir", link); err != nil {
877 t.Fatal(err)
878 }
879
880 abslink := filepath.Join(td, "abslink")
881 if err := os.Symlink(dir, abslink); err != nil {
882 t.Fatal(err)
883 }
884
885 linklink := filepath.Join(td, "linklink")
886 if err := os.Symlink("link", linklink); err != nil {
887 t.Fatal(err)
888 }
889
890
891
892
893
894
895
896
897
898
899
900
901 for _, tt := range []struct {
902 desc string
903 root string
904 want []string
905 buggyGOOS []string
906 }{
907 {
908 desc: "no slash",
909 root: link,
910 want: []string{link},
911 },
912 {
913 desc: "slash",
914 root: link + string(filepath.Separator),
915 want: []string{link, filepath.Join(link, "foo")},
916 },
917 {
918 desc: "abs no slash",
919 root: abslink,
920 want: []string{abslink},
921 },
922 {
923 desc: "abs with slash",
924 root: abslink + string(filepath.Separator),
925 want: []string{abslink, filepath.Join(abslink, "foo")},
926 },
927 {
928 desc: "double link no slash",
929 root: linklink,
930 want: []string{linklink},
931 },
932 {
933 desc: "double link with slash",
934 root: linklink + string(filepath.Separator),
935 want: []string{linklink, filepath.Join(linklink, "foo")},
936 buggyGOOS: []string{"darwin", "ios"},
937 },
938 } {
939 tt := tt
940 t.Run(tt.desc, func(t *testing.T) {
941 var walked []string
942 err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
943 if err != nil {
944 return err
945 }
946 t.Logf("%#q: %v", path, info.Mode())
947 walked = append(walked, filepath.Clean(path))
948 return nil
949 })
950 if err != nil {
951 t.Fatal(err)
952 }
953
954 if !slices.Equal(walked, tt.want) {
955 t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
956 if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
957 t.Logf("(ignoring known bug on %v)", runtime.GOOS)
958 } else {
959 t.Fail()
960 }
961 }
962 })
963 }
964 }
965
966 var basetests = []PathTest{
967 {"", "."},
968 {".", "."},
969 {"/.", "."},
970 {"/", "/"},
971 {"////", "/"},
972 {"x/", "x"},
973 {"abc", "abc"},
974 {"abc/def", "def"},
975 {"a/b/.x", ".x"},
976 {"a/b/c.", "c."},
977 {"a/b/c.x", "c.x"},
978 }
979
980 var winbasetests = []PathTest{
981 {`c:\`, `\`},
982 {`c:.`, `.`},
983 {`c:\a\b`, `b`},
984 {`c:a\b`, `b`},
985 {`c:a\b\c`, `c`},
986 {`\\host\share\`, `\`},
987 {`\\host\share\a`, `a`},
988 {`\\host\share\a\b`, `b`},
989 }
990
991 func TestBase(t *testing.T) {
992 tests := basetests
993 if runtime.GOOS == "windows" {
994
995 for i := range tests {
996 tests[i].result = filepath.Clean(tests[i].result)
997 }
998
999 tests = append(tests, winbasetests...)
1000 }
1001 for _, test := range tests {
1002 if s := filepath.Base(test.path); s != test.result {
1003 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
1004 }
1005 }
1006 }
1007
1008 var dirtests = []PathTest{
1009 {"", "."},
1010 {".", "."},
1011 {"/.", "/"},
1012 {"/", "/"},
1013 {"/foo", "/"},
1014 {"x/", "x"},
1015 {"abc", "."},
1016 {"abc/def", "abc"},
1017 {"a/b/.x", "a/b"},
1018 {"a/b/c.", "a/b"},
1019 {"a/b/c.x", "a/b"},
1020 }
1021
1022 var nonwindirtests = []PathTest{
1023 {"////", "/"},
1024 }
1025
1026 var windirtests = []PathTest{
1027 {`c:\`, `c:\`},
1028 {`c:.`, `c:.`},
1029 {`c:\a\b`, `c:\a`},
1030 {`c:a\b`, `c:a`},
1031 {`c:a\b\c`, `c:a\b`},
1032 {`\\host\share`, `\\host\share`},
1033 {`\\host\share\`, `\\host\share\`},
1034 {`\\host\share\a`, `\\host\share\`},
1035 {`\\host\share\a\b`, `\\host\share\a`},
1036 {`\\\\`, `\\\\`},
1037 }
1038
1039 func TestDir(t *testing.T) {
1040 tests := dirtests
1041 if runtime.GOOS == "windows" {
1042
1043 for i := range tests {
1044 tests[i].result = filepath.Clean(tests[i].result)
1045 }
1046
1047 tests = append(tests, windirtests...)
1048 } else {
1049 tests = append(tests, nonwindirtests...)
1050 }
1051 for _, test := range tests {
1052 if s := filepath.Dir(test.path); s != test.result {
1053 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
1054 }
1055 }
1056 }
1057
1058 type IsAbsTest struct {
1059 path string
1060 isAbs bool
1061 }
1062
1063 var isabstests = []IsAbsTest{
1064 {"", false},
1065 {"/", true},
1066 {"/usr/bin/gcc", true},
1067 {"..", false},
1068 {"/a/../bb", true},
1069 {".", false},
1070 {"./", false},
1071 {"lala", false},
1072 }
1073
1074 var winisabstests = []IsAbsTest{
1075 {`C:\`, true},
1076 {`c\`, false},
1077 {`c::`, false},
1078 {`c:`, false},
1079 {`/`, false},
1080 {`\`, false},
1081 {`\Windows`, false},
1082 {`c:a\b`, false},
1083 {`c:\a\b`, true},
1084 {`c:/a/b`, true},
1085 {`\\host\share`, true},
1086 {`\\host\share\`, true},
1087 {`\\host\share\foo`, true},
1088 {`//host/share/foo/bar`, true},
1089 {`\\?\a\b\c`, true},
1090 {`\??\a\b\c`, true},
1091 }
1092
1093 func TestIsAbs(t *testing.T) {
1094 var tests []IsAbsTest
1095 if runtime.GOOS == "windows" {
1096 tests = append(tests, winisabstests...)
1097
1098 for _, test := range isabstests {
1099 tests = append(tests, IsAbsTest{test.path, false})
1100 }
1101
1102 for _, test := range isabstests {
1103 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
1104 }
1105 } else {
1106 tests = isabstests
1107 }
1108
1109 for _, test := range tests {
1110 if r := filepath.IsAbs(test.path); r != test.isAbs {
1111 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
1112 }
1113 }
1114 }
1115
1116 type EvalSymlinksTest struct {
1117
1118 path, dest string
1119 }
1120
1121 var EvalSymlinksTestDirs = []EvalSymlinksTest{
1122 {"test", ""},
1123 {"test/dir", ""},
1124 {"test/dir/link3", "../../"},
1125 {"test/link1", "../test"},
1126 {"test/link2", "dir"},
1127 {"test/linkabs", "/"},
1128 {"test/link4", "../test2"},
1129 {"test2", "test/dir"},
1130
1131 {"src", ""},
1132 {"src/pool", ""},
1133 {"src/pool/test", ""},
1134 {"src/versions", ""},
1135 {"src/versions/current", "../../version"},
1136 {"src/versions/v1", ""},
1137 {"src/versions/v1/modules", ""},
1138 {"src/versions/v1/modules/test", "../../../pool/test"},
1139 {"version", "src/versions/v1"},
1140 }
1141
1142 var EvalSymlinksTests = []EvalSymlinksTest{
1143 {"test", "test"},
1144 {"test/dir", "test/dir"},
1145 {"test/dir/../..", "."},
1146 {"test/link1", "test"},
1147 {"test/link2", "test/dir"},
1148 {"test/link1/dir", "test/dir"},
1149 {"test/link2/..", "test"},
1150 {"test/dir/link3", "."},
1151 {"test/link2/link3/test", "test"},
1152 {"test/linkabs", "/"},
1153 {"test/link4/..", "test"},
1154 {"src/versions/current/modules/test", "src/pool/test"},
1155 }
1156
1157
1158
1159 func simpleJoin(dir, path string) string {
1160 return dir + string(filepath.Separator) + path
1161 }
1162
1163 func testEvalSymlinks(t *testing.T, path, want string) {
1164 have, err := filepath.EvalSymlinks(path)
1165 if err != nil {
1166 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1167 return
1168 }
1169 if filepath.Clean(have) != filepath.Clean(want) {
1170 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1171 }
1172 }
1173
1174 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1175 t.Chdir(wd)
1176 have, err := filepath.EvalSymlinks(path)
1177 if err != nil {
1178 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1179 return
1180 }
1181 if filepath.Clean(have) != filepath.Clean(want) {
1182 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1183 }
1184 }
1185
1186 func TestEvalSymlinks(t *testing.T) {
1187 testenv.MustHaveSymlink(t)
1188
1189 tmpDir := t.TempDir()
1190
1191
1192
1193 var err error
1194 tmpDir, err = filepath.EvalSymlinks(tmpDir)
1195 if err != nil {
1196 t.Fatal("eval symlink for tmp dir:", err)
1197 }
1198
1199
1200 for _, d := range EvalSymlinksTestDirs {
1201 var err error
1202 path := simpleJoin(tmpDir, d.path)
1203 if d.dest == "" {
1204 err = os.Mkdir(path, 0755)
1205 } else {
1206 err = os.Symlink(d.dest, path)
1207 }
1208 if err != nil {
1209 t.Fatal(err)
1210 }
1211 }
1212
1213
1214 for _, test := range EvalSymlinksTests {
1215 path := simpleJoin(tmpDir, test.path)
1216
1217 dest := simpleJoin(tmpDir, test.dest)
1218 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1219 dest = test.dest
1220 }
1221 testEvalSymlinks(t, path, dest)
1222
1223
1224 testEvalSymlinksAfterChdir(t, path, ".", ".")
1225
1226
1227 if runtime.GOOS == "windows" {
1228 volDot := filepath.VolumeName(tmpDir) + "."
1229 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1230 }
1231
1232
1233 dotdotPath := simpleJoin("..", test.dest)
1234 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1235 dotdotPath = test.dest
1236 }
1237 testEvalSymlinksAfterChdir(t,
1238 simpleJoin(tmpDir, "test"),
1239 simpleJoin("..", test.path),
1240 dotdotPath)
1241
1242
1243 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1244 }
1245 }
1246
1247 func TestEvalSymlinksIsNotExist(t *testing.T) {
1248 testenv.MustHaveSymlink(t)
1249 t.Chdir(t.TempDir())
1250
1251 _, err := filepath.EvalSymlinks("notexist")
1252 if !os.IsNotExist(err) {
1253 t.Errorf("expected the file is not found, got %v\n", err)
1254 }
1255
1256 err = os.Symlink("notexist", "link")
1257 if err != nil {
1258 t.Fatal(err)
1259 }
1260 defer os.Remove("link")
1261
1262 _, err = filepath.EvalSymlinks("link")
1263 if !os.IsNotExist(err) {
1264 t.Errorf("expected the file is not found, got %v\n", err)
1265 }
1266 }
1267
1268 func TestIssue13582(t *testing.T) {
1269 testenv.MustHaveSymlink(t)
1270
1271 tmpDir := t.TempDir()
1272
1273 dir := filepath.Join(tmpDir, "dir")
1274 err := os.Mkdir(dir, 0755)
1275 if err != nil {
1276 t.Fatal(err)
1277 }
1278 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1279 err = os.Symlink(dir, linkToDir)
1280 if err != nil {
1281 t.Fatal(err)
1282 }
1283 file := filepath.Join(linkToDir, "file")
1284 err = os.WriteFile(file, nil, 0644)
1285 if err != nil {
1286 t.Fatal(err)
1287 }
1288 link1 := filepath.Join(linkToDir, "link1")
1289 err = os.Symlink(file, link1)
1290 if err != nil {
1291 t.Fatal(err)
1292 }
1293 link2 := filepath.Join(linkToDir, "link2")
1294 err = os.Symlink(link1, link2)
1295 if err != nil {
1296 t.Fatal(err)
1297 }
1298
1299
1300 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1301 if err != nil {
1302 t.Fatal(err)
1303 }
1304 realDir := filepath.Join(realTmpDir, "dir")
1305 realFile := filepath.Join(realDir, "file")
1306
1307 tests := []struct {
1308 path, want string
1309 }{
1310 {dir, realDir},
1311 {linkToDir, realDir},
1312 {file, realFile},
1313 {link1, realFile},
1314 {link2, realFile},
1315 }
1316 for i, test := range tests {
1317 have, err := filepath.EvalSymlinks(test.path)
1318 if err != nil {
1319 t.Fatal(err)
1320 }
1321 if have != test.want {
1322 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1323 }
1324 }
1325 }
1326
1327
1328 func TestRelativeSymlinkToAbsolute(t *testing.T) {
1329 testenv.MustHaveSymlink(t)
1330
1331
1332 tmpDir := t.TempDir()
1333 t.Chdir(tmpDir)
1334
1335
1336
1337
1338
1339 if err := os.Symlink(tmpDir, "link"); err != nil {
1340 t.Fatal(err)
1341 }
1342 t.Logf(`os.Symlink(%q, "link")`, tmpDir)
1343
1344 p, err := filepath.EvalSymlinks("link")
1345 if err != nil {
1346 t.Fatalf(`EvalSymlinks("link"): %v`, err)
1347 }
1348 want, err := filepath.EvalSymlinks(tmpDir)
1349 if err != nil {
1350 t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
1351 }
1352 if p != want {
1353 t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
1354 }
1355 t.Logf(`EvalSymlinks("link") = %q`, p)
1356 }
1357
1358
1359
1360 var absTestDirs = []string{
1361 "a",
1362 "a/b",
1363 "a/b/c",
1364 }
1365
1366
1367
1368
1369 var absTests = []string{
1370 ".",
1371 "b",
1372 "b/",
1373 "../a",
1374 "../a/b",
1375 "../a/b/./c/../../.././a",
1376 "../a/b/./c/../../.././a/",
1377 "$",
1378 "$/.",
1379 "$/a/../a/b",
1380 "$/a/b/c/../../.././a",
1381 "$/a/b/c/../../.././a/",
1382 }
1383
1384 func TestAbs(t *testing.T) {
1385 root := t.TempDir()
1386 t.Chdir(root)
1387
1388 for _, dir := range absTestDirs {
1389 err := os.Mkdir(dir, 0777)
1390 if err != nil {
1391 t.Fatal("Mkdir failed: ", err)
1392 }
1393 }
1394
1395
1396
1397 tests := absTests
1398 if runtime.GOOS == "windows" {
1399 vol := filepath.VolumeName(root)
1400 var extra []string
1401 for _, path := range absTests {
1402 if strings.Contains(path, "$") {
1403 continue
1404 }
1405 path = vol + path
1406 extra = append(extra, path)
1407 }
1408 tests = append(slices.Clip(tests), extra...)
1409 }
1410
1411 err := os.Chdir(absTestDirs[0])
1412 if err != nil {
1413 t.Fatal("chdir failed: ", err)
1414 }
1415
1416 for _, path := range tests {
1417 path = strings.ReplaceAll(path, "$", root)
1418 info, err := os.Stat(path)
1419 if err != nil {
1420 t.Errorf("%s: %s", path, err)
1421 continue
1422 }
1423
1424 abspath, err := filepath.Abs(path)
1425 if err != nil {
1426 t.Errorf("Abs(%q) error: %v", path, err)
1427 continue
1428 }
1429 absinfo, err := os.Stat(abspath)
1430 if err != nil || !os.SameFile(absinfo, info) {
1431 t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1432 }
1433 if !filepath.IsAbs(abspath) {
1434 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1435 }
1436 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1437 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1438 }
1439 }
1440 }
1441
1442
1443
1444
1445 func TestAbsEmptyString(t *testing.T) {
1446 root := t.TempDir()
1447 t.Chdir(root)
1448
1449 info, err := os.Stat(root)
1450 if err != nil {
1451 t.Fatalf("%s: %s", root, err)
1452 }
1453
1454 abspath, err := filepath.Abs("")
1455 if err != nil {
1456 t.Fatalf(`Abs("") error: %v`, err)
1457 }
1458 absinfo, err := os.Stat(abspath)
1459 if err != nil || !os.SameFile(absinfo, info) {
1460 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1461 }
1462 if !filepath.IsAbs(abspath) {
1463 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1464 }
1465 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1466 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1467 }
1468 }
1469
1470 type RelTests struct {
1471 root, path, want string
1472 }
1473
1474 var reltests = []RelTests{
1475 {"a/b", "a/b", "."},
1476 {"a/b/.", "a/b", "."},
1477 {"a/b", "a/b/.", "."},
1478 {"./a/b", "a/b", "."},
1479 {"a/b", "./a/b", "."},
1480 {"ab/cd", "ab/cde", "../cde"},
1481 {"ab/cd", "ab/c", "../c"},
1482 {"a/b", "a/b/c/d", "c/d"},
1483 {"a/b", "a/b/../c", "../c"},
1484 {"a/b/../c", "a/b", "../b"},
1485 {"a/b/c", "a/c/d", "../../c/d"},
1486 {"a/b", "c/d", "../../c/d"},
1487 {"a/b/c/d", "a/b", "../.."},
1488 {"a/b/c/d", "a/b/", "../.."},
1489 {"a/b/c/d/", "a/b", "../.."},
1490 {"a/b/c/d/", "a/b/", "../.."},
1491 {"../../a/b", "../../a/b/c/d", "c/d"},
1492 {"/a/b", "/a/b", "."},
1493 {"/a/b/.", "/a/b", "."},
1494 {"/a/b", "/a/b/.", "."},
1495 {"/ab/cd", "/ab/cde", "../cde"},
1496 {"/ab/cd", "/ab/c", "../c"},
1497 {"/a/b", "/a/b/c/d", "c/d"},
1498 {"/a/b", "/a/b/../c", "../c"},
1499 {"/a/b/../c", "/a/b", "../b"},
1500 {"/a/b/c", "/a/c/d", "../../c/d"},
1501 {"/a/b", "/c/d", "../../c/d"},
1502 {"/a/b/c/d", "/a/b", "../.."},
1503 {"/a/b/c/d", "/a/b/", "../.."},
1504 {"/a/b/c/d/", "/a/b", "../.."},
1505 {"/a/b/c/d/", "/a/b/", "../.."},
1506 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1507 {".", "a/b", "a/b"},
1508 {".", "..", ".."},
1509
1510
1511 {"..", ".", "err"},
1512 {"..", "a", "err"},
1513 {"../..", "..", "err"},
1514 {"a", "/a", "err"},
1515 {"/a", "a", "err"},
1516 }
1517
1518 var winreltests = []RelTests{
1519 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1520 {`C:\`, `D:\`, `err`},
1521 {`C:`, `D:`, `err`},
1522 {`C:\Projects`, `c:\projects\src`, `src`},
1523 {`C:\Projects`, `c:\projects`, `.`},
1524 {`C:\Projects\a\..`, `c:\projects`, `.`},
1525 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1526 }
1527
1528 func TestRel(t *testing.T) {
1529 tests := append([]RelTests{}, reltests...)
1530 if runtime.GOOS == "windows" {
1531 for i := range tests {
1532 tests[i].want = filepath.FromSlash(tests[i].want)
1533 }
1534 tests = append(tests, winreltests...)
1535 }
1536 for _, test := range tests {
1537 got, err := filepath.Rel(test.root, test.path)
1538 if test.want == "err" {
1539 if err == nil {
1540 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1541 }
1542 continue
1543 }
1544 if err != nil {
1545 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1546 }
1547 if got != test.want {
1548 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1549 }
1550 }
1551 }
1552
1553 type VolumeNameTest struct {
1554 path string
1555 vol string
1556 }
1557
1558 var volumenametests = []VolumeNameTest{
1559 {`c:/foo/bar`, `c:`},
1560 {`c:`, `c:`},
1561 {`c:\`, `c:`},
1562 {`2:`, `2:`},
1563 {``, ``},
1564 {`\\\host`, `\\\host`},
1565 {`\\\host\`, `\\\host`},
1566 {`\\\host\share`, `\\\host`},
1567 {`\\\host\\share`, `\\\host`},
1568 {`\\host`, `\\host`},
1569 {`//host`, `\\host`},
1570 {`\\host\`, `\\host\`},
1571 {`//host/`, `\\host\`},
1572 {`\\host\share`, `\\host\share`},
1573 {`//host/share`, `\\host\share`},
1574 {`\\host\share\`, `\\host\share`},
1575 {`//host/share/`, `\\host\share`},
1576 {`\\host\share\foo`, `\\host\share`},
1577 {`//host/share/foo`, `\\host\share`},
1578 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1579 {`//host/share//foo///bar////baz`, `\\host\share`},
1580 {`\\host\share\foo\..\bar`, `\\host\share`},
1581 {`//host/share/foo/../bar`, `\\host\share`},
1582 {`//.`, `\\.`},
1583 {`//./`, `\\.\`},
1584 {`//./NUL`, `\\.\NUL`},
1585 {`//?`, `\\?`},
1586 {`//?/`, `\\?\`},
1587 {`//?/NUL`, `\\?\NUL`},
1588 {`/??`, `\??`},
1589 {`/??/`, `\??\`},
1590 {`/??/NUL`, `\??\NUL`},
1591 {`//./a/b`, `\\.\a`},
1592 {`//./C:`, `\\.\C:`},
1593 {`//./C:/`, `\\.\C:`},
1594 {`//./C:/a/b/c`, `\\.\C:`},
1595 {`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
1596 {`//./UNC/host`, `\\.\UNC\host`},
1597 {`//./UNC/host\`, `\\.\UNC\host\`},
1598 {`//./UNC`, `\\.\UNC`},
1599 {`//./UNC/`, `\\.\UNC\`},
1600 {`\\?\x`, `\\?\x`},
1601 {`\??\x`, `\??\x`},
1602 }
1603
1604 func TestVolumeName(t *testing.T) {
1605 if runtime.GOOS != "windows" {
1606 return
1607 }
1608 for _, v := range volumenametests {
1609 if vol := filepath.VolumeName(v.path); vol != v.vol {
1610 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1611 }
1612 }
1613 }
1614
1615 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1616 if runtime.GOOS != "windows" {
1617 return
1618 }
1619 wd, _ := os.Getwd()
1620 if len(wd) < 3 {
1621 t.Errorf("Current directory path %q is too short", wd)
1622 }
1623 lp := strings.ToLower(wd)
1624 up := strings.ToUpper(wd)
1625 flp, err := filepath.EvalSymlinks(lp)
1626 if err != nil {
1627 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1628 }
1629 fup, err := filepath.EvalSymlinks(up)
1630 if err != nil {
1631 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1632 }
1633 if flp != fup {
1634 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1635 }
1636 }
1637
1638 func TestBug3486(t *testing.T) {
1639 if runtime.GOOS == "ios" {
1640 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1641 }
1642 root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
1643 utf16 := filepath.Join(root, "utf16")
1644 utf8 := filepath.Join(root, "utf8")
1645 seenUTF16 := false
1646 seenUTF8 := false
1647 err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1648 if err != nil {
1649 t.Fatal(err)
1650 }
1651
1652 switch pth {
1653 case utf16:
1654 seenUTF16 = true
1655 return filepath.SkipDir
1656 case utf8:
1657 if !seenUTF16 {
1658 t.Fatal("filepath.Walk out of order - utf8 before utf16")
1659 }
1660 seenUTF8 = true
1661 }
1662 return nil
1663 })
1664 if err != nil {
1665 t.Fatal(err)
1666 }
1667 if !seenUTF8 {
1668 t.Fatalf("%q not seen", utf8)
1669 }
1670 }
1671
1672 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1673 tmpdir := t.TempDir()
1674 t.Chdir(tmpdir)
1675
1676 err := mklink(tmpdir, "link")
1677 if err != nil {
1678 t.Fatal(err)
1679 }
1680
1681 var visited []string
1682 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1683 if err != nil {
1684 t.Fatal(err)
1685 }
1686 rel, err := filepath.Rel(tmpdir, path)
1687 if err != nil {
1688 t.Fatal(err)
1689 }
1690 visited = append(visited, rel)
1691 return nil
1692 })
1693 if err != nil {
1694 t.Fatal(err)
1695 }
1696 slices.Sort(visited)
1697 want := []string{".", "link"}
1698 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1699 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1700 }
1701 }
1702
1703 func TestWalkSymlink(t *testing.T) {
1704 testenv.MustHaveSymlink(t)
1705 testWalkSymlink(t, os.Symlink)
1706 }
1707
1708 func TestIssue29372(t *testing.T) {
1709 tmpDir := t.TempDir()
1710
1711 path := filepath.Join(tmpDir, "file.txt")
1712 err := os.WriteFile(path, nil, 0644)
1713 if err != nil {
1714 t.Fatal(err)
1715 }
1716
1717 pathSeparator := string(filepath.Separator)
1718 tests := []string{
1719 path + strings.Repeat(pathSeparator, 1),
1720 path + strings.Repeat(pathSeparator, 2),
1721 path + strings.Repeat(pathSeparator, 1) + ".",
1722 path + strings.Repeat(pathSeparator, 2) + ".",
1723 path + strings.Repeat(pathSeparator, 1) + "..",
1724 path + strings.Repeat(pathSeparator, 2) + "..",
1725 }
1726
1727 for i, test := range tests {
1728 _, err = filepath.EvalSymlinks(test)
1729 if err != syscall.ENOTDIR {
1730 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1731 }
1732 }
1733 }
1734
1735
1736 func TestEvalSymlinksAboveRoot(t *testing.T) {
1737 testenv.MustHaveSymlink(t)
1738
1739 t.Parallel()
1740
1741 tmpDir := t.TempDir()
1742
1743 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1744 if err != nil {
1745 t.Fatal(err)
1746 }
1747
1748 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1749 t.Fatal(err)
1750 }
1751 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1752 t.Fatal(err)
1753 }
1754 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1755 t.Fatal(err)
1756 }
1757
1758
1759 vol := filepath.VolumeName(evalTmpDir)
1760 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1761 var dd []string
1762 for i := 0; i < c+2; i++ {
1763 dd = append(dd, "..")
1764 }
1765
1766 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1767
1768
1769 for _, i := range []int{c, c + 1, c + 2} {
1770 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1771 resolved, err := filepath.EvalSymlinks(check)
1772 switch {
1773 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1774
1775 testenv.SkipFlaky(t, 37910)
1776 case err != nil:
1777 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1778 case !strings.HasSuffix(resolved, wantSuffix):
1779 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1780 default:
1781 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1782 }
1783 }
1784 }
1785
1786
1787 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1788 testenv.MustHaveSymlink(t)
1789 t.Chdir(t.TempDir())
1790
1791 subdir := filepath.Join("a", "b")
1792 if err := os.MkdirAll(subdir, 0777); err != nil {
1793 t.Fatal(err)
1794 }
1795 if err := os.Symlink(subdir, "c"); err != nil {
1796 t.Fatal(err)
1797 }
1798 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1799 t.Fatal(err)
1800 }
1801
1802 subdir = filepath.Join("d", "e", "f")
1803 if err := os.MkdirAll(subdir, 0777); err != nil {
1804 t.Fatal(err)
1805 }
1806 if err := os.Chdir(subdir); err != nil {
1807 t.Fatal(err)
1808 }
1809
1810 check := filepath.Join("..", "..", "..", "c", "file")
1811 wantSuffix := filepath.Join("a", "b", "file")
1812 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1813 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1814 } else if !strings.HasSuffix(resolved, wantSuffix) {
1815 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1816 } else {
1817 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1818 }
1819 }
1820
1821 func TestIssue51617(t *testing.T) {
1822 dir := t.TempDir()
1823 for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
1824 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
1825 t.Fatal(err)
1826 }
1827 }
1828 bad := filepath.Join(dir, "a", "bad")
1829 if err := os.Chmod(bad, 0); err != nil {
1830 t.Fatal(err)
1831 }
1832 defer os.Chmod(bad, 0700)
1833 var saw []string
1834 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1835 if err != nil {
1836 return filepath.SkipDir
1837 }
1838 if d.IsDir() {
1839 rel, err := filepath.Rel(dir, path)
1840 if err != nil {
1841 t.Fatal(err)
1842 }
1843 saw = append(saw, rel)
1844 }
1845 return nil
1846 })
1847 if err != nil {
1848 t.Fatal(err)
1849 }
1850 want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
1851 if !slices.Equal(saw, want) {
1852 t.Errorf("got directories %v, want %v", saw, want)
1853 }
1854 }
1855
1856 func TestEscaping(t *testing.T) {
1857 dir := t.TempDir()
1858 t.Chdir(t.TempDir())
1859
1860 for _, p := range []string{
1861 filepath.Join(dir, "x"),
1862 } {
1863 if !filepath.IsLocal(p) {
1864 continue
1865 }
1866 f, err := os.Create(p)
1867 if err != nil {
1868 f.Close()
1869 }
1870 ents, err := os.ReadDir(dir)
1871 if err != nil {
1872 t.Fatal(err)
1873 }
1874 for _, e := range ents {
1875 t.Fatalf("found: %v", e.Name())
1876 }
1877 }
1878 }
1879
1880 func TestEvalSymlinksTooManyLinks(t *testing.T) {
1881 testenv.MustHaveSymlink(t)
1882 dir := filepath.Join(t.TempDir(), "dir")
1883 err := os.Symlink(dir, dir)
1884 if err != nil {
1885 t.Fatal(err)
1886 }
1887 _, err = filepath.EvalSymlinks(dir)
1888 if err == nil {
1889 t.Fatal("expected error, got nil")
1890 }
1891 }
1892
View as plain text