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