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