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\\`},
113 // Don't allow cleaning to move an element with a colon to the start of the path.
116 {`a/../c:/a`, `.\c:\a`},
117 {`a/../../c:`, `..\c:`},
118 {`foo:bar`, `foo:bar`},
121 func TestClean(t *testing.T) {
123 if runtime.GOOS == "windows" {
124 for i := range tests {
125 tests[i].result = filepath.FromSlash(tests[i].result)
127 tests = append(tests, wincleantests...)
129 tests = append(tests, nonwincleantests...)
131 for _, test := range tests {
132 if s := filepath.Clean(test.path); s != test.result {
133 t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
135 if s := filepath.Clean(test.result); s != test.result {
136 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
141 t.Skip("skipping malloc count in short mode")
143 if runtime.GOMAXPROCS(0) > 1 {
144 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
148 for _, test := range tests {
149 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
151 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
156 type IsLocalTest struct {
161 var islocaltests = []IsLocalTest{
174 {`a/../b:/../../c`, false},
177 var winislocaltests = []IsLocalTest{
193 {`dollar$`, true}, // not a special file name
196 var plan9islocaltests = []IsLocalTest{
200 func TestIsLocal(t *testing.T) {
201 tests := islocaltests
202 if runtime.GOOS == "windows" {
203 tests = append(tests, winislocaltests...)
205 if runtime.GOOS == "plan9" {
206 tests = append(tests, plan9islocaltests...)
208 for _, test := range tests {
209 if got := filepath.IsLocal(test.path); got != test.isLocal {
210 t.Errorf("IsLocal(%q) = %v, want %v", test.path, got, test.isLocal)
215 const sep = filepath.Separator
217 var slashtests = []PathTest{
220 {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
221 {"a//b", string([]byte{'a', sep, sep, 'b'})},
224 func TestFromAndToSlash(t *testing.T) {
225 for _, test := range slashtests {
226 if s := filepath.FromSlash(test.path); s != test.result {
227 t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
229 if s := filepath.ToSlash(test.result); s != test.path {
230 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
235 type SplitListTest struct {
240 const lsep = filepath.ListSeparator
242 var splitlisttests = []SplitListTest{
244 {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
245 {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
248 var winsplitlisttests = []SplitListTest{
250 {`"a"`, []string{`a`}},
253 {`";"`, []string{`;`}},
254 {`"a;b"`, []string{`a;b`}},
255 {`";";`, []string{`;`, ``}},
256 {`;";"`, []string{``, `;`}},
259 {`a";"b`, []string{`a;b`}},
260 {`a; ""b`, []string{`a`, ` b`}},
261 {`"a;b`, []string{`a;b`}},
262 {`""a;b`, []string{`a`, `b`}},
263 {`"""a;b`, []string{`a;b`}},
264 {`""""a;b`, []string{`a`, `b`}},
265 {`a";b`, []string{`a;b`}},
266 {`a;b";c`, []string{`a`, `b;c`}},
267 {`"a";b";c`, []string{`a`, `b;c`}},
270 func TestSplitList(t *testing.T) {
271 tests := splitlisttests
272 if runtime.GOOS == "windows" {
273 tests = append(tests, winsplitlisttests...)
275 for _, test := range tests {
276 if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
277 t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
282 type SplitTest struct {
283 path, dir, file string
286 var unixsplittests = []SplitTest{
288 {"a/b/", "a/b/", ""},
294 var winsplittests = []SplitTest{
297 {`c:/foo`, `c:/`, `foo`},
298 {`c:/foo/bar`, `c:/foo/`, `bar`},
299 {`//host/share`, `//host/share`, ``},
300 {`//host/share/`, `//host/share/`, ``},
301 {`//host/share/foo`, `//host/share/`, `foo`},
302 {`\\host\share`, `\\host\share`, ``},
303 {`\\host\share\`, `\\host\share\`, ``},
304 {`\\host\share\foo`, `\\host\share\`, `foo`},
307 func TestSplit(t *testing.T) {
308 var splittests []SplitTest
309 splittests = unixsplittests
310 if runtime.GOOS == "windows" {
311 splittests = append(splittests, winsplittests...)
313 for _, test := range splittests {
314 if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
315 t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
320 type JoinTest struct {
325 var jointests = []JoinTest{
331 {[]string{"/"}, "/"},
332 {[]string{"a"}, "a"},
335 {[]string{"a", "b"}, "a/b"},
336 {[]string{"a", ""}, "a"},
337 {[]string{"", "b"}, "b"},
338 {[]string{"/", "a"}, "/a"},
339 {[]string{"/", "a/b"}, "/a/b"},
340 {[]string{"/", ""}, "/"},
341 {[]string{"/a", "b"}, "/a/b"},
342 {[]string{"a", "/b"}, "a/b"},
343 {[]string{"/a", "/b"}, "/a/b"},
344 {[]string{"a/", "b"}, "a/b"},
345 {[]string{"a/", ""}, "a"},
346 {[]string{"", ""}, ""},
349 {[]string{"/", "a", "b"}, "/a/b"},
352 var nonwinjointests = []JoinTest{
353 {[]string{"//", "a"}, "/a"},
356 var winjointests = []JoinTest{
357 {[]string{`directory`, `file`}, `directory\file`},
358 {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
359 {[]string{`C:\Windows\`, ``}, `C:\Windows`},
360 {[]string{`C:\`, `Windows`}, `C:\Windows`},
361 {[]string{`C:`, `a`}, `C:a`},
362 {[]string{`C:`, `a\b`}, `C:a\b`},
363 {[]string{`C:`, `a`, `b`}, `C:a\b`},
364 {[]string{`C:`, ``, `b`}, `C:b`},
365 {[]string{`C:`, ``, ``, `b`}, `C:b`},
366 {[]string{`C:`, ``}, `C:.`},
367 {[]string{`C:`, ``, ``}, `C:.`},
368 {[]string{`C:`, `\a`}, `C:\a`},
369 {[]string{`C:`, ``, `\a`}, `C:\a`},
370 {[]string{`C:.`, `a`}, `C:a`},
371 {[]string{`C:a`, `b`}, `C:a\b`},
372 {[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
373 {[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
374 {[]string{`\\host\share\foo`}, `\\host\share\foo`},
375 {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
376 {[]string{`\`}, `\`},
377 {[]string{`\`, ``}, `\`},
378 {[]string{`\`, `a`}, `\a`},
379 {[]string{`\\`, `a`}, `\\a`},
380 {[]string{`\`, `a`, `b`}, `\a\b`},
381 {[]string{`\\`, `a`, `b`}, `\\a\b`},
382 {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
383 {[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
384 {[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
385 {[]string{`//`, `a`}, `\\a`},
386 {[]string{`a:\b\c`, `x\..\y:\..\..\z`}, `a:\b\z`},
389 func TestJoin(t *testing.T) {
390 if runtime.GOOS == "windows" {
391 jointests = append(jointests, winjointests...)
393 jointests = append(jointests, nonwinjointests...)
395 for _, test := range jointests {
396 expected := filepath.FromSlash(test.path)
397 if p := filepath.Join(test.elem...); p != expected {
398 t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
403 type ExtTest struct {
407 var exttests = []ExtTest{
409 {"path.pb.go", ".go"},
411 {"a.dir/b.go", ".go"},
415 func TestExt(t *testing.T) {
416 for _, test := range exttests {
417 if x := filepath.Ext(test.path); x != test.ext {
418 t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
425 entries []*Node // nil if the entry is a file
455 func walkTree(n *Node, path string, f func(path string, n *Node)) {
457 for _, e := range n.entries {
458 walkTree(e, filepath.Join(path, e.name), f)
462 func makeTree(t *testing.T) {
463 walkTree(tree, tree.name, func(path string, n *Node) {
464 if n.entries == nil {
465 fd, err := os.Create(path)
467 t.Errorf("makeTree: %v", err)
477 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
479 func checkMarks(t *testing.T, report bool) {
480 walkTree(tree, tree.name, func(path string, n *Node) {
481 if n.mark != 1 && report {
482 t.Errorf("node %s mark = %d; expected 1", path, n.mark)
488 // Assumes that each node name is unique. Good enough for a test.
489 // If clear is true, any incoming error is cleared before return. The errors
490 // are always accumulated, though.
491 func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
493 walkTree(tree, tree.name, func(path string, n *Node) {
499 *errors = append(*errors, err)
508 // chdir changes the current working directory to the named directory,
509 // and then restore the original working directory at the end of the test.
510 func chdir(t *testing.T, dir string) {
511 olddir, err := os.Getwd()
513 t.Fatalf("getwd %s: %v", dir, err)
515 if err := os.Chdir(dir); err != nil {
516 t.Fatalf("chdir %s: %v", dir, err)
520 if err := os.Chdir(olddir); err != nil {
521 t.Errorf("restore original working directory %s: %v", olddir, err)
527 func chtmpdir(t *testing.T) (restore func()) {
528 oldwd, err := os.Getwd()
530 t.Fatalf("chtmpdir: %v", err)
532 d, err := os.MkdirTemp("", "test")
534 t.Fatalf("chtmpdir: %v", err)
536 if err := os.Chdir(d); err != nil {
537 t.Fatalf("chtmpdir: %v", err)
540 if err := os.Chdir(oldwd); err != nil {
541 t.Fatalf("chtmpdir: %v", err)
547 // tempDirCanonical returns a temporary directory for the test to use, ensuring
548 // that the returned path does not contain symlinks.
549 func tempDirCanonical(t *testing.T) string {
552 cdir, err := filepath.EvalSymlinks(dir)
554 t.Errorf("tempDirCanonical: %v", err)
560 func TestWalk(t *testing.T) {
561 walk := func(root string, fn fs.WalkDirFunc) error {
562 return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
563 return fn(path, fs.FileInfoToDirEntry(info), err)
569 func TestWalkDir(t *testing.T) {
570 testWalk(t, filepath.WalkDir, 2)
573 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
574 if runtime.GOOS == "ios" {
575 restore := chtmpdir(t)
579 tmpDir := t.TempDir()
581 origDir, err := os.Getwd()
583 t.Fatal("finding working dir:", err)
585 if err = os.Chdir(tmpDir); err != nil {
586 t.Fatal("entering temp dir:", err)
588 defer os.Chdir(origDir)
591 errors := make([]error, 0, 10)
593 markFn := func(path string, d fs.DirEntry, err error) error {
594 return mark(d, err, &errors, clear)
597 err = walk(tree.name, markFn)
599 t.Fatalf("no error expected, found: %s", err)
601 if len(errors) != 0 {
602 t.Fatalf("unexpected errors: %s", errors)
607 t.Run("PermErr", func(t *testing.T) {
608 // Test permission errors. Only possible if we're not root
609 // and only on some file systems (AFS, FAT). To avoid errors during
610 // all.bash on those file systems, skip during go test -short.
611 // Chmod is not supported on wasip1.
612 if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
613 t.Skip("skipping on " + runtime.GOOS)
615 if os.Getuid() == 0 {
616 t.Skip("skipping as root")
619 t.Skip("skipping in short mode")
622 // introduce 2 errors: chmod top-level directories to 0
623 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
624 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
626 // 3) capture errors, expect two.
627 // mark respective subtrees manually
628 markTree(tree.entries[1])
629 markTree(tree.entries[3])
630 // correct double-marking of directory itself
631 tree.entries[1].mark -= errVisit
632 tree.entries[3].mark -= errVisit
633 err := walk(tree.name, markFn)
635 t.Fatalf("expected no error return from Walk, got %s", err)
637 if len(errors) != 2 {
638 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
640 // the inaccessible subtrees were marked manually
644 // 4) capture errors, stop after first error.
645 // mark respective subtrees manually
646 markTree(tree.entries[1])
647 markTree(tree.entries[3])
648 // correct double-marking of directory itself
649 tree.entries[1].mark -= errVisit
650 tree.entries[3].mark -= errVisit
651 clear = false // error will stop processing
652 err = walk(tree.name, markFn)
654 t.Fatalf("expected error return from Walk")
656 if len(errors) != 1 {
657 t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
659 // the inaccessible subtrees were marked manually
663 // restore permissions
664 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
665 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
669 func touch(t *testing.T, name string) {
670 f, err := os.Create(name)
674 if err := f.Close(); err != nil {
679 func TestWalkSkipDirOnFile(t *testing.T) {
682 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
685 touch(t, filepath.Join(td, "dir/foo1"))
686 touch(t, filepath.Join(td, "dir/foo2"))
689 walker := func(path string) error {
690 if strings.HasSuffix(path, "foo2") {
693 if strings.HasSuffix(path, "foo1") {
694 return filepath.SkipDir
698 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
699 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
701 check := func(t *testing.T, walk func(root string) error, root string) {
709 t.Errorf("SkipDir on file foo1 did not block processing of foo2")
713 t.Run("Walk", func(t *testing.T) {
714 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
716 check(t, Walk, filepath.Join(td, "dir"))
718 t.Run("WalkDir", func(t *testing.T) {
719 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
720 check(t, WalkDir, td)
721 check(t, WalkDir, filepath.Join(td, "dir"))
725 func TestWalkSkipAllOnFile(t *testing.T) {
728 if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
731 if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
735 touch(t, filepath.Join(td, "dir", "foo1"))
736 touch(t, filepath.Join(td, "dir", "foo2"))
737 touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
738 touch(t, filepath.Join(td, "dir", "foo4"))
739 touch(t, filepath.Join(td, "dir2", "bar"))
740 touch(t, filepath.Join(td, "last"))
742 remainingWereSkipped := true
743 walker := func(path string) error {
744 if strings.HasSuffix(path, "foo2") {
745 return filepath.SkipAll
748 if strings.HasSuffix(path, "foo3") ||
749 strings.HasSuffix(path, "foo4") ||
750 strings.HasSuffix(path, "bar") ||
751 strings.HasSuffix(path, "last") {
752 remainingWereSkipped = false
757 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
758 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
760 check := func(t *testing.T, walk func(root string) error, root string) {
762 remainingWereSkipped = true
763 if err := walk(root); err != nil {
766 if !remainingWereSkipped {
767 t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
771 t.Run("Walk", func(t *testing.T) {
772 Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
774 check(t, Walk, filepath.Join(td, "dir"))
776 t.Run("WalkDir", func(t *testing.T) {
777 WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
778 check(t, WalkDir, td)
779 check(t, WalkDir, filepath.Join(td, "dir"))
783 func TestWalkFileError(t *testing.T) {
786 touch(t, filepath.Join(td, "foo"))
787 touch(t, filepath.Join(td, "bar"))
788 dir := filepath.Join(td, "dir")
789 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
792 touch(t, filepath.Join(dir, "baz"))
793 touch(t, filepath.Join(dir, "stat-error"))
795 *filepath.LstatP = os.Lstat
797 statErr := errors.New("some stat error")
798 *filepath.LstatP = func(path string) (fs.FileInfo, error) {
799 if strings.HasSuffix(path, "stat-error") {
802 return os.Lstat(path)
804 got := map[string]error{}
805 err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
806 rel, _ := filepath.Rel(td, path)
807 got[filepath.ToSlash(rel)] = err
811 t.Errorf("Walk error: %v", err)
813 want := map[string]error{
819 "dir/stat-error": statErr,
821 if !reflect.DeepEqual(got, want) {
822 t.Errorf("Walked %#v; want %#v", got, want)
826 func TestWalkSymlinkRoot(t *testing.T) {
827 testenv.MustHaveSymlink(t)
830 dir := filepath.Join(td, "dir")
831 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
834 touch(t, filepath.Join(dir, "foo"))
836 link := filepath.Join(td, "link")
837 if err := os.Symlink("dir", link); err != nil {
841 abslink := filepath.Join(td, "abslink")
842 if err := os.Symlink(dir, abslink); err != nil {
846 linklink := filepath.Join(td, "linklink")
847 if err := os.Symlink("link", linklink); err != nil {
851 // Per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12:
852 // “A pathname that contains at least one non- <slash> character and that ends
853 // with one or more trailing <slash> characters shall not be resolved
854 // successfully unless the last pathname component before the trailing <slash>
855 // characters names an existing directory [...].”
857 // Since Walk does not traverse symlinks itself, its behavior should depend on
858 // whether the path passed to Walk ends in a slash: if it does not end in a slash,
859 // Walk should report the symlink itself (since it is the last pathname component);
860 // but if it does end in a slash, Walk should walk the directory to which the symlink
861 // refers (since it must be fully resolved before walking).
862 for _, tt := range []struct {
871 want: []string{link},
875 root: link + string(filepath.Separator),
876 want: []string{link, filepath.Join(link, "foo")},
879 desc: "abs no slash",
881 want: []string{abslink},
884 desc: "abs with slash",
885 root: abslink + string(filepath.Separator),
886 want: []string{abslink, filepath.Join(abslink, "foo")},
889 desc: "double link no slash",
891 want: []string{linklink},
894 desc: "double link with slash",
895 root: linklink + string(filepath.Separator),
896 want: []string{linklink, filepath.Join(linklink, "foo")},
897 buggyGOOS: []string{"darwin", "ios"}, // https://go.dev/issue/59586
901 t.Run(tt.desc, func(t *testing.T) {
903 err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
907 t.Logf("%#q: %v", path, info.Mode())
908 walked = append(walked, filepath.Clean(path))
915 if !reflect.DeepEqual(walked, tt.want) {
916 t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
917 if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
918 t.Logf("(ignoring known bug on %v)", runtime.GOOS)
927 var basetests = []PathTest{
941 var winbasetests = []PathTest{
947 {`\\host\share\`, `\`},
948 {`\\host\share\a`, `a`},
949 {`\\host\share\a\b`, `b`},
952 func TestBase(t *testing.T) {
954 if runtime.GOOS == "windows" {
955 // make unix tests work on windows
956 for i := range tests {
957 tests[i].result = filepath.Clean(tests[i].result)
959 // add windows specific tests
960 tests = append(tests, winbasetests...)
962 for _, test := range tests {
963 if s := filepath.Base(test.path); s != test.result {
964 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
969 var dirtests = []PathTest{
983 var nonwindirtests = []PathTest{
987 var windirtests = []PathTest{
992 {`c:a\b\c`, `c:a\b`},
993 {`\\host\share`, `\\host\share`},
994 {`\\host\share\`, `\\host\share\`},
995 {`\\host\share\a`, `\\host\share\`},
996 {`\\host\share\a\b`, `\\host\share\a`},
1000 func TestDir(t *testing.T) {
1002 if runtime.GOOS == "windows" {
1003 // make unix tests work on windows
1004 for i := range tests {
1005 tests[i].result = filepath.Clean(tests[i].result)
1007 // add windows specific tests
1008 tests = append(tests, windirtests...)
1010 tests = append(tests, nonwindirtests...)
1012 for _, test := range tests {
1013 if s := filepath.Dir(test.path); s != test.result {
1014 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
1019 type IsAbsTest struct {
1024 var isabstests = []IsAbsTest{
1027 {"/usr/bin/gcc", true},
1035 var winisabstests = []IsAbsTest{
1042 {`\Windows`, false},
1046 {`\\host\share`, true},
1047 {`\\host\share\`, true},
1048 {`\\host\share\foo`, true},
1049 {`//host/share/foo/bar`, true},
1052 func TestIsAbs(t *testing.T) {
1053 var tests []IsAbsTest
1054 if runtime.GOOS == "windows" {
1055 tests = append(tests, winisabstests...)
1056 // All non-windows tests should fail, because they have no volume letter.
1057 for _, test := range isabstests {
1058 tests = append(tests, IsAbsTest{test.path, false})
1060 // All non-windows test should work as intended if prefixed with volume letter.
1061 for _, test := range isabstests {
1062 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
1068 for _, test := range tests {
1069 if r := filepath.IsAbs(test.path); r != test.isAbs {
1070 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
1075 type EvalSymlinksTest struct {
1076 // If dest is empty, the path is created; otherwise the dest is symlinked to the path.
1080 var EvalSymlinksTestDirs = []EvalSymlinksTest{
1083 {"test/dir/link3", "../../"},
1084 {"test/link1", "../test"},
1085 {"test/link2", "dir"},
1086 {"test/linkabs", "/"},
1087 {"test/link4", "../test2"},
1088 {"test2", "test/dir"},
1092 {"src/pool/test", ""},
1093 {"src/versions", ""},
1094 {"src/versions/current", "../../version"},
1095 {"src/versions/v1", ""},
1096 {"src/versions/v1/modules", ""},
1097 {"src/versions/v1/modules/test", "../../../pool/test"},
1098 {"version", "src/versions/v1"},
1101 var EvalSymlinksTests = []EvalSymlinksTest{
1103 {"test/dir", "test/dir"},
1104 {"test/dir/../..", "."},
1105 {"test/link1", "test"},
1106 {"test/link2", "test/dir"},
1107 {"test/link1/dir", "test/dir"},
1108 {"test/link2/..", "test"},
1109 {"test/dir/link3", "."},
1110 {"test/link2/link3/test", "test"},
1111 {"test/linkabs", "/"},
1112 {"test/link4/..", "test"},
1113 {"src/versions/current/modules/test", "src/pool/test"},
1116 // simpleJoin builds a file name from the directory and path.
1117 // It does not use Join because we don't want ".." to be evaluated.
1118 func simpleJoin(dir, path string) string {
1119 return dir + string(filepath.Separator) + path
1122 func testEvalSymlinks(t *testing.T, path, want string) {
1123 have, err := filepath.EvalSymlinks(path)
1125 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1128 if filepath.Clean(have) != filepath.Clean(want) {
1129 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1133 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1134 cwd, err := os.Getwd()
1139 err := os.Chdir(cwd)
1150 have, err := filepath.EvalSymlinks(path)
1152 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1155 if filepath.Clean(have) != filepath.Clean(want) {
1156 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1160 func TestEvalSymlinks(t *testing.T) {
1161 testenv.MustHaveSymlink(t)
1163 tmpDir := t.TempDir()
1165 // /tmp may itself be a symlink! Avoid the confusion, although
1166 // it means trusting the thing we're testing.
1168 tmpDir, err = filepath.EvalSymlinks(tmpDir)
1170 t.Fatal("eval symlink for tmp dir:", err)
1173 // Create the symlink farm using relative paths.
1174 for _, d := range EvalSymlinksTestDirs {
1176 path := simpleJoin(tmpDir, d.path)
1178 err = os.Mkdir(path, 0755)
1180 err = os.Symlink(d.dest, path)
1187 // Evaluate the symlink farm.
1188 for _, test := range EvalSymlinksTests {
1189 path := simpleJoin(tmpDir, test.path)
1191 dest := simpleJoin(tmpDir, test.dest)
1192 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1195 testEvalSymlinks(t, path, dest)
1197 // test EvalSymlinks(".")
1198 testEvalSymlinksAfterChdir(t, path, ".", ".")
1200 // test EvalSymlinks("C:.") on Windows
1201 if runtime.GOOS == "windows" {
1202 volDot := filepath.VolumeName(tmpDir) + "."
1203 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1206 // test EvalSymlinks(".."+path)
1207 dotdotPath := simpleJoin("..", test.dest)
1208 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1209 dotdotPath = test.dest
1211 testEvalSymlinksAfterChdir(t,
1212 simpleJoin(tmpDir, "test"),
1213 simpleJoin("..", test.path),
1216 // test EvalSymlinks(p) where p is relative path
1217 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1221 func TestEvalSymlinksIsNotExist(t *testing.T) {
1222 testenv.MustHaveSymlink(t)
1226 _, err := filepath.EvalSymlinks("notexist")
1227 if !os.IsNotExist(err) {
1228 t.Errorf("expected the file is not found, got %v\n", err)
1231 err = os.Symlink("notexist", "link")
1235 defer os.Remove("link")
1237 _, err = filepath.EvalSymlinks("link")
1238 if !os.IsNotExist(err) {
1239 t.Errorf("expected the file is not found, got %v\n", err)
1243 func TestIssue13582(t *testing.T) {
1244 testenv.MustHaveSymlink(t)
1246 tmpDir := t.TempDir()
1248 dir := filepath.Join(tmpDir, "dir")
1249 err := os.Mkdir(dir, 0755)
1253 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1254 err = os.Symlink(dir, linkToDir)
1258 file := filepath.Join(linkToDir, "file")
1259 err = os.WriteFile(file, nil, 0644)
1263 link1 := filepath.Join(linkToDir, "link1")
1264 err = os.Symlink(file, link1)
1268 link2 := filepath.Join(linkToDir, "link2")
1269 err = os.Symlink(link1, link2)
1274 // /tmp may itself be a symlink!
1275 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1279 realDir := filepath.Join(realTmpDir, "dir")
1280 realFile := filepath.Join(realDir, "file")
1286 {linkToDir, realDir},
1291 for i, test := range tests {
1292 have, err := filepath.EvalSymlinks(test.path)
1296 if have != test.want {
1297 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1303 func TestRelativeSymlinkToAbsolute(t *testing.T) {
1304 testenv.MustHaveSymlink(t)
1305 // Not parallel: uses os.Chdir.
1307 tmpDir := t.TempDir()
1310 // Create "link" in the current working directory as a symlink to an arbitrary
1311 // absolute path. On macOS, this path is likely to begin with a symlink
1312 // itself: generally either in /var (symlinked to "private/var") or /tmp
1313 // (symlinked to "private/tmp").
1314 if err := os.Symlink(tmpDir, "link"); err != nil {
1317 t.Logf(`os.Symlink(%q, "link")`, tmpDir)
1319 p, err := filepath.EvalSymlinks("link")
1321 t.Fatalf(`EvalSymlinks("link"): %v`, err)
1323 want, err := filepath.EvalSymlinks(tmpDir)
1325 t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
1328 t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
1330 t.Logf(`EvalSymlinks("link") = %q`, p)
1333 // Test directories relative to temporary directory.
1334 // The tests are run in absTestDirs[0].
1335 var absTestDirs = []string{
1341 // Test paths relative to temporary directory. $ expands to the directory.
1342 // The tests are run in absTestDirs[0].
1343 // We create absTestDirs first.
1344 var absTests = []string{
1350 "../a/b/./c/../../.././a",
1351 "../a/b/./c/../../.././a/",
1355 "$/a/b/c/../../.././a",
1356 "$/a/b/c/../../.././a/",
1359 func TestAbs(t *testing.T) {
1361 wd, err := os.Getwd()
1363 t.Fatal("getwd failed: ", err)
1365 err = os.Chdir(root)
1367 t.Fatal("chdir failed: ", err)
1371 for _, dir := range absTestDirs {
1372 err = os.Mkdir(dir, 0777)
1374 t.Fatal("Mkdir failed: ", err)
1378 if runtime.GOOS == "windows" {
1379 vol := filepath.VolumeName(root)
1381 for _, path := range absTests {
1382 if strings.Contains(path, "$") {
1386 extra = append(extra, path)
1388 absTests = append(absTests, extra...)
1391 err = os.Chdir(absTestDirs[0])
1393 t.Fatal("chdir failed: ", err)
1396 for _, path := range absTests {
1397 path = strings.ReplaceAll(path, "$", root)
1398 info, err := os.Stat(path)
1400 t.Errorf("%s: %s", path, err)
1404 abspath, err := filepath.Abs(path)
1406 t.Errorf("Abs(%q) error: %v", path, err)
1409 absinfo, err := os.Stat(abspath)
1410 if err != nil || !os.SameFile(absinfo, info) {
1411 t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1413 if !filepath.IsAbs(abspath) {
1414 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1416 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1417 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1422 // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
1423 // We test it separately from all other absTests because the empty string is not
1424 // a valid path, so it can't be used with os.Stat.
1425 func TestAbsEmptyString(t *testing.T) {
1428 wd, err := os.Getwd()
1430 t.Fatal("getwd failed: ", err)
1432 err = os.Chdir(root)
1434 t.Fatal("chdir failed: ", err)
1438 info, err := os.Stat(root)
1440 t.Fatalf("%s: %s", root, err)
1443 abspath, err := filepath.Abs("")
1445 t.Fatalf(`Abs("") error: %v`, err)
1447 absinfo, err := os.Stat(abspath)
1448 if err != nil || !os.SameFile(absinfo, info) {
1449 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1451 if !filepath.IsAbs(abspath) {
1452 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1454 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1455 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1459 type RelTests struct {
1460 root, path, want string
1463 var reltests = []RelTests{
1464 {"a/b", "a/b", "."},
1465 {"a/b/.", "a/b", "."},
1466 {"a/b", "a/b/.", "."},
1467 {"./a/b", "a/b", "."},
1468 {"a/b", "./a/b", "."},
1469 {"ab/cd", "ab/cde", "../cde"},
1470 {"ab/cd", "ab/c", "../c"},
1471 {"a/b", "a/b/c/d", "c/d"},
1472 {"a/b", "a/b/../c", "../c"},
1473 {"a/b/../c", "a/b", "../b"},
1474 {"a/b/c", "a/c/d", "../../c/d"},
1475 {"a/b", "c/d", "../../c/d"},
1476 {"a/b/c/d", "a/b", "../.."},
1477 {"a/b/c/d", "a/b/", "../.."},
1478 {"a/b/c/d/", "a/b", "../.."},
1479 {"a/b/c/d/", "a/b/", "../.."},
1480 {"../../a/b", "../../a/b/c/d", "c/d"},
1481 {"/a/b", "/a/b", "."},
1482 {"/a/b/.", "/a/b", "."},
1483 {"/a/b", "/a/b/.", "."},
1484 {"/ab/cd", "/ab/cde", "../cde"},
1485 {"/ab/cd", "/ab/c", "../c"},
1486 {"/a/b", "/a/b/c/d", "c/d"},
1487 {"/a/b", "/a/b/../c", "../c"},
1488 {"/a/b/../c", "/a/b", "../b"},
1489 {"/a/b/c", "/a/c/d", "../../c/d"},
1490 {"/a/b", "/c/d", "../../c/d"},
1491 {"/a/b/c/d", "/a/b", "../.."},
1492 {"/a/b/c/d", "/a/b/", "../.."},
1493 {"/a/b/c/d/", "/a/b", "../.."},
1494 {"/a/b/c/d/", "/a/b/", "../.."},
1495 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1496 {".", "a/b", "a/b"},
1499 // can't do purely lexically
1502 {"../..", "..", "err"},
1507 var winreltests = []RelTests{
1508 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1509 {`C:\`, `D:\`, `err`},
1510 {`C:`, `D:`, `err`},
1511 {`C:\Projects`, `c:\projects\src`, `src`},
1512 {`C:\Projects`, `c:\projects`, `.`},
1513 {`C:\Projects\a\..`, `c:\projects`, `.`},
1514 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1517 func TestRel(t *testing.T) {
1518 tests := append([]RelTests{}, reltests...)
1519 if runtime.GOOS == "windows" {
1520 for i := range tests {
1521 tests[i].want = filepath.FromSlash(tests[i].want)
1523 tests = append(tests, winreltests...)
1525 for _, test := range tests {
1526 got, err := filepath.Rel(test.root, test.path)
1527 if test.want == "err" {
1529 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1534 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1536 if got != test.want {
1537 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1542 type VolumeNameTest struct {
1547 var volumenametests = []VolumeNameTest{
1548 {`c:/foo/bar`, `c:`},
1552 {`\\\host`, `\\\host`},
1553 {`\\\host\`, `\\\host`},
1554 {`\\\host\share`, `\\\host`},
1555 {`\\\host\\share`, `\\\host`},
1556 {`\\host`, `\\host`},
1557 {`//host`, `\\host`},
1558 {`\\host\`, `\\host\`},
1559 {`//host/`, `\\host\`},
1560 {`\\host\share`, `\\host\share`},
1561 {`//host/share`, `\\host\share`},
1562 {`\\host\share\`, `\\host\share`},
1563 {`//host/share/`, `\\host\share`},
1564 {`\\host\share\foo`, `\\host\share`},
1565 {`//host/share/foo`, `\\host\share`},
1566 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1567 {`//host/share//foo///bar////baz`, `\\host\share`},
1568 {`\\host\share\foo\..\bar`, `\\host\share`},
1569 {`//host/share/foo/../bar`, `\\host\share`},
1570 {`//./NUL`, `\\.\NUL`},
1571 {`//?/NUL`, `\\?\NUL`},
1572 {`//./C:`, `\\.\C:`},
1573 {`//./C:/a/b/c`, `\\.\C:`},
1574 {`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
1575 {`//./UNC/host`, `\\.\UNC\host`},
1578 func TestVolumeName(t *testing.T) {
1579 if runtime.GOOS != "windows" {
1582 for _, v := range volumenametests {
1583 if vol := filepath.VolumeName(v.path); vol != v.vol {
1584 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1589 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1590 if runtime.GOOS != "windows" {
1595 t.Errorf("Current directory path %q is too short", wd)
1597 lp := strings.ToLower(wd)
1598 up := strings.ToUpper(wd)
1599 flp, err := filepath.EvalSymlinks(lp)
1601 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1603 fup, err := filepath.EvalSymlinks(up)
1605 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1608 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1612 func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
1613 if runtime.GOOS == "ios" {
1614 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1616 root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
1617 utf16 := filepath.Join(root, "utf16")
1618 utf8 := filepath.Join(root, "utf8")
1621 err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1629 return filepath.SkipDir
1632 t.Fatal("filepath.Walk out of order - utf8 before utf16")
1642 t.Fatalf("%q not seen", utf8)
1646 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1647 tmpdir := t.TempDir()
1649 wd, err := os.Getwd()
1655 err = os.Chdir(tmpdir)
1660 err = mklink(tmpdir, "link")
1665 var visited []string
1666 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1670 rel, err := filepath.Rel(tmpdir, path)
1674 visited = append(visited, rel)
1680 sort.Strings(visited)
1681 want := []string{".", "link"}
1682 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1683 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1687 func TestWalkSymlink(t *testing.T) {
1688 testenv.MustHaveSymlink(t)
1689 testWalkSymlink(t, os.Symlink)
1692 func TestIssue29372(t *testing.T) {
1693 tmpDir := t.TempDir()
1695 path := filepath.Join(tmpDir, "file.txt")
1696 err := os.WriteFile(path, nil, 0644)
1701 pathSeparator := string(filepath.Separator)
1703 path + strings.Repeat(pathSeparator, 1),
1704 path + strings.Repeat(pathSeparator, 2),
1705 path + strings.Repeat(pathSeparator, 1) + ".",
1706 path + strings.Repeat(pathSeparator, 2) + ".",
1707 path + strings.Repeat(pathSeparator, 1) + "..",
1708 path + strings.Repeat(pathSeparator, 2) + "..",
1711 for i, test := range tests {
1712 _, err = filepath.EvalSymlinks(test)
1713 if err != syscall.ENOTDIR {
1714 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1719 // Issue 30520 part 1.
1720 func TestEvalSymlinksAboveRoot(t *testing.T) {
1721 testenv.MustHaveSymlink(t)
1725 tmpDir := t.TempDir()
1727 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1732 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1735 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1738 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1742 // Count the number of ".." elements to get to the root directory.
1743 vol := filepath.VolumeName(evalTmpDir)
1744 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1746 for i := 0; i < c+2; i++ {
1747 dd = append(dd, "..")
1750 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1752 // Try different numbers of "..".
1753 for _, i := range []int{c, c + 1, c + 2} {
1754 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1755 resolved, err := filepath.EvalSymlinks(check)
1757 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1758 // On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
1759 testenv.SkipFlaky(t, 37910)
1761 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1762 case !strings.HasSuffix(resolved, wantSuffix):
1763 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1765 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1770 // Issue 30520 part 2.
1771 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1772 testenv.MustHaveSymlink(t)
1774 tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1778 defer os.RemoveAll(tmpDir)
1781 subdir := filepath.Join("a", "b")
1782 if err := os.MkdirAll(subdir, 0777); err != nil {
1785 if err := os.Symlink(subdir, "c"); err != nil {
1788 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1792 subdir = filepath.Join("d", "e", "f")
1793 if err := os.MkdirAll(subdir, 0777); err != nil {
1796 if err := os.Chdir(subdir); err != nil {
1800 check := filepath.Join("..", "..", "..", "c", "file")
1801 wantSuffix := filepath.Join("a", "b", "file")
1802 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1803 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1804 } else if !strings.HasSuffix(resolved, wantSuffix) {
1805 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1807 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1811 func TestIssue51617(t *testing.T) {
1813 for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
1814 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
1818 bad := filepath.Join(dir, "a", "bad")
1819 if err := os.Chmod(bad, 0); err != nil {
1822 defer os.Chmod(bad, 0700) // avoid errors on cleanup
1824 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1826 return filepath.SkipDir
1829 rel, err := filepath.Rel(dir, path)
1833 saw = append(saw, rel)
1840 want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
1841 if !reflect.DeepEqual(saw, want) {
1842 t.Errorf("got directories %v, want %v", saw, want)