1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
23 type PathTest struct {
27 var cleantests = []PathTest{
30 {"abc/def", "abc/def"},
35 {"../../abc", "../../abc"},
39 // Empty is current dir
42 // Remove trailing slash
44 {"abc/def/", "abc/def"},
51 // Remove doubled slash
52 {"abc//def//ghi", "abc/def/ghi"},
56 {"abc/./def", "abc/def"},
57 {"/./abc/def", "/abc/def"},
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"},
70 {"a/../b:/../../c", `../c`},
73 {"abc/./../def", "def"},
74 {"abc//./../def", "def"},
75 {"abc/../../././../def", "../../def"},
78 var nonwincleantests = []PathTest{
79 // Remove leading doubled slash
85 var wincleantests = []PathTest{
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`},
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`},
107 {`.\c:\foo`, `.\c:\foo`},
108 {`.\c:foo`, `.\c:foo`},
110 {`///abc`, `\\\abc`},
111 {`//abc//`, `\\abc\\`},
112 {`\\?\C:\`, `\\?\C:\`},
113 {`\\?\C:\a`, `\\?\C:\a`},
115 // Don't allow cleaning to move an element with a colon to the start of the path.
118 {`a/../c:/a`, `.\c:\a`},
119 {`a/../../c:`, `..\c:`},
120 {`foo:bar`, `foo:bar`},
122 // Don't allow cleaning to create a Root Local Device path like \??\a.
123 {`/a/../??/a`, `\.\??\a`},
126 func TestClean(t *testing.T) {
128 if runtime.GOOS == "windows" {
129 for i := range tests {
130 tests[i].result = filepath.FromSlash(tests[i].result)
132 tests = append(tests, wincleantests...)
134 tests = append(tests, nonwincleantests...)
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)
140 if s := filepath.Clean(test.result); s != test.result {
141 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
146 t.Skip("skipping malloc count in short mode")
148 if runtime.GOMAXPROCS(0) > 1 {
149 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
153 for _, test := range tests {
154 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
156 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
161 type IsLocalTest struct {
166 var islocaltests = []IsLocalTest{
179 {`a/../b:/../../c`, false},
182 var winislocaltests = []IsLocalTest{
188 {"a/nul : a", false},
218 {`dollar$`, true}, // not a special file name
221 var plan9islocaltests = []IsLocalTest{
225 func TestIsLocal(t *testing.T) {
226 tests := islocaltests
227 if runtime.GOOS == "windows" {
228 tests = append(tests, winislocaltests...)
230 if runtime.GOOS == "plan9" {
231 tests = append(tests, plan9islocaltests...)
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)
240 const sep = filepath.Separator
242 var slashtests = []PathTest{
245 {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
246 {"a//b", string([]byte{'a', sep, sep, 'b'})},
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)
254 if s := filepath.ToSlash(test.result); s != test.path {
255 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
260 type SplitListTest struct {
265 const lsep = filepath.ListSeparator
267 var splitlisttests = []SplitListTest{
269 {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
270 {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
273 var winsplitlisttests = []SplitListTest{
275 {`"a"`, []string{`a`}},
278 {`";"`, []string{`;`}},
279 {`"a;b"`, []string{`a;b`}},
280 {`";";`, []string{`;`, ``}},
281 {`;";"`, []string{``, `;`}},
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`}},
295 func TestSplitList(t *testing.T) {
296 tests := splitlisttests
297 if runtime.GOOS == "windows" {
298 tests = append(tests, winsplitlisttests...)
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)
307 type SplitTest struct {
308 path, dir, file string
311 var unixsplittests = []SplitTest{
313 {"a/b/", "a/b/", ""},
319 var winsplittests = []SplitTest{
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`},
332 func TestSplit(t *testing.T) {
333 var splittests []SplitTest
334 splittests = unixsplittests
335 if runtime.GOOS == "windows" {
336 splittests = append(splittests, winsplittests...)
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)
345 type JoinTest struct {
350 var jointests = []JoinTest{
356 {[]string{"/"}, "/"},
357 {[]string{"a"}, "a"},
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{"", ""}, ""},
374 {[]string{"/", "a", "b"}, "/a/b"},
377 var nonwinjointests = []JoinTest{
378 {[]string{"//", "a"}, "/a"},
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`},
415 func TestJoin(t *testing.T) {
416 if runtime.GOOS == "windows" {
417 jointests = append(jointests, winjointests...)
419 jointests = append(jointests, nonwinjointests...)
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)
429 type ExtTest struct {
433 var exttests = []ExtTest{
435 {"path.pb.go", ".go"},
437 {"a.dir/b.go", ".go"},
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)
451 entries []*Node // nil if the entry is a file
481 func walkTree(n *Node, path string, f func(path string, n *Node)) {
483 for _, e := range n.entries {
484 walkTree(e, filepath.Join(path, e.name), f)
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)
493 t.Errorf("makeTree: %v", err)
503 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
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)
514 // Assumes that each node name is unique. Good enough for a test.
515 // If clear is true, any incoming error is cleared before return. The errors
516 // are always accumulated, though.
517 func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
519 walkTree(tree, tree.name, func(path string, n *Node) {
525 *errors = append(*errors, err)
534 // chdir changes the current working directory to the named directory,
535 // and then restore the original working directory at the end of the test.
536 func chdir(t *testing.T, dir string) {
537 olddir, err := os.Getwd()
539 t.Fatalf("getwd %s: %v", dir, err)
541 if err := os.Chdir(dir); err != nil {
542 t.Fatalf("chdir %s: %v", dir, err)
546 if err := os.Chdir(olddir); err != nil {
547 t.Errorf("restore original working directory %s: %v", olddir, err)
553 func chtmpdir(t *testing.T) (restore func()) {
554 oldwd, err := os.Getwd()
556 t.Fatalf("chtmpdir: %v", err)
558 d, err := os.MkdirTemp("", "test")
560 t.Fatalf("chtmpdir: %v", err)
562 if err := os.Chdir(d); err != nil {
563 t.Fatalf("chtmpdir: %v", err)
566 if err := os.Chdir(oldwd); err != nil {
567 t.Fatalf("chtmpdir: %v", err)
573 // tempDirCanonical returns a temporary directory for the test to use, ensuring
574 // that the returned path does not contain symlinks.
575 func tempDirCanonical(t *testing.T) string {
578 cdir, err := filepath.EvalSymlinks(dir)
580 t.Errorf("tempDirCanonical: %v", err)
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, fs.FileInfoToDirEntry(info), err)
595 func TestWalkDir(t *testing.T) {
596 testWalk(t, filepath.WalkDir, 2)
599 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
600 if runtime.GOOS == "ios" {
601 restore := chtmpdir(t)
605 tmpDir := t.TempDir()
607 origDir, err := os.Getwd()
609 t.Fatal("finding working dir:", err)
611 if err = os.Chdir(tmpDir); err != nil {
612 t.Fatal("entering temp dir:", err)
614 defer os.Chdir(origDir)
617 errors := make([]error, 0, 10)
619 markFn := func(path string, d fs.DirEntry, err error) error {
620 return mark(d, err, &errors, clear)
623 err = walk(tree.name, markFn)
625 t.Fatalf("no error expected, found: %s", err)
627 if len(errors) != 0 {
628 t.Fatalf("unexpected errors: %s", errors)
633 t.Run("PermErr", func(t *testing.T) {
634 // Test permission errors. Only possible if we're not root
635 // and only on some file systems (AFS, FAT). To avoid errors during
636 // all.bash on those file systems, skip during go test -short.
637 // Chmod is not supported on wasip1.
638 if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
639 t.Skip("skipping on " + runtime.GOOS)
641 if os.Getuid() == 0 {
642 t.Skip("skipping as root")
645 t.Skip("skipping in short mode")
648 // introduce 2 errors: chmod top-level directories to 0
649 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
650 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
652 // 3) capture errors, expect two.
653 // mark respective subtrees manually
654 markTree(tree.entries[1])
655 markTree(tree.entries[3])
656 // correct double-marking of directory itself
657 tree.entries[1].mark -= errVisit
658 tree.entries[3].mark -= errVisit
659 err := walk(tree.name, markFn)
661 t.Fatalf("expected no error return from Walk, got %s", err)
663 if len(errors) != 2 {
664 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
666 // the inaccessible subtrees were marked manually
670 // 4) capture errors, stop after first error.
671 // mark respective subtrees manually
672 markTree(tree.entries[1])
673 markTree(tree.entries[3])
674 // correct double-marking of directory itself
675 tree.entries[1].mark -= errVisit
676 tree.entries[3].mark -= errVisit
677 clear = false // error will stop processing
678 err = walk(tree.name, markFn)
680 t.Fatalf("expected error return from Walk")
682 if len(errors) != 1 {
683 t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
685 // the inaccessible subtrees were marked manually
689 // restore permissions
690 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
691 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
695 func touch(t *testing.T, name string) {
696 f, err := os.Create(name)
700 if err := f.Close(); err != nil {
705 func TestWalkSkipDirOnFile(t *testing.T) {
708 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
711 touch(t, filepath.Join(td, "dir/foo1"))
712 touch(t, filepath.Join(td, "dir/foo2"))
715 walker := func(path string) error {
716 if strings.HasSuffix(path, "foo2") {
719 if strings.HasSuffix(path, "foo1") {
720 return filepath.SkipDir
724 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
725 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
727 check := func(t *testing.T, walk func(root string) error, root string) {
735 t.Errorf("SkipDir on file foo1 did not block processing of foo2")
739 t.Run("Walk", func(t *testing.T) {
740 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
742 check(t, Walk, filepath.Join(td, "dir"))
744 t.Run("WalkDir", func(t *testing.T) {
745 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
746 check(t, WalkDir, td)
747 check(t, WalkDir, filepath.Join(td, "dir"))
751 func TestWalkSkipAllOnFile(t *testing.T) {
754 if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
757 if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
761 touch(t, filepath.Join(td, "dir", "foo1"))
762 touch(t, filepath.Join(td, "dir", "foo2"))
763 touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
764 touch(t, filepath.Join(td, "dir", "foo4"))
765 touch(t, filepath.Join(td, "dir2", "bar"))
766 touch(t, filepath.Join(td, "last"))
768 remainingWereSkipped := true
769 walker := func(path string) error {
770 if strings.HasSuffix(path, "foo2") {
771 return filepath.SkipAll
774 if strings.HasSuffix(path, "foo3") ||
775 strings.HasSuffix(path, "foo4") ||
776 strings.HasSuffix(path, "bar") ||
777 strings.HasSuffix(path, "last") {
778 remainingWereSkipped = false
783 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
784 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
786 check := func(t *testing.T, walk func(root string) error, root string) {
788 remainingWereSkipped = true
789 if err := walk(root); err != nil {
792 if !remainingWereSkipped {
793 t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
797 t.Run("Walk", func(t *testing.T) {
798 Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
800 check(t, Walk, filepath.Join(td, "dir"))
802 t.Run("WalkDir", func(t *testing.T) {
803 WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
804 check(t, WalkDir, td)
805 check(t, WalkDir, filepath.Join(td, "dir"))
809 func TestWalkFileError(t *testing.T) {
812 touch(t, filepath.Join(td, "foo"))
813 touch(t, filepath.Join(td, "bar"))
814 dir := filepath.Join(td, "dir")
815 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
818 touch(t, filepath.Join(dir, "baz"))
819 touch(t, filepath.Join(dir, "stat-error"))
821 *filepath.LstatP = os.Lstat
823 statErr := errors.New("some stat error")
824 *filepath.LstatP = func(path string) (fs.FileInfo, error) {
825 if strings.HasSuffix(path, "stat-error") {
828 return os.Lstat(path)
830 got := map[string]error{}
831 err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
832 rel, _ := filepath.Rel(td, path)
833 got[filepath.ToSlash(rel)] = err
837 t.Errorf("Walk error: %v", err)
839 want := map[string]error{
845 "dir/stat-error": statErr,
847 if !reflect.DeepEqual(got, want) {
848 t.Errorf("Walked %#v; want %#v", got, want)
852 func TestWalkSymlinkRoot(t *testing.T) {
853 testenv.MustHaveSymlink(t)
856 dir := filepath.Join(td, "dir")
857 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
860 touch(t, filepath.Join(dir, "foo"))
862 link := filepath.Join(td, "link")
863 if err := os.Symlink("dir", link); err != nil {
867 abslink := filepath.Join(td, "abslink")
868 if err := os.Symlink(dir, abslink); err != nil {
872 linklink := filepath.Join(td, "linklink")
873 if err := os.Symlink("link", linklink); err != nil {
877 // Per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12:
878 // “A pathname that contains at least one non- <slash> character and that ends
879 // with one or more trailing <slash> characters shall not be resolved
880 // successfully unless the last pathname component before the trailing <slash>
881 // characters names an existing directory [...].”
883 // Since Walk does not traverse symlinks itself, its behavior should depend on
884 // whether the path passed to Walk ends in a slash: if it does not end in a slash,
885 // Walk should report the symlink itself (since it is the last pathname component);
886 // but if it does end in a slash, Walk should walk the directory to which the symlink
887 // refers (since it must be fully resolved before walking).
888 for _, tt := range []struct {
897 want: []string{link},
901 root: link + string(filepath.Separator),
902 want: []string{link, filepath.Join(link, "foo")},
905 desc: "abs no slash",
907 want: []string{abslink},
910 desc: "abs with slash",
911 root: abslink + string(filepath.Separator),
912 want: []string{abslink, filepath.Join(abslink, "foo")},
915 desc: "double link no slash",
917 want: []string{linklink},
920 desc: "double link with slash",
921 root: linklink + string(filepath.Separator),
922 want: []string{linklink, filepath.Join(linklink, "foo")},
923 buggyGOOS: []string{"darwin", "ios"}, // https://go.dev/issue/59586
927 t.Run(tt.desc, func(t *testing.T) {
929 err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
933 t.Logf("%#q: %v", path, info.Mode())
934 walked = append(walked, filepath.Clean(path))
941 if !reflect.DeepEqual(walked, tt.want) {
942 t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
943 if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
944 t.Logf("(ignoring known bug on %v)", runtime.GOOS)
953 var basetests = []PathTest{
967 var winbasetests = []PathTest{
973 {`\\host\share\`, `\`},
974 {`\\host\share\a`, `a`},
975 {`\\host\share\a\b`, `b`},
978 func TestBase(t *testing.T) {
980 if runtime.GOOS == "windows" {
981 // make unix tests work on windows
982 for i := range tests {
983 tests[i].result = filepath.Clean(tests[i].result)
985 // add windows specific tests
986 tests = append(tests, winbasetests...)
988 for _, test := range tests {
989 if s := filepath.Base(test.path); s != test.result {
990 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
995 var dirtests = []PathTest{
1009 var nonwindirtests = []PathTest{
1013 var windirtests = []PathTest{
1018 {`c:a\b\c`, `c:a\b`},
1019 {`\\host\share`, `\\host\share`},
1020 {`\\host\share\`, `\\host\share\`},
1021 {`\\host\share\a`, `\\host\share\`},
1022 {`\\host\share\a\b`, `\\host\share\a`},
1026 func TestDir(t *testing.T) {
1028 if runtime.GOOS == "windows" {
1029 // make unix tests work on windows
1030 for i := range tests {
1031 tests[i].result = filepath.Clean(tests[i].result)
1033 // add windows specific tests
1034 tests = append(tests, windirtests...)
1036 tests = append(tests, nonwindirtests...)
1038 for _, test := range tests {
1039 if s := filepath.Dir(test.path); s != test.result {
1040 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
1045 type IsAbsTest struct {
1050 var isabstests = []IsAbsTest{
1053 {"/usr/bin/gcc", true},
1061 var winisabstests = []IsAbsTest{
1068 {`\Windows`, false},
1072 {`\\host\share`, true},
1073 {`\\host\share\`, true},
1074 {`\\host\share\foo`, true},
1075 {`//host/share/foo/bar`, true},
1076 {`\\?\a\b\c`, true},
1077 {`\??\a\b\c`, true},
1080 func TestIsAbs(t *testing.T) {
1081 var tests []IsAbsTest
1082 if runtime.GOOS == "windows" {
1083 tests = append(tests, winisabstests...)
1084 // All non-windows tests should fail, because they have no volume letter.
1085 for _, test := range isabstests {
1086 tests = append(tests, IsAbsTest{test.path, false})
1088 // All non-windows test should work as intended if prefixed with volume letter.
1089 for _, test := range isabstests {
1090 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
1096 for _, test := range tests {
1097 if r := filepath.IsAbs(test.path); r != test.isAbs {
1098 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
1103 type EvalSymlinksTest struct {
1104 // If dest is empty, the path is created; otherwise the dest is symlinked to the path.
1108 var EvalSymlinksTestDirs = []EvalSymlinksTest{
1111 {"test/dir/link3", "../../"},
1112 {"test/link1", "../test"},
1113 {"test/link2", "dir"},
1114 {"test/linkabs", "/"},
1115 {"test/link4", "../test2"},
1116 {"test2", "test/dir"},
1120 {"src/pool/test", ""},
1121 {"src/versions", ""},
1122 {"src/versions/current", "../../version"},
1123 {"src/versions/v1", ""},
1124 {"src/versions/v1/modules", ""},
1125 {"src/versions/v1/modules/test", "../../../pool/test"},
1126 {"version", "src/versions/v1"},
1129 var EvalSymlinksTests = []EvalSymlinksTest{
1131 {"test/dir", "test/dir"},
1132 {"test/dir/../..", "."},
1133 {"test/link1", "test"},
1134 {"test/link2", "test/dir"},
1135 {"test/link1/dir", "test/dir"},
1136 {"test/link2/..", "test"},
1137 {"test/dir/link3", "."},
1138 {"test/link2/link3/test", "test"},
1139 {"test/linkabs", "/"},
1140 {"test/link4/..", "test"},
1141 {"src/versions/current/modules/test", "src/pool/test"},
1144 // simpleJoin builds a file name from the directory and path.
1145 // It does not use Join because we don't want ".." to be evaluated.
1146 func simpleJoin(dir, path string) string {
1147 return dir + string(filepath.Separator) + path
1150 func testEvalSymlinks(t *testing.T, path, want string) {
1151 have, err := filepath.EvalSymlinks(path)
1153 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1156 if filepath.Clean(have) != filepath.Clean(want) {
1157 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1161 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1162 cwd, err := os.Getwd()
1167 err := os.Chdir(cwd)
1178 have, err := filepath.EvalSymlinks(path)
1180 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1183 if filepath.Clean(have) != filepath.Clean(want) {
1184 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1188 func TestEvalSymlinks(t *testing.T) {
1189 testenv.MustHaveSymlink(t)
1191 tmpDir := t.TempDir()
1193 // /tmp may itself be a symlink! Avoid the confusion, although
1194 // it means trusting the thing we're testing.
1196 tmpDir, err = filepath.EvalSymlinks(tmpDir)
1198 t.Fatal("eval symlink for tmp dir:", err)
1201 // Create the symlink farm using relative paths.
1202 for _, d := range EvalSymlinksTestDirs {
1204 path := simpleJoin(tmpDir, d.path)
1206 err = os.Mkdir(path, 0755)
1208 err = os.Symlink(d.dest, path)
1215 // Evaluate the symlink farm.
1216 for _, test := range EvalSymlinksTests {
1217 path := simpleJoin(tmpDir, test.path)
1219 dest := simpleJoin(tmpDir, test.dest)
1220 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1223 testEvalSymlinks(t, path, dest)
1225 // test EvalSymlinks(".")
1226 testEvalSymlinksAfterChdir(t, path, ".", ".")
1228 // test EvalSymlinks("C:.") on Windows
1229 if runtime.GOOS == "windows" {
1230 volDot := filepath.VolumeName(tmpDir) + "."
1231 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1234 // test EvalSymlinks(".."+path)
1235 dotdotPath := simpleJoin("..", test.dest)
1236 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1237 dotdotPath = test.dest
1239 testEvalSymlinksAfterChdir(t,
1240 simpleJoin(tmpDir, "test"),
1241 simpleJoin("..", test.path),
1244 // test EvalSymlinks(p) where p is relative path
1245 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1249 func TestEvalSymlinksIsNotExist(t *testing.T) {
1250 testenv.MustHaveSymlink(t)
1254 _, err := filepath.EvalSymlinks("notexist")
1255 if !os.IsNotExist(err) {
1256 t.Errorf("expected the file is not found, got %v\n", err)
1259 err = os.Symlink("notexist", "link")
1263 defer os.Remove("link")
1265 _, err = filepath.EvalSymlinks("link")
1266 if !os.IsNotExist(err) {
1267 t.Errorf("expected the file is not found, got %v\n", err)
1271 func TestIssue13582(t *testing.T) {
1272 testenv.MustHaveSymlink(t)
1274 tmpDir := t.TempDir()
1276 dir := filepath.Join(tmpDir, "dir")
1277 err := os.Mkdir(dir, 0755)
1281 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1282 err = os.Symlink(dir, linkToDir)
1286 file := filepath.Join(linkToDir, "file")
1287 err = os.WriteFile(file, nil, 0644)
1291 link1 := filepath.Join(linkToDir, "link1")
1292 err = os.Symlink(file, link1)
1296 link2 := filepath.Join(linkToDir, "link2")
1297 err = os.Symlink(link1, link2)
1302 // /tmp may itself be a symlink!
1303 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1307 realDir := filepath.Join(realTmpDir, "dir")
1308 realFile := filepath.Join(realDir, "file")
1314 {linkToDir, realDir},
1319 for i, test := range tests {
1320 have, err := filepath.EvalSymlinks(test.path)
1324 if have != test.want {
1325 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1331 func TestRelativeSymlinkToAbsolute(t *testing.T) {
1332 testenv.MustHaveSymlink(t)
1333 // Not parallel: uses os.Chdir.
1335 tmpDir := t.TempDir()
1338 // Create "link" in the current working directory as a symlink to an arbitrary
1339 // absolute path. On macOS, this path is likely to begin with a symlink
1340 // itself: generally either in /var (symlinked to "private/var") or /tmp
1341 // (symlinked to "private/tmp").
1342 if err := os.Symlink(tmpDir, "link"); err != nil {
1345 t.Logf(`os.Symlink(%q, "link")`, tmpDir)
1347 p, err := filepath.EvalSymlinks("link")
1349 t.Fatalf(`EvalSymlinks("link"): %v`, err)
1351 want, err := filepath.EvalSymlinks(tmpDir)
1353 t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
1356 t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
1358 t.Logf(`EvalSymlinks("link") = %q`, p)
1361 // Test directories relative to temporary directory.
1362 // The tests are run in absTestDirs[0].
1363 var absTestDirs = []string{
1369 // Test paths relative to temporary directory. $ expands to the directory.
1370 // The tests are run in absTestDirs[0].
1371 // We create absTestDirs first.
1372 var absTests = []string{
1378 "../a/b/./c/../../.././a",
1379 "../a/b/./c/../../.././a/",
1383 "$/a/b/c/../../.././a",
1384 "$/a/b/c/../../.././a/",
1387 func TestAbs(t *testing.T) {
1389 wd, err := os.Getwd()
1391 t.Fatal("getwd failed: ", err)
1393 err = os.Chdir(root)
1395 t.Fatal("chdir failed: ", err)
1399 for _, dir := range absTestDirs {
1400 err = os.Mkdir(dir, 0777)
1402 t.Fatal("Mkdir failed: ", err)
1406 if runtime.GOOS == "windows" {
1407 vol := filepath.VolumeName(root)
1409 for _, path := range absTests {
1410 if strings.Contains(path, "$") {
1414 extra = append(extra, path)
1416 absTests = append(absTests, extra...)
1419 err = os.Chdir(absTestDirs[0])
1421 t.Fatal("chdir failed: ", err)
1424 for _, path := range absTests {
1425 path = strings.ReplaceAll(path, "$", root)
1426 info, err := os.Stat(path)
1428 t.Errorf("%s: %s", path, err)
1432 abspath, err := filepath.Abs(path)
1434 t.Errorf("Abs(%q) error: %v", path, err)
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)
1441 if !filepath.IsAbs(abspath) {
1442 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1444 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1445 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1450 // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
1451 // We test it separately from all other absTests because the empty string is not
1452 // a valid path, so it can't be used with os.Stat.
1453 func TestAbsEmptyString(t *testing.T) {
1456 wd, err := os.Getwd()
1458 t.Fatal("getwd failed: ", err)
1460 err = os.Chdir(root)
1462 t.Fatal("chdir failed: ", err)
1466 info, err := os.Stat(root)
1468 t.Fatalf("%s: %s", root, err)
1471 abspath, err := filepath.Abs("")
1473 t.Fatalf(`Abs("") error: %v`, err)
1475 absinfo, err := os.Stat(abspath)
1476 if err != nil || !os.SameFile(absinfo, info) {
1477 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1479 if !filepath.IsAbs(abspath) {
1480 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1482 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1483 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1487 type RelTests struct {
1488 root, path, want string
1491 var reltests = []RelTests{
1492 {"a/b", "a/b", "."},
1493 {"a/b/.", "a/b", "."},
1494 {"a/b", "a/b/.", "."},
1495 {"./a/b", "a/b", "."},
1496 {"a/b", "./a/b", "."},
1497 {"ab/cd", "ab/cde", "../cde"},
1498 {"ab/cd", "ab/c", "../c"},
1499 {"a/b", "a/b/c/d", "c/d"},
1500 {"a/b", "a/b/../c", "../c"},
1501 {"a/b/../c", "a/b", "../b"},
1502 {"a/b/c", "a/c/d", "../../c/d"},
1503 {"a/b", "c/d", "../../c/d"},
1504 {"a/b/c/d", "a/b", "../.."},
1505 {"a/b/c/d", "a/b/", "../.."},
1506 {"a/b/c/d/", "a/b", "../.."},
1507 {"a/b/c/d/", "a/b/", "../.."},
1508 {"../../a/b", "../../a/b/c/d", "c/d"},
1509 {"/a/b", "/a/b", "."},
1510 {"/a/b/.", "/a/b", "."},
1511 {"/a/b", "/a/b/.", "."},
1512 {"/ab/cd", "/ab/cde", "../cde"},
1513 {"/ab/cd", "/ab/c", "../c"},
1514 {"/a/b", "/a/b/c/d", "c/d"},
1515 {"/a/b", "/a/b/../c", "../c"},
1516 {"/a/b/../c", "/a/b", "../b"},
1517 {"/a/b/c", "/a/c/d", "../../c/d"},
1518 {"/a/b", "/c/d", "../../c/d"},
1519 {"/a/b/c/d", "/a/b", "../.."},
1520 {"/a/b/c/d", "/a/b/", "../.."},
1521 {"/a/b/c/d/", "/a/b", "../.."},
1522 {"/a/b/c/d/", "/a/b/", "../.."},
1523 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1524 {".", "a/b", "a/b"},
1527 // can't do purely lexically
1530 {"../..", "..", "err"},
1535 var winreltests = []RelTests{
1536 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1537 {`C:\`, `D:\`, `err`},
1538 {`C:`, `D:`, `err`},
1539 {`C:\Projects`, `c:\projects\src`, `src`},
1540 {`C:\Projects`, `c:\projects`, `.`},
1541 {`C:\Projects\a\..`, `c:\projects`, `.`},
1542 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1545 func TestRel(t *testing.T) {
1546 tests := append([]RelTests{}, reltests...)
1547 if runtime.GOOS == "windows" {
1548 for i := range tests {
1549 tests[i].want = filepath.FromSlash(tests[i].want)
1551 tests = append(tests, winreltests...)
1553 for _, test := range tests {
1554 got, err := filepath.Rel(test.root, test.path)
1555 if test.want == "err" {
1557 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1562 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1564 if got != test.want {
1565 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1570 type VolumeNameTest struct {
1575 var volumenametests = []VolumeNameTest{
1576 {`c:/foo/bar`, `c:`},
1581 {`\\\host`, `\\\host`},
1582 {`\\\host\`, `\\\host`},
1583 {`\\\host\share`, `\\\host`},
1584 {`\\\host\\share`, `\\\host`},
1585 {`\\host`, `\\host`},
1586 {`//host`, `\\host`},
1587 {`\\host\`, `\\host\`},
1588 {`//host/`, `\\host\`},
1589 {`\\host\share`, `\\host\share`},
1590 {`//host/share`, `\\host\share`},
1591 {`\\host\share\`, `\\host\share`},
1592 {`//host/share/`, `\\host\share`},
1593 {`\\host\share\foo`, `\\host\share`},
1594 {`//host/share/foo`, `\\host\share`},
1595 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1596 {`//host/share//foo///bar////baz`, `\\host\share`},
1597 {`\\host\share\foo\..\bar`, `\\host\share`},
1598 {`//host/share/foo/../bar`, `\\host\share`},
1601 {`//./NUL`, `\\.\NUL`},
1604 {`//?/NUL`, `\\?\NUL`},
1607 {`/??/NUL`, `\??\NUL`},
1608 {`//./a/b`, `\\.\a`},
1609 {`//./C:`, `\\.\C:`},
1610 {`//./C:/`, `\\.\C:`},
1611 {`//./C:/a/b/c`, `\\.\C:`},
1612 {`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
1613 {`//./UNC/host`, `\\.\UNC\host`},
1614 {`//./UNC/host\`, `\\.\UNC\host\`},
1615 {`//./UNC`, `\\.\UNC`},
1616 {`//./UNC/`, `\\.\UNC\`},
1621 func TestVolumeName(t *testing.T) {
1622 if runtime.GOOS != "windows" {
1625 for _, v := range volumenametests {
1626 if vol := filepath.VolumeName(v.path); vol != v.vol {
1627 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1632 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1633 if runtime.GOOS != "windows" {
1638 t.Errorf("Current directory path %q is too short", wd)
1640 lp := strings.ToLower(wd)
1641 up := strings.ToUpper(wd)
1642 flp, err := filepath.EvalSymlinks(lp)
1644 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1646 fup, err := filepath.EvalSymlinks(up)
1648 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1651 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1655 func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
1656 if runtime.GOOS == "ios" {
1657 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1659 root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
1660 utf16 := filepath.Join(root, "utf16")
1661 utf8 := filepath.Join(root, "utf8")
1664 err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1672 return filepath.SkipDir
1675 t.Fatal("filepath.Walk out of order - utf8 before utf16")
1685 t.Fatalf("%q not seen", utf8)
1689 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1690 tmpdir := t.TempDir()
1692 wd, err := os.Getwd()
1698 err = os.Chdir(tmpdir)
1703 err = mklink(tmpdir, "link")
1708 var visited []string
1709 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1713 rel, err := filepath.Rel(tmpdir, path)
1717 visited = append(visited, rel)
1723 sort.Strings(visited)
1724 want := []string{".", "link"}
1725 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1726 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1730 func TestWalkSymlink(t *testing.T) {
1731 testenv.MustHaveSymlink(t)
1732 testWalkSymlink(t, os.Symlink)
1735 func TestIssue29372(t *testing.T) {
1736 tmpDir := t.TempDir()
1738 path := filepath.Join(tmpDir, "file.txt")
1739 err := os.WriteFile(path, nil, 0644)
1744 pathSeparator := string(filepath.Separator)
1746 path + strings.Repeat(pathSeparator, 1),
1747 path + strings.Repeat(pathSeparator, 2),
1748 path + strings.Repeat(pathSeparator, 1) + ".",
1749 path + strings.Repeat(pathSeparator, 2) + ".",
1750 path + strings.Repeat(pathSeparator, 1) + "..",
1751 path + strings.Repeat(pathSeparator, 2) + "..",
1754 for i, test := range tests {
1755 _, err = filepath.EvalSymlinks(test)
1756 if err != syscall.ENOTDIR {
1757 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1762 // Issue 30520 part 1.
1763 func TestEvalSymlinksAboveRoot(t *testing.T) {
1764 testenv.MustHaveSymlink(t)
1768 tmpDir := t.TempDir()
1770 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1775 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1778 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1781 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1785 // Count the number of ".." elements to get to the root directory.
1786 vol := filepath.VolumeName(evalTmpDir)
1787 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1789 for i := 0; i < c+2; i++ {
1790 dd = append(dd, "..")
1793 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1795 // Try different numbers of "..".
1796 for _, i := range []int{c, c + 1, c + 2} {
1797 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1798 resolved, err := filepath.EvalSymlinks(check)
1800 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1801 // On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
1802 testenv.SkipFlaky(t, 37910)
1804 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1805 case !strings.HasSuffix(resolved, wantSuffix):
1806 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1808 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1813 // Issue 30520 part 2.
1814 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1815 testenv.MustHaveSymlink(t)
1817 tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1821 defer os.RemoveAll(tmpDir)
1824 subdir := filepath.Join("a", "b")
1825 if err := os.MkdirAll(subdir, 0777); err != nil {
1828 if err := os.Symlink(subdir, "c"); err != nil {
1831 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1835 subdir = filepath.Join("d", "e", "f")
1836 if err := os.MkdirAll(subdir, 0777); err != nil {
1839 if err := os.Chdir(subdir); err != nil {
1843 check := filepath.Join("..", "..", "..", "c", "file")
1844 wantSuffix := filepath.Join("a", "b", "file")
1845 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1846 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1847 } else if !strings.HasSuffix(resolved, wantSuffix) {
1848 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1850 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1854 func TestIssue51617(t *testing.T) {
1856 for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
1857 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
1861 bad := filepath.Join(dir, "a", "bad")
1862 if err := os.Chmod(bad, 0); err != nil {
1865 defer os.Chmod(bad, 0700) // avoid errors on cleanup
1867 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1869 return filepath.SkipDir
1872 rel, err := filepath.Rel(dir, path)
1876 saw = append(saw, rel)
1883 want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
1884 if !reflect.DeepEqual(saw, want) {
1885 t.Errorf("got directories %v, want %v", saw, want)
1889 func TestEscaping(t *testing.T) {
1894 for _, p := range []string{
1895 filepath.Join(dir2, "x"),
1897 if !filepath.IsLocal(p) {
1900 f, err := os.Create(p)
1904 ents, err := os.ReadDir(dir2)
1908 for _, e := range ents {
1909 t.Fatalf("found: %v", e.Name())