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 t.Run(tt.desc, func(t *testing.T) {
940 var walked []string
941 err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
942 if err != nil {
943 return err
944 }
945 t.Logf("%#q: %v", path, info.Mode())
946 walked = append(walked, filepath.Clean(path))
947 return nil
948 })
949 if err != nil {
950 t.Fatal(err)
951 }
952
953 if !slices.Equal(walked, tt.want) {
954 t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
955 if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
956 t.Logf("(ignoring known bug on %v)", runtime.GOOS)
957 } else {
958 t.Fail()
959 }
960 }
961 })
962 }
963 }
964
965 var basetests = []PathTest{
966 {"", "."},
967 {".", "."},
968 {"/.", "."},
969 {"/", "/"},
970 {"////", "/"},
971 {"x/", "x"},
972 {"abc", "abc"},
973 {"abc/def", "def"},
974 {"a/b/.x", ".x"},
975 {"a/b/c.", "c."},
976 {"a/b/c.x", "c.x"},
977 }
978
979 var winbasetests = []PathTest{
980 {`c:\`, `\`},
981 {`c:.`, `.`},
982 {`c:\a\b`, `b`},
983 {`c:a\b`, `b`},
984 {`c:a\b\c`, `c`},
985 {`\\host\share\`, `\`},
986 {`\\host\share\a`, `a`},
987 {`\\host\share\a\b`, `b`},
988 }
989
990 func TestBase(t *testing.T) {
991 tests := basetests
992 if runtime.GOOS == "windows" {
993
994 for i := range tests {
995 tests[i].result = filepath.Clean(tests[i].result)
996 }
997
998 tests = append(tests, winbasetests...)
999 }
1000 for _, test := range tests {
1001 if s := filepath.Base(test.path); s != test.result {
1002 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
1003 }
1004 }
1005 }
1006
1007 var dirtests = []PathTest{
1008 {"", "."},
1009 {".", "."},
1010 {"/.", "/"},
1011 {"/", "/"},
1012 {"/foo", "/"},
1013 {"x/", "x"},
1014 {"abc", "."},
1015 {"abc/def", "abc"},
1016 {"a/b/.x", "a/b"},
1017 {"a/b/c.", "a/b"},
1018 {"a/b/c.x", "a/b"},
1019 }
1020
1021 var nonwindirtests = []PathTest{
1022 {"////", "/"},
1023 }
1024
1025 var windirtests = []PathTest{
1026 {`c:\`, `c:\`},
1027 {`c:.`, `c:.`},
1028 {`c:\a\b`, `c:\a`},
1029 {`c:a\b`, `c:a`},
1030 {`c:a\b\c`, `c:a\b`},
1031 {`\\host\share`, `\\host\share`},
1032 {`\\host\share\`, `\\host\share\`},
1033 {`\\host\share\a`, `\\host\share\`},
1034 {`\\host\share\a\b`, `\\host\share\a`},
1035 {`\\\\`, `\\\\`},
1036 }
1037
1038 func TestDir(t *testing.T) {
1039 tests := dirtests
1040 if runtime.GOOS == "windows" {
1041
1042 for i := range tests {
1043 tests[i].result = filepath.Clean(tests[i].result)
1044 }
1045
1046 tests = append(tests, windirtests...)
1047 } else {
1048 tests = append(tests, nonwindirtests...)
1049 }
1050 for _, test := range tests {
1051 if s := filepath.Dir(test.path); s != test.result {
1052 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
1053 }
1054 }
1055 }
1056
1057 type IsAbsTest struct {
1058 path string
1059 isAbs bool
1060 }
1061
1062 var isabstests = []IsAbsTest{
1063 {"", false},
1064 {"/", true},
1065 {"/usr/bin/gcc", true},
1066 {"..", false},
1067 {"/a/../bb", true},
1068 {".", false},
1069 {"./", false},
1070 {"lala", false},
1071 }
1072
1073 var winisabstests = []IsAbsTest{
1074 {`C:\`, true},
1075 {`c\`, false},
1076 {`c::`, false},
1077 {`c:`, false},
1078 {`/`, false},
1079 {`\`, false},
1080 {`\Windows`, false},
1081 {`c:a\b`, false},
1082 {`c:\a\b`, true},
1083 {`c:/a/b`, true},
1084 {`\\host\share`, true},
1085 {`\\host\share\`, true},
1086 {`\\host\share\foo`, true},
1087 {`//host/share/foo/bar`, true},
1088 {`\\?\a\b\c`, true},
1089 {`\??\a\b\c`, true},
1090 }
1091
1092 func TestIsAbs(t *testing.T) {
1093 var tests []IsAbsTest
1094 if runtime.GOOS == "windows" {
1095 tests = append(tests, winisabstests...)
1096
1097 for _, test := range isabstests {
1098 tests = append(tests, IsAbsTest{test.path, false})
1099 }
1100
1101 for _, test := range isabstests {
1102 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
1103 }
1104 } else {
1105 tests = isabstests
1106 }
1107
1108 for _, test := range tests {
1109 if r := filepath.IsAbs(test.path); r != test.isAbs {
1110 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
1111 }
1112 }
1113 }
1114
1115 type EvalSymlinksTest struct {
1116
1117 path, dest string
1118 }
1119
1120 var EvalSymlinksTestDirs = []EvalSymlinksTest{
1121 {"test", ""},
1122 {"test/dir", ""},
1123 {"test/dir/link3", "../../"},
1124 {"test/link1", "../test"},
1125 {"test/link2", "dir"},
1126 {"test/linkabs", "/"},
1127 {"test/link4", "../test2"},
1128 {"test2", "test/dir"},
1129
1130 {"src", ""},
1131 {"src/pool", ""},
1132 {"src/pool/test", ""},
1133 {"src/versions", ""},
1134 {"src/versions/current", "../../version"},
1135 {"src/versions/v1", ""},
1136 {"src/versions/v1/modules", ""},
1137 {"src/versions/v1/modules/test", "../../../pool/test"},
1138 {"version", "src/versions/v1"},
1139 }
1140
1141 var EvalSymlinksTests = []EvalSymlinksTest{
1142 {"test", "test"},
1143 {"test/dir", "test/dir"},
1144 {"test/dir/../..", "."},
1145 {"test/link1", "test"},
1146 {"test/link2", "test/dir"},
1147 {"test/link1/dir", "test/dir"},
1148 {"test/link2/..", "test"},
1149 {"test/dir/link3", "."},
1150 {"test/link2/link3/test", "test"},
1151 {"test/linkabs", "/"},
1152 {"test/link4/..", "test"},
1153 {"src/versions/current/modules/test", "src/pool/test"},
1154 }
1155
1156
1157
1158 func simpleJoin(dir, path string) string {
1159 return dir + string(filepath.Separator) + path
1160 }
1161
1162 func testEvalSymlinks(t *testing.T, path, want string) {
1163 have, err := filepath.EvalSymlinks(path)
1164 if err != nil {
1165 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1166 return
1167 }
1168 if filepath.Clean(have) != filepath.Clean(want) {
1169 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1170 }
1171 }
1172
1173 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1174 t.Chdir(wd)
1175 have, err := filepath.EvalSymlinks(path)
1176 if err != nil {
1177 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1178 return
1179 }
1180 if filepath.Clean(have) != filepath.Clean(want) {
1181 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1182 }
1183 }
1184
1185 func TestEvalSymlinks(t *testing.T) {
1186 testenv.MustHaveSymlink(t)
1187
1188 tmpDir := t.TempDir()
1189
1190
1191
1192 var err error
1193 tmpDir, err = filepath.EvalSymlinks(tmpDir)
1194 if err != nil {
1195 t.Fatal("eval symlink for tmp dir:", err)
1196 }
1197
1198
1199 for _, d := range EvalSymlinksTestDirs {
1200 var err error
1201 path := simpleJoin(tmpDir, d.path)
1202 if d.dest == "" {
1203 err = os.Mkdir(path, 0755)
1204 } else {
1205 err = os.Symlink(d.dest, path)
1206 }
1207 if err != nil {
1208 t.Fatal(err)
1209 }
1210 }
1211
1212
1213 for _, test := range EvalSymlinksTests {
1214 path := simpleJoin(tmpDir, test.path)
1215
1216 dest := simpleJoin(tmpDir, test.dest)
1217 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1218 dest = test.dest
1219 }
1220 testEvalSymlinks(t, path, dest)
1221
1222
1223 testEvalSymlinksAfterChdir(t, path, ".", ".")
1224
1225
1226 if runtime.GOOS == "windows" {
1227 volDot := filepath.VolumeName(tmpDir) + "."
1228 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1229 }
1230
1231
1232 dotdotPath := simpleJoin("..", test.dest)
1233 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1234 dotdotPath = test.dest
1235 }
1236 testEvalSymlinksAfterChdir(t,
1237 simpleJoin(tmpDir, "test"),
1238 simpleJoin("..", test.path),
1239 dotdotPath)
1240
1241
1242 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1243 }
1244 }
1245
1246 func TestEvalSymlinksIsNotExist(t *testing.T) {
1247 testenv.MustHaveSymlink(t)
1248 t.Chdir(t.TempDir())
1249
1250 _, err := filepath.EvalSymlinks("notexist")
1251 if !os.IsNotExist(err) {
1252 t.Errorf("expected the file is not found, got %v\n", err)
1253 }
1254
1255 err = os.Symlink("notexist", "link")
1256 if err != nil {
1257 t.Fatal(err)
1258 }
1259 defer os.Remove("link")
1260
1261 _, err = filepath.EvalSymlinks("link")
1262 if !os.IsNotExist(err) {
1263 t.Errorf("expected the file is not found, got %v\n", err)
1264 }
1265 }
1266
1267 func TestIssue13582(t *testing.T) {
1268 testenv.MustHaveSymlink(t)
1269
1270 tmpDir := t.TempDir()
1271
1272 dir := filepath.Join(tmpDir, "dir")
1273 err := os.Mkdir(dir, 0755)
1274 if err != nil {
1275 t.Fatal(err)
1276 }
1277 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1278 err = os.Symlink(dir, linkToDir)
1279 if err != nil {
1280 t.Fatal(err)
1281 }
1282 file := filepath.Join(linkToDir, "file")
1283 err = os.WriteFile(file, nil, 0644)
1284 if err != nil {
1285 t.Fatal(err)
1286 }
1287 link1 := filepath.Join(linkToDir, "link1")
1288 err = os.Symlink(file, link1)
1289 if err != nil {
1290 t.Fatal(err)
1291 }
1292 link2 := filepath.Join(linkToDir, "link2")
1293 err = os.Symlink(link1, link2)
1294 if err != nil {
1295 t.Fatal(err)
1296 }
1297
1298
1299 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1300 if err != nil {
1301 t.Fatal(err)
1302 }
1303 realDir := filepath.Join(realTmpDir, "dir")
1304 realFile := filepath.Join(realDir, "file")
1305
1306 tests := []struct {
1307 path, want string
1308 }{
1309 {dir, realDir},
1310 {linkToDir, realDir},
1311 {file, realFile},
1312 {link1, realFile},
1313 {link2, realFile},
1314 }
1315 for i, test := range tests {
1316 have, err := filepath.EvalSymlinks(test.path)
1317 if err != nil {
1318 t.Fatal(err)
1319 }
1320 if have != test.want {
1321 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1322 }
1323 }
1324 }
1325
1326
1327 func TestRelativeSymlinkToAbsolute(t *testing.T) {
1328 testenv.MustHaveSymlink(t)
1329
1330
1331 tmpDir := t.TempDir()
1332 t.Chdir(tmpDir)
1333
1334
1335
1336
1337
1338 if err := os.Symlink(tmpDir, "link"); err != nil {
1339 t.Fatal(err)
1340 }
1341 t.Logf(`os.Symlink(%q, "link")`, tmpDir)
1342
1343 p, err := filepath.EvalSymlinks("link")
1344 if err != nil {
1345 t.Fatalf(`EvalSymlinks("link"): %v`, err)
1346 }
1347 want, err := filepath.EvalSymlinks(tmpDir)
1348 if err != nil {
1349 t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
1350 }
1351 if p != want {
1352 t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
1353 }
1354 t.Logf(`EvalSymlinks("link") = %q`, p)
1355 }
1356
1357
1358
1359 var absTestDirs = []string{
1360 "a",
1361 "a/b",
1362 "a/b/c",
1363 }
1364
1365
1366
1367
1368 var absTests = []string{
1369 ".",
1370 "b",
1371 "b/",
1372 "../a",
1373 "../a/b",
1374 "../a/b/./c/../../.././a",
1375 "../a/b/./c/../../.././a/",
1376 "$",
1377 "$/.",
1378 "$/a/../a/b",
1379 "$/a/b/c/../../.././a",
1380 "$/a/b/c/../../.././a/",
1381 }
1382
1383 func TestAbs(t *testing.T) {
1384 root := t.TempDir()
1385 t.Chdir(root)
1386
1387 for _, dir := range absTestDirs {
1388 err := os.Mkdir(dir, 0777)
1389 if err != nil {
1390 t.Fatal("Mkdir failed: ", err)
1391 }
1392 }
1393
1394
1395
1396 tests := absTests
1397 if runtime.GOOS == "windows" {
1398 vol := filepath.VolumeName(root)
1399 var extra []string
1400 for _, path := range absTests {
1401 if strings.Contains(path, "$") {
1402 continue
1403 }
1404 path = vol + path
1405 extra = append(extra, path)
1406 }
1407 tests = append(slices.Clip(tests), extra...)
1408 }
1409
1410 err := os.Chdir(absTestDirs[0])
1411 if err != nil {
1412 t.Fatal("chdir failed: ", err)
1413 }
1414
1415 for _, path := range tests {
1416 path = strings.ReplaceAll(path, "$", root)
1417 info, err := os.Stat(path)
1418 if err != nil {
1419 t.Errorf("%s: %s", path, err)
1420 continue
1421 }
1422
1423 abspath, err := filepath.Abs(path)
1424 if err != nil {
1425 t.Errorf("Abs(%q) error: %v", path, err)
1426 continue
1427 }
1428 absinfo, err := os.Stat(abspath)
1429 if err != nil || !os.SameFile(absinfo, info) {
1430 t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1431 }
1432 if !filepath.IsAbs(abspath) {
1433 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1434 }
1435 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1436 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1437 }
1438 }
1439 }
1440
1441
1442
1443
1444 func TestAbsEmptyString(t *testing.T) {
1445 root := t.TempDir()
1446 t.Chdir(root)
1447
1448 info, err := os.Stat(root)
1449 if err != nil {
1450 t.Fatalf("%s: %s", root, err)
1451 }
1452
1453 abspath, err := filepath.Abs("")
1454 if err != nil {
1455 t.Fatalf(`Abs("") error: %v`, err)
1456 }
1457 absinfo, err := os.Stat(abspath)
1458 if err != nil || !os.SameFile(absinfo, info) {
1459 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1460 }
1461 if !filepath.IsAbs(abspath) {
1462 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1463 }
1464 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1465 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1466 }
1467 }
1468
1469 type RelTests struct {
1470 root, path, want string
1471 }
1472
1473 var reltests = []RelTests{
1474 {"a/b", "a/b", "."},
1475 {"a/b/.", "a/b", "."},
1476 {"a/b", "a/b/.", "."},
1477 {"./a/b", "a/b", "."},
1478 {"a/b", "./a/b", "."},
1479 {"ab/cd", "ab/cde", "../cde"},
1480 {"ab/cd", "ab/c", "../c"},
1481 {"a/b", "a/b/c/d", "c/d"},
1482 {"a/b", "a/b/../c", "../c"},
1483 {"a/b/../c", "a/b", "../b"},
1484 {"a/b/c", "a/c/d", "../../c/d"},
1485 {"a/b", "c/d", "../../c/d"},
1486 {"a/b/c/d", "a/b", "../.."},
1487 {"a/b/c/d", "a/b/", "../.."},
1488 {"a/b/c/d/", "a/b", "../.."},
1489 {"a/b/c/d/", "a/b/", "../.."},
1490 {"../../a/b", "../../a/b/c/d", "c/d"},
1491 {"/a/b", "/a/b", "."},
1492 {"/a/b/.", "/a/b", "."},
1493 {"/a/b", "/a/b/.", "."},
1494 {"/ab/cd", "/ab/cde", "../cde"},
1495 {"/ab/cd", "/ab/c", "../c"},
1496 {"/a/b", "/a/b/c/d", "c/d"},
1497 {"/a/b", "/a/b/../c", "../c"},
1498 {"/a/b/../c", "/a/b", "../b"},
1499 {"/a/b/c", "/a/c/d", "../../c/d"},
1500 {"/a/b", "/c/d", "../../c/d"},
1501 {"/a/b/c/d", "/a/b", "../.."},
1502 {"/a/b/c/d", "/a/b/", "../.."},
1503 {"/a/b/c/d/", "/a/b", "../.."},
1504 {"/a/b/c/d/", "/a/b/", "../.."},
1505 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1506 {".", "a/b", "a/b"},
1507 {".", "..", ".."},
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
1893 func BenchmarkIsLocal(b *testing.B) {
1894 tests := islocaltests
1895 if runtime.GOOS == "windows" {
1896 tests = append(tests, winislocaltests...)
1897 }
1898 if runtime.GOOS == "plan9" {
1899 tests = append(tests, plan9islocaltests...)
1900 }
1901 for b.Loop() {
1902 for _, test := range tests {
1903 filepath.IsLocal(test.path)
1904 }
1905 }
1906 }
1907
View as plain text