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
1512 {"..", ".", "err"},
1513 {"..", "a", "err"},
1514 {"../..", "..", "err"},
1515 {"a", "/a", "err"},
1516 {"/a", "a", "err"},
1517 }
1518
1519 var winreltests = []RelTests{
1520 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1521 {`C:\`, `D:\`, `err`},
1522 {`C:`, `D:`, `err`},
1523 {`C:\Projects`, `c:\projects\src`, `src`},
1524 {`C:\Projects`, `c:\projects`, `.`},
1525 {`C:\Projects\a\..`, `c:\projects`, `.`},
1526 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1527 }
1528
1529 func TestRel(t *testing.T) {
1530 tests := append([]RelTests{}, reltests...)
1531 if runtime.GOOS == "windows" {
1532 for i := range tests {
1533 tests[i].want = filepath.FromSlash(tests[i].want)
1534 }
1535 tests = append(tests, winreltests...)
1536 }
1537 for _, test := range tests {
1538 got, err := filepath.Rel(test.root, test.path)
1539 if test.want == "err" {
1540 if err == nil {
1541 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1542 }
1543 continue
1544 }
1545 if err != nil {
1546 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1547 }
1548 if got != test.want {
1549 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1550 }
1551 }
1552 }
1553
1554 type VolumeNameTest struct {
1555 path string
1556 vol string
1557 }
1558
1559 var volumenametests = []VolumeNameTest{
1560 {`c:/foo/bar`, `c:`},
1561 {`c:`, `c:`},
1562 {`c:\`, `c:`},
1563 {`2:`, `2:`},
1564 {``, ``},
1565 {`\\\host`, `\\\host`},
1566 {`\\\host\`, `\\\host`},
1567 {`\\\host\share`, `\\\host`},
1568 {`\\\host\\share`, `\\\host`},
1569 {`\\host`, `\\host`},
1570 {`//host`, `\\host`},
1571 {`\\host\`, `\\host\`},
1572 {`//host/`, `\\host\`},
1573 {`\\host\share`, `\\host\share`},
1574 {`//host/share`, `\\host\share`},
1575 {`\\host\share\`, `\\host\share`},
1576 {`//host/share/`, `\\host\share`},
1577 {`\\host\share\foo`, `\\host\share`},
1578 {`//host/share/foo`, `\\host\share`},
1579 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1580 {`//host/share//foo///bar////baz`, `\\host\share`},
1581 {`\\host\share\foo\..\bar`, `\\host\share`},
1582 {`//host/share/foo/../bar`, `\\host\share`},
1583 {`//.`, `\\.`},
1584 {`//./`, `\\.\`},
1585 {`//./NUL`, `\\.\NUL`},
1586 {`//?`, `\\?`},
1587 {`//?/`, `\\?\`},
1588 {`//?/NUL`, `\\?\NUL`},
1589 {`/??`, `\??`},
1590 {`/??/`, `\??\`},
1591 {`/??/NUL`, `\??\NUL`},
1592 {`//./a/b`, `\\.\a`},
1593 {`//./C:`, `\\.\C:`},
1594 {`//./C:/`, `\\.\C:`},
1595 {`//./C:/a/b/c`, `\\.\C:`},
1596 {`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
1597 {`//./UNC/host`, `\\.\UNC\host`},
1598 {`//./UNC/host\`, `\\.\UNC\host\`},
1599 {`//./UNC`, `\\.\UNC`},
1600 {`//./UNC/`, `\\.\UNC\`},
1601 {`\\?\x`, `\\?\x`},
1602 {`\??\x`, `\??\x`},
1603 }
1604
1605 func TestVolumeName(t *testing.T) {
1606 if runtime.GOOS != "windows" {
1607 return
1608 }
1609 for _, v := range volumenametests {
1610 if vol := filepath.VolumeName(v.path); vol != v.vol {
1611 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1612 }
1613 }
1614 }
1615
1616 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1617 if runtime.GOOS != "windows" {
1618 return
1619 }
1620 wd, _ := os.Getwd()
1621 if len(wd) < 3 {
1622 t.Errorf("Current directory path %q is too short", wd)
1623 }
1624 lp := strings.ToLower(wd)
1625 up := strings.ToUpper(wd)
1626 flp, err := filepath.EvalSymlinks(lp)
1627 if err != nil {
1628 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1629 }
1630 fup, err := filepath.EvalSymlinks(up)
1631 if err != nil {
1632 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1633 }
1634 if flp != fup {
1635 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1636 }
1637 }
1638
1639 func TestBug3486(t *testing.T) {
1640 if runtime.GOOS == "ios" {
1641 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1642 }
1643 root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
1644 utf16 := filepath.Join(root, "utf16")
1645 utf8 := filepath.Join(root, "utf8")
1646 seenUTF16 := false
1647 seenUTF8 := false
1648 err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1649 if err != nil {
1650 t.Fatal(err)
1651 }
1652
1653 switch pth {
1654 case utf16:
1655 seenUTF16 = true
1656 return filepath.SkipDir
1657 case utf8:
1658 if !seenUTF16 {
1659 t.Fatal("filepath.Walk out of order - utf8 before utf16")
1660 }
1661 seenUTF8 = true
1662 }
1663 return nil
1664 })
1665 if err != nil {
1666 t.Fatal(err)
1667 }
1668 if !seenUTF8 {
1669 t.Fatalf("%q not seen", utf8)
1670 }
1671 }
1672
1673 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1674 tmpdir := t.TempDir()
1675 t.Chdir(tmpdir)
1676
1677 err := mklink(tmpdir, "link")
1678 if err != nil {
1679 t.Fatal(err)
1680 }
1681
1682 var visited []string
1683 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1684 if err != nil {
1685 t.Fatal(err)
1686 }
1687 rel, err := filepath.Rel(tmpdir, path)
1688 if err != nil {
1689 t.Fatal(err)
1690 }
1691 visited = append(visited, rel)
1692 return nil
1693 })
1694 if err != nil {
1695 t.Fatal(err)
1696 }
1697 slices.Sort(visited)
1698 want := []string{".", "link"}
1699 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1700 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1701 }
1702 }
1703
1704 func TestWalkSymlink(t *testing.T) {
1705 testenv.MustHaveSymlink(t)
1706 testWalkSymlink(t, os.Symlink)
1707 }
1708
1709 func TestIssue29372(t *testing.T) {
1710 tmpDir := t.TempDir()
1711
1712 path := filepath.Join(tmpDir, "file.txt")
1713 err := os.WriteFile(path, nil, 0644)
1714 if err != nil {
1715 t.Fatal(err)
1716 }
1717
1718 pathSeparator := string(filepath.Separator)
1719 tests := []string{
1720 path + strings.Repeat(pathSeparator, 1),
1721 path + strings.Repeat(pathSeparator, 2),
1722 path + strings.Repeat(pathSeparator, 1) + ".",
1723 path + strings.Repeat(pathSeparator, 2) + ".",
1724 path + strings.Repeat(pathSeparator, 1) + "..",
1725 path + strings.Repeat(pathSeparator, 2) + "..",
1726 }
1727
1728 for i, test := range tests {
1729 _, err = filepath.EvalSymlinks(test)
1730 if err != syscall.ENOTDIR {
1731 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1732 }
1733 }
1734 }
1735
1736
1737 func TestEvalSymlinksAboveRoot(t *testing.T) {
1738 testenv.MustHaveSymlink(t)
1739
1740 t.Parallel()
1741
1742 tmpDir := t.TempDir()
1743
1744 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1745 if err != nil {
1746 t.Fatal(err)
1747 }
1748
1749 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1750 t.Fatal(err)
1751 }
1752 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1753 t.Fatal(err)
1754 }
1755 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1756 t.Fatal(err)
1757 }
1758
1759
1760 vol := filepath.VolumeName(evalTmpDir)
1761 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1762 var dd []string
1763 for i := 0; i < c+2; i++ {
1764 dd = append(dd, "..")
1765 }
1766
1767 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1768
1769
1770 for _, i := range []int{c, c + 1, c + 2} {
1771 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1772 resolved, err := filepath.EvalSymlinks(check)
1773 switch {
1774 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1775
1776 testenv.SkipFlaky(t, 37910)
1777 case err != nil:
1778 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1779 case !strings.HasSuffix(resolved, wantSuffix):
1780 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1781 default:
1782 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1783 }
1784 }
1785 }
1786
1787
1788 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1789 testenv.MustHaveSymlink(t)
1790 t.Chdir(t.TempDir())
1791
1792 subdir := filepath.Join("a", "b")
1793 if err := os.MkdirAll(subdir, 0777); err != nil {
1794 t.Fatal(err)
1795 }
1796 if err := os.Symlink(subdir, "c"); err != nil {
1797 t.Fatal(err)
1798 }
1799 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1800 t.Fatal(err)
1801 }
1802
1803 subdir = filepath.Join("d", "e", "f")
1804 if err := os.MkdirAll(subdir, 0777); err != nil {
1805 t.Fatal(err)
1806 }
1807 if err := os.Chdir(subdir); err != nil {
1808 t.Fatal(err)
1809 }
1810
1811 check := filepath.Join("..", "..", "..", "c", "file")
1812 wantSuffix := filepath.Join("a", "b", "file")
1813 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1814 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1815 } else if !strings.HasSuffix(resolved, wantSuffix) {
1816 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1817 } else {
1818 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1819 }
1820 }
1821
1822 func TestIssue51617(t *testing.T) {
1823 dir := t.TempDir()
1824 for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
1825 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
1826 t.Fatal(err)
1827 }
1828 }
1829 bad := filepath.Join(dir, "a", "bad")
1830 if err := os.Chmod(bad, 0); err != nil {
1831 t.Fatal(err)
1832 }
1833 defer os.Chmod(bad, 0700)
1834 var saw []string
1835 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1836 if err != nil {
1837 return filepath.SkipDir
1838 }
1839 if d.IsDir() {
1840 rel, err := filepath.Rel(dir, path)
1841 if err != nil {
1842 t.Fatal(err)
1843 }
1844 saw = append(saw, rel)
1845 }
1846 return nil
1847 })
1848 if err != nil {
1849 t.Fatal(err)
1850 }
1851 want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
1852 if !slices.Equal(saw, want) {
1853 t.Errorf("got directories %v, want %v", saw, want)
1854 }
1855 }
1856
1857 func TestEscaping(t *testing.T) {
1858 dir := t.TempDir()
1859 t.Chdir(t.TempDir())
1860
1861 for _, p := range []string{
1862 filepath.Join(dir, "x"),
1863 } {
1864 if !filepath.IsLocal(p) {
1865 continue
1866 }
1867 f, err := os.Create(p)
1868 if err != nil {
1869 f.Close()
1870 }
1871 ents, err := os.ReadDir(dir)
1872 if err != nil {
1873 t.Fatal(err)
1874 }
1875 for _, e := range ents {
1876 t.Fatalf("found: %v", e.Name())
1877 }
1878 }
1879 }
1880
1881 func TestEvalSymlinksTooManyLinks(t *testing.T) {
1882 testenv.MustHaveSymlink(t)
1883 dir := filepath.Join(t.TempDir(), "dir")
1884 err := os.Symlink(dir, dir)
1885 if err != nil {
1886 t.Fatal(err)
1887 }
1888 _, err = filepath.EvalSymlinks(dir)
1889 if err == nil {
1890 t.Fatal("expected error, got nil")
1891 }
1892 }
1893
1894 func BenchmarkIsLocal(b *testing.B) {
1895 tests := islocaltests
1896 if runtime.GOOS == "windows" {
1897 tests = append(tests, winislocaltests...)
1898 }
1899 if runtime.GOOS == "plan9" {
1900 tests = append(tests, plan9islocaltests...)
1901 }
1902 for b.Loop() {
1903 for _, test := range tests {
1904 filepath.IsLocal(test.path)
1905 }
1906 }
1907 }
1908
View as plain text