c++: address deduction and concepts [CWG2918]
[official-gcc.git] / libgo / go / path / filepath / path_test.go
blob85926eadf11415996fcabaafd6c3d18a92dee79b
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.
5 package filepath_test
7 import (
8 "errors"
9 "fmt"
10 "internal/testenv"
11 "io/fs"
12 "os"
13 "path/filepath"
14 "reflect"
15 "runtime"
16 "sort"
17 "strings"
18 "syscall"
19 "testing"
22 type PathTest struct {
23 path, result string
26 var cleantests = []PathTest{
27 // Already clean
28 {"abc", "abc"},
29 {"abc/def", "abc/def"},
30 {"a/b/c", "a/b/c"},
31 {".", "."},
32 {"..", ".."},
33 {"../..", "../.."},
34 {"../../abc", "../../abc"},
35 {"/abc", "/abc"},
36 {"/", "/"},
38 // Empty is current dir
39 {"", "."},
41 // Remove trailing slash
42 {"abc/", "abc"},
43 {"abc/def/", "abc/def"},
44 {"a/b/c/", "a/b/c"},
45 {"./", "."},
46 {"../", ".."},
47 {"../../", "../.."},
48 {"/abc/", "/abc"},
50 // Remove doubled slash
51 {"abc//def//ghi", "abc/def/ghi"},
52 {"//abc", "/abc"},
53 {"///abc", "/abc"},
54 {"//abc//", "/abc"},
55 {"abc//", "abc"},
57 // Remove . elements
58 {"abc/./def", "abc/def"},
59 {"/./abc/def", "/abc/def"},
60 {"abc/.", "abc"},
62 // Remove .. elements
63 {"abc/def/ghi/../jkl", "abc/def/jkl"},
64 {"abc/def/../ghi/../jkl", "abc/jkl"},
65 {"abc/def/..", "abc"},
66 {"abc/def/../..", "."},
67 {"/abc/def/../..", "/"},
68 {"abc/def/../../..", ".."},
69 {"/abc/def/../../..", "/"},
70 {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
71 {"/../abc", "/abc"},
73 // Combinations
74 {"abc/./../def", "def"},
75 {"abc//./../def", "def"},
76 {"abc/../../././../def", "../../def"},
79 var wincleantests = []PathTest{
80 {`c:`, `c:.`},
81 {`c:\`, `c:\`},
82 {`c:\abc`, `c:\abc`},
83 {`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
84 {`c:\abc\def\..\..`, `c:\`},
85 {`c:\..\abc`, `c:\abc`},
86 {`c:..\abc`, `c:..\abc`},
87 {`\`, `\`},
88 {`/`, `\`},
89 {`\\i\..\c$`, `\c$`},
90 {`\\i\..\i\c$`, `\i\c$`},
91 {`\\i\..\I\c$`, `\I\c$`},
92 {`\\host\share\foo\..\bar`, `\\host\share\bar`},
93 {`//host/share/foo/../baz`, `\\host\share\baz`},
94 {`\\a\b\..\c`, `\\a\b\c`},
95 {`\\a\b`, `\\a\b`},
98 func TestClean(t *testing.T) {
99 tests := cleantests
100 if runtime.GOOS == "windows" {
101 for i := range tests {
102 tests[i].result = filepath.FromSlash(tests[i].result)
104 tests = append(tests, wincleantests...)
106 for _, test := range tests {
107 if s := filepath.Clean(test.path); s != test.result {
108 t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
110 if s := filepath.Clean(test.result); s != test.result {
111 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
115 if testing.Short() {
116 t.Skip("skipping malloc count in short mode")
118 if runtime.GOMAXPROCS(0) > 1 {
119 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
120 return
123 t.Log("Skipping AllocsPerRun for gccgo")
124 return
126 for _, test := range tests {
127 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
128 if allocs > 0 {
129 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
134 const sep = filepath.Separator
136 var slashtests = []PathTest{
137 {"", ""},
138 {"/", string(sep)},
139 {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
140 {"a//b", string([]byte{'a', sep, sep, 'b'})},
143 func TestFromAndToSlash(t *testing.T) {
144 for _, test := range slashtests {
145 if s := filepath.FromSlash(test.path); s != test.result {
146 t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
148 if s := filepath.ToSlash(test.result); s != test.path {
149 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
154 type SplitListTest struct {
155 list string
156 result []string
159 const lsep = filepath.ListSeparator
161 var splitlisttests = []SplitListTest{
162 {"", []string{}},
163 {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
164 {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
167 var winsplitlisttests = []SplitListTest{
168 // quoted
169 {`"a"`, []string{`a`}},
171 // semicolon
172 {`";"`, []string{`;`}},
173 {`"a;b"`, []string{`a;b`}},
174 {`";";`, []string{`;`, ``}},
175 {`;";"`, []string{``, `;`}},
177 // partially quoted
178 {`a";"b`, []string{`a;b`}},
179 {`a; ""b`, []string{`a`, ` b`}},
180 {`"a;b`, []string{`a;b`}},
181 {`""a;b`, []string{`a`, `b`}},
182 {`"""a;b`, []string{`a;b`}},
183 {`""""a;b`, []string{`a`, `b`}},
184 {`a";b`, []string{`a;b`}},
185 {`a;b";c`, []string{`a`, `b;c`}},
186 {`"a";b";c`, []string{`a`, `b;c`}},
189 func TestSplitList(t *testing.T) {
190 tests := splitlisttests
191 if runtime.GOOS == "windows" {
192 tests = append(tests, winsplitlisttests...)
194 for _, test := range tests {
195 if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
196 t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
201 type SplitTest struct {
202 path, dir, file string
205 var unixsplittests = []SplitTest{
206 {"a/b", "a/", "b"},
207 {"a/b/", "a/b/", ""},
208 {"a/", "a/", ""},
209 {"a", "", "a"},
210 {"/", "/", ""},
213 var winsplittests = []SplitTest{
214 {`c:`, `c:`, ``},
215 {`c:/`, `c:/`, ``},
216 {`c:/foo`, `c:/`, `foo`},
217 {`c:/foo/bar`, `c:/foo/`, `bar`},
218 {`//host/share`, `//host/share`, ``},
219 {`//host/share/`, `//host/share/`, ``},
220 {`//host/share/foo`, `//host/share/`, `foo`},
221 {`\\host\share`, `\\host\share`, ``},
222 {`\\host\share\`, `\\host\share\`, ``},
223 {`\\host\share\foo`, `\\host\share\`, `foo`},
226 func TestSplit(t *testing.T) {
227 var splittests []SplitTest
228 splittests = unixsplittests
229 if runtime.GOOS == "windows" {
230 splittests = append(splittests, winsplittests...)
232 for _, test := range splittests {
233 if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
234 t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
239 type JoinTest struct {
240 elem []string
241 path string
244 var jointests = []JoinTest{
245 // zero parameters
246 {[]string{}, ""},
248 // one parameter
249 {[]string{""}, ""},
250 {[]string{"/"}, "/"},
251 {[]string{"a"}, "a"},
253 // two parameters
254 {[]string{"a", "b"}, "a/b"},
255 {[]string{"a", ""}, "a"},
256 {[]string{"", "b"}, "b"},
257 {[]string{"/", "a"}, "/a"},
258 {[]string{"/", "a/b"}, "/a/b"},
259 {[]string{"/", ""}, "/"},
260 {[]string{"//", "a"}, "/a"},
261 {[]string{"/a", "b"}, "/a/b"},
262 {[]string{"a/", "b"}, "a/b"},
263 {[]string{"a/", ""}, "a"},
264 {[]string{"", ""}, ""},
266 // three parameters
267 {[]string{"/", "a", "b"}, "/a/b"},
270 var winjointests = []JoinTest{
271 {[]string{`directory`, `file`}, `directory\file`},
272 {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
273 {[]string{`C:\Windows\`, ``}, `C:\Windows`},
274 {[]string{`C:\`, `Windows`}, `C:\Windows`},
275 {[]string{`C:`, `a`}, `C:a`},
276 {[]string{`C:`, `a\b`}, `C:a\b`},
277 {[]string{`C:`, `a`, `b`}, `C:a\b`},
278 {[]string{`C:`, ``, `b`}, `C:b`},
279 {[]string{`C:`, ``, ``, `b`}, `C:b`},
280 {[]string{`C:`, ``}, `C:.`},
281 {[]string{`C:`, ``, ``}, `C:.`},
282 {[]string{`C:.`, `a`}, `C:a`},
283 {[]string{`C:a`, `b`}, `C:a\b`},
284 {[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
285 {[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
286 {[]string{`\\host\share\foo`}, `\\host\share\foo`},
287 {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
288 {[]string{`\`}, `\`},
289 {[]string{`\`, ``}, `\`},
290 {[]string{`\`, `a`}, `\a`},
291 {[]string{`\\`, `a`}, `\a`},
292 {[]string{`\`, `a`, `b`}, `\a\b`},
293 {[]string{`\\`, `a`, `b`}, `\a\b`},
294 {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
295 {[]string{`\\a`, `b`, `c`}, `\a\b\c`},
296 {[]string{`\\a\`, `b`, `c`}, `\a\b\c`},
299 func TestJoin(t *testing.T) {
300 if runtime.GOOS == "windows" {
301 jointests = append(jointests, winjointests...)
303 for _, test := range jointests {
304 expected := filepath.FromSlash(test.path)
305 if p := filepath.Join(test.elem...); p != expected {
306 t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
311 type ExtTest struct {
312 path, ext string
315 var exttests = []ExtTest{
316 {"path.go", ".go"},
317 {"path.pb.go", ".go"},
318 {"a.dir/b", ""},
319 {"a.dir/b.go", ".go"},
320 {"a.dir/", ""},
323 func TestExt(t *testing.T) {
324 for _, test := range exttests {
325 if x := filepath.Ext(test.path); x != test.ext {
326 t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
331 type Node struct {
332 name string
333 entries []*Node // nil if the entry is a file
334 mark int
337 var tree = &Node{
338 "testdata",
339 []*Node{
340 {"a", nil, 0},
341 {"b", []*Node{}, 0},
342 {"c", nil, 0},
344 "d",
345 []*Node{
346 {"x", nil, 0},
347 {"y", []*Node{}, 0},
349 "z",
350 []*Node{
351 {"u", nil, 0},
352 {"v", nil, 0},
363 func walkTree(n *Node, path string, f func(path string, n *Node)) {
364 f(path, n)
365 for _, e := range n.entries {
366 walkTree(e, filepath.Join(path, e.name), f)
370 func makeTree(t *testing.T) {
371 walkTree(tree, tree.name, func(path string, n *Node) {
372 if n.entries == nil {
373 fd, err := os.Create(path)
374 if err != nil {
375 t.Errorf("makeTree: %v", err)
376 return
378 fd.Close()
379 } else {
380 os.Mkdir(path, 0770)
385 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
387 func checkMarks(t *testing.T, report bool) {
388 walkTree(tree, tree.name, func(path string, n *Node) {
389 if n.mark != 1 && report {
390 t.Errorf("node %s mark = %d; expected 1", path, n.mark)
392 n.mark = 0
396 // Assumes that each node name is unique. Good enough for a test.
397 // If clear is true, any incoming error is cleared before return. The errors
398 // are always accumulated, though.
399 func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
400 name := d.Name()
401 walkTree(tree, tree.name, func(path string, n *Node) {
402 if n.name == name {
403 n.mark++
406 if err != nil {
407 *errors = append(*errors, err)
408 if clear {
409 return nil
411 return err
413 return nil
416 // chdir changes the current working directory to the named directory,
417 // and then restore the original working directory at the end of the test.
418 func chdir(t *testing.T, dir string) {
419 olddir, err := os.Getwd()
420 if err != nil {
421 t.Fatalf("getwd %s: %v", dir, err)
423 if err := os.Chdir(dir); err != nil {
424 t.Fatalf("chdir %s: %v", dir, err)
427 t.Cleanup(func() {
428 if err := os.Chdir(olddir); err != nil {
429 t.Errorf("restore original working directory %s: %v", olddir, err)
430 os.Exit(1)
435 func chtmpdir(t *testing.T) (restore func()) {
436 oldwd, err := os.Getwd()
437 if err != nil {
438 t.Fatalf("chtmpdir: %v", err)
440 d, err := os.MkdirTemp("", "test")
441 if err != nil {
442 t.Fatalf("chtmpdir: %v", err)
444 if err := os.Chdir(d); err != nil {
445 t.Fatalf("chtmpdir: %v", err)
447 return func() {
448 if err := os.Chdir(oldwd); err != nil {
449 t.Fatalf("chtmpdir: %v", err)
451 os.RemoveAll(d)
455 // tempDirCanonical returns a temporary directory for the test to use, ensuring
456 // that the returned path does not contain symlinks.
457 func tempDirCanonical(t *testing.T) string {
458 dir := t.TempDir()
460 cdir, err := filepath.EvalSymlinks(dir)
461 if err != nil {
462 t.Errorf("tempDirCanonical: %v", err)
465 return cdir
468 func TestWalk(t *testing.T) {
469 walk := func(root string, fn fs.WalkDirFunc) error {
470 return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
471 return fn(path, &statDirEntry{info}, err)
474 testWalk(t, walk, 1)
477 type statDirEntry struct {
478 info fs.FileInfo
481 func (d *statDirEntry) Name() string { return d.info.Name() }
482 func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
483 func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
484 func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
486 func TestWalkDir(t *testing.T) {
487 testWalk(t, filepath.WalkDir, 2)
490 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
491 if runtime.GOOS == "ios" {
492 restore := chtmpdir(t)
493 defer restore()
496 tmpDir := t.TempDir()
498 origDir, err := os.Getwd()
499 if err != nil {
500 t.Fatal("finding working dir:", err)
502 if err = os.Chdir(tmpDir); err != nil {
503 t.Fatal("entering temp dir:", err)
505 defer os.Chdir(origDir)
507 makeTree(t)
508 errors := make([]error, 0, 10)
509 clear := true
510 markFn := func(path string, d fs.DirEntry, err error) error {
511 return mark(d, err, &errors, clear)
513 // Expect no errors.
514 err = walk(tree.name, markFn)
515 if err != nil {
516 t.Fatalf("no error expected, found: %s", err)
518 if len(errors) != 0 {
519 t.Fatalf("unexpected errors: %s", errors)
521 checkMarks(t, true)
522 errors = errors[0:0]
524 t.Run("PermErr", func(t *testing.T) {
525 // Test permission errors. Only possible if we're not root
526 // and only on some file systems (AFS, FAT). To avoid errors during
527 // all.bash on those file systems, skip during go test -short.
528 if runtime.GOOS == "windows" {
529 t.Skip("skipping on Windows")
531 if os.Getuid() == 0 {
532 t.Skip("skipping as root")
534 if testing.Short() {
535 t.Skip("skipping in short mode")
538 // introduce 2 errors: chmod top-level directories to 0
539 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
540 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
542 // 3) capture errors, expect two.
543 // mark respective subtrees manually
544 markTree(tree.entries[1])
545 markTree(tree.entries[3])
546 // correct double-marking of directory itself
547 tree.entries[1].mark -= errVisit
548 tree.entries[3].mark -= errVisit
549 err := walk(tree.name, markFn)
550 if err != nil {
551 t.Fatalf("expected no error return from Walk, got %s", err)
553 if len(errors) != 2 {
554 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
556 // the inaccessible subtrees were marked manually
557 checkMarks(t, true)
558 errors = errors[0:0]
560 // 4) capture errors, stop after first error.
561 // mark respective subtrees manually
562 markTree(tree.entries[1])
563 markTree(tree.entries[3])
564 // correct double-marking of directory itself
565 tree.entries[1].mark -= errVisit
566 tree.entries[3].mark -= errVisit
567 clear = false // error will stop processing
568 err = walk(tree.name, markFn)
569 if err == nil {
570 t.Fatalf("expected error return from Walk")
572 if len(errors) != 1 {
573 t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
575 // the inaccessible subtrees were marked manually
576 checkMarks(t, false)
577 errors = errors[0:0]
579 // restore permissions
580 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
581 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
585 func touch(t *testing.T, name string) {
586 f, err := os.Create(name)
587 if err != nil {
588 t.Fatal(err)
590 if err := f.Close(); err != nil {
591 t.Fatal(err)
595 func TestWalkSkipDirOnFile(t *testing.T) {
596 td := t.TempDir()
598 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
599 t.Fatal(err)
601 touch(t, filepath.Join(td, "dir/foo1"))
602 touch(t, filepath.Join(td, "dir/foo2"))
604 sawFoo2 := false
605 walker := func(path string) error {
606 if strings.HasSuffix(path, "foo2") {
607 sawFoo2 = true
609 if strings.HasSuffix(path, "foo1") {
610 return filepath.SkipDir
612 return nil
614 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
615 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
617 check := func(t *testing.T, walk func(root string) error, root string) {
618 t.Helper()
619 sawFoo2 = false
620 err := walk(root)
621 if err != nil {
622 t.Fatal(err)
624 if sawFoo2 {
625 t.Errorf("SkipDir on file foo1 did not block processing of foo2")
629 t.Run("Walk", func(t *testing.T) {
630 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
631 check(t, Walk, td)
632 check(t, Walk, filepath.Join(td, "dir"))
634 t.Run("WalkDir", func(t *testing.T) {
635 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
636 check(t, WalkDir, td)
637 check(t, WalkDir, filepath.Join(td, "dir"))
641 func TestWalkFileError(t *testing.T) {
642 td := t.TempDir()
644 touch(t, filepath.Join(td, "foo"))
645 touch(t, filepath.Join(td, "bar"))
646 dir := filepath.Join(td, "dir")
647 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
648 t.Fatal(err)
650 touch(t, filepath.Join(dir, "baz"))
651 touch(t, filepath.Join(dir, "stat-error"))
652 defer func() {
653 *filepath.LstatP = os.Lstat
655 statErr := errors.New("some stat error")
656 *filepath.LstatP = func(path string) (fs.FileInfo, error) {
657 if strings.HasSuffix(path, "stat-error") {
658 return nil, statErr
660 return os.Lstat(path)
662 got := map[string]error{}
663 err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
664 rel, _ := filepath.Rel(td, path)
665 got[filepath.ToSlash(rel)] = err
666 return nil
668 if err != nil {
669 t.Errorf("Walk error: %v", err)
671 want := map[string]error{
672 ".": nil,
673 "foo": nil,
674 "bar": nil,
675 "dir": nil,
676 "dir/baz": nil,
677 "dir/stat-error": statErr,
679 if !reflect.DeepEqual(got, want) {
680 t.Errorf("Walked %#v; want %#v", got, want)
684 var basetests = []PathTest{
685 {"", "."},
686 {".", "."},
687 {"/.", "."},
688 {"/", "/"},
689 {"////", "/"},
690 {"x/", "x"},
691 {"abc", "abc"},
692 {"abc/def", "def"},
693 {"a/b/.x", ".x"},
694 {"a/b/c.", "c."},
695 {"a/b/c.x", "c.x"},
698 var winbasetests = []PathTest{
699 {`c:\`, `\`},
700 {`c:.`, `.`},
701 {`c:\a\b`, `b`},
702 {`c:a\b`, `b`},
703 {`c:a\b\c`, `c`},
704 {`\\host\share\`, `\`},
705 {`\\host\share\a`, `a`},
706 {`\\host\share\a\b`, `b`},
709 func TestBase(t *testing.T) {
710 tests := basetests
711 if runtime.GOOS == "windows" {
712 // make unix tests work on windows
713 for i := range tests {
714 tests[i].result = filepath.Clean(tests[i].result)
716 // add windows specific tests
717 tests = append(tests, winbasetests...)
719 for _, test := range tests {
720 if s := filepath.Base(test.path); s != test.result {
721 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
726 var dirtests = []PathTest{
727 {"", "."},
728 {".", "."},
729 {"/.", "/"},
730 {"/", "/"},
731 {"////", "/"},
732 {"/foo", "/"},
733 {"x/", "x"},
734 {"abc", "."},
735 {"abc/def", "abc"},
736 {"a/b/.x", "a/b"},
737 {"a/b/c.", "a/b"},
738 {"a/b/c.x", "a/b"},
741 var windirtests = []PathTest{
742 {`c:\`, `c:\`},
743 {`c:.`, `c:.`},
744 {`c:\a\b`, `c:\a`},
745 {`c:a\b`, `c:a`},
746 {`c:a\b\c`, `c:a\b`},
747 {`\\host\share`, `\\host\share`},
748 {`\\host\share\`, `\\host\share\`},
749 {`\\host\share\a`, `\\host\share\`},
750 {`\\host\share\a\b`, `\\host\share\a`},
753 func TestDir(t *testing.T) {
754 tests := dirtests
755 if runtime.GOOS == "windows" {
756 // make unix tests work on windows
757 for i := range tests {
758 tests[i].result = filepath.Clean(tests[i].result)
760 // add windows specific tests
761 tests = append(tests, windirtests...)
763 for _, test := range tests {
764 if s := filepath.Dir(test.path); s != test.result {
765 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
770 type IsAbsTest struct {
771 path string
772 isAbs bool
775 var isabstests = []IsAbsTest{
776 {"", false},
777 {"/", true},
778 {"/usr/bin/gcc", true},
779 {"..", false},
780 {"/a/../bb", true},
781 {".", false},
782 {"./", false},
783 {"lala", false},
786 var winisabstests = []IsAbsTest{
787 {`C:\`, true},
788 {`c\`, false},
789 {`c::`, false},
790 {`c:`, false},
791 {`/`, false},
792 {`\`, false},
793 {`\Windows`, false},
794 {`c:a\b`, false},
795 {`c:\a\b`, true},
796 {`c:/a/b`, true},
797 {`\\host\share`, true},
798 {`\\host\share\`, true},
799 {`\\host\share\foo`, true},
800 {`//host/share/foo/bar`, true},
803 func TestIsAbs(t *testing.T) {
804 var tests []IsAbsTest
805 if runtime.GOOS == "windows" {
806 tests = append(tests, winisabstests...)
807 // All non-windows tests should fail, because they have no volume letter.
808 for _, test := range isabstests {
809 tests = append(tests, IsAbsTest{test.path, false})
811 // All non-windows test should work as intended if prefixed with volume letter.
812 for _, test := range isabstests {
813 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
815 // Test reserved names.
816 tests = append(tests, IsAbsTest{os.DevNull, true})
817 tests = append(tests, IsAbsTest{"NUL", true})
818 tests = append(tests, IsAbsTest{"nul", true})
819 tests = append(tests, IsAbsTest{"CON", true})
820 } else {
821 tests = isabstests
824 for _, test := range tests {
825 if r := filepath.IsAbs(test.path); r != test.isAbs {
826 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
831 type EvalSymlinksTest struct {
832 // If dest is empty, the path is created; otherwise the dest is symlinked to the path.
833 path, dest string
836 var EvalSymlinksTestDirs = []EvalSymlinksTest{
837 {"test", ""},
838 {"test/dir", ""},
839 {"test/dir/link3", "../../"},
840 {"test/link1", "../test"},
841 {"test/link2", "dir"},
842 {"test/linkabs", "/"},
843 {"test/link4", "../test2"},
844 {"test2", "test/dir"},
845 // Issue 23444.
846 {"src", ""},
847 {"src/pool", ""},
848 {"src/pool/test", ""},
849 {"src/versions", ""},
850 {"src/versions/current", "../../version"},
851 {"src/versions/v1", ""},
852 {"src/versions/v1/modules", ""},
853 {"src/versions/v1/modules/test", "../../../pool/test"},
854 {"version", "src/versions/v1"},
857 var EvalSymlinksTests = []EvalSymlinksTest{
858 {"test", "test"},
859 {"test/dir", "test/dir"},
860 {"test/dir/../..", "."},
861 {"test/link1", "test"},
862 {"test/link2", "test/dir"},
863 {"test/link1/dir", "test/dir"},
864 {"test/link2/..", "test"},
865 {"test/dir/link3", "."},
866 {"test/link2/link3/test", "test"},
867 {"test/linkabs", "/"},
868 {"test/link4/..", "test"},
869 {"src/versions/current/modules/test", "src/pool/test"},
872 // simpleJoin builds a file name from the directory and path.
873 // It does not use Join because we don't want ".." to be evaluated.
874 func simpleJoin(dir, path string) string {
875 return dir + string(filepath.Separator) + path
878 func testEvalSymlinks(t *testing.T, path, want string) {
879 have, err := filepath.EvalSymlinks(path)
880 if err != nil {
881 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
882 return
884 if filepath.Clean(have) != filepath.Clean(want) {
885 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
889 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
890 cwd, err := os.Getwd()
891 if err != nil {
892 t.Fatal(err)
894 defer func() {
895 err := os.Chdir(cwd)
896 if err != nil {
897 t.Fatal(err)
901 err = os.Chdir(wd)
902 if err != nil {
903 t.Fatal(err)
906 have, err := filepath.EvalSymlinks(path)
907 if err != nil {
908 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
909 return
911 if filepath.Clean(have) != filepath.Clean(want) {
912 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
916 func TestEvalSymlinks(t *testing.T) {
917 testenv.MustHaveSymlink(t)
919 tmpDir := t.TempDir()
921 // /tmp may itself be a symlink! Avoid the confusion, although
922 // it means trusting the thing we're testing.
923 var err error
924 tmpDir, err = filepath.EvalSymlinks(tmpDir)
925 if err != nil {
926 t.Fatal("eval symlink for tmp dir:", err)
929 // Create the symlink farm using relative paths.
930 for _, d := range EvalSymlinksTestDirs {
931 var err error
932 path := simpleJoin(tmpDir, d.path)
933 if d.dest == "" {
934 err = os.Mkdir(path, 0755)
935 } else {
936 err = os.Symlink(d.dest, path)
938 if err != nil {
939 t.Fatal(err)
943 // Evaluate the symlink farm.
944 for _, test := range EvalSymlinksTests {
945 path := simpleJoin(tmpDir, test.path)
947 dest := simpleJoin(tmpDir, test.dest)
948 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
949 dest = test.dest
951 testEvalSymlinks(t, path, dest)
953 // test EvalSymlinks(".")
954 testEvalSymlinksAfterChdir(t, path, ".", ".")
956 // test EvalSymlinks("C:.") on Windows
957 if runtime.GOOS == "windows" {
958 volDot := filepath.VolumeName(tmpDir) + "."
959 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
962 // test EvalSymlinks(".."+path)
963 dotdotPath := simpleJoin("..", test.dest)
964 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
965 dotdotPath = test.dest
967 testEvalSymlinksAfterChdir(t,
968 simpleJoin(tmpDir, "test"),
969 simpleJoin("..", test.path),
970 dotdotPath)
972 // test EvalSymlinks(p) where p is relative path
973 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
977 func TestEvalSymlinksIsNotExist(t *testing.T) {
978 testenv.MustHaveSymlink(t)
980 defer chtmpdir(t)()
982 _, err := filepath.EvalSymlinks("notexist")
983 if !os.IsNotExist(err) {
984 t.Errorf("expected the file is not found, got %v\n", err)
987 err = os.Symlink("notexist", "link")
988 if err != nil {
989 t.Fatal(err)
991 defer os.Remove("link")
993 _, err = filepath.EvalSymlinks("link")
994 if !os.IsNotExist(err) {
995 t.Errorf("expected the file is not found, got %v\n", err)
999 func TestIssue13582(t *testing.T) {
1000 testenv.MustHaveSymlink(t)
1002 tmpDir := t.TempDir()
1004 dir := filepath.Join(tmpDir, "dir")
1005 err := os.Mkdir(dir, 0755)
1006 if err != nil {
1007 t.Fatal(err)
1009 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1010 err = os.Symlink(dir, linkToDir)
1011 if err != nil {
1012 t.Fatal(err)
1014 file := filepath.Join(linkToDir, "file")
1015 err = os.WriteFile(file, nil, 0644)
1016 if err != nil {
1017 t.Fatal(err)
1019 link1 := filepath.Join(linkToDir, "link1")
1020 err = os.Symlink(file, link1)
1021 if err != nil {
1022 t.Fatal(err)
1024 link2 := filepath.Join(linkToDir, "link2")
1025 err = os.Symlink(link1, link2)
1026 if err != nil {
1027 t.Fatal(err)
1030 // /tmp may itself be a symlink!
1031 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1032 if err != nil {
1033 t.Fatal(err)
1035 realDir := filepath.Join(realTmpDir, "dir")
1036 realFile := filepath.Join(realDir, "file")
1038 tests := []struct {
1039 path, want string
1041 {dir, realDir},
1042 {linkToDir, realDir},
1043 {file, realFile},
1044 {link1, realFile},
1045 {link2, realFile},
1047 for i, test := range tests {
1048 have, err := filepath.EvalSymlinks(test.path)
1049 if err != nil {
1050 t.Fatal(err)
1052 if have != test.want {
1053 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1058 // Test directories relative to temporary directory.
1059 // The tests are run in absTestDirs[0].
1060 var absTestDirs = []string{
1061 "a",
1062 "a/b",
1063 "a/b/c",
1066 // Test paths relative to temporary directory. $ expands to the directory.
1067 // The tests are run in absTestDirs[0].
1068 // We create absTestDirs first.
1069 var absTests = []string{
1070 ".",
1071 "b",
1072 "b/",
1073 "../a",
1074 "../a/b",
1075 "../a/b/./c/../../.././a",
1076 "../a/b/./c/../../.././a/",
1077 "$",
1078 "$/.",
1079 "$/a/../a/b",
1080 "$/a/b/c/../../.././a",
1081 "$/a/b/c/../../.././a/",
1084 func TestAbs(t *testing.T) {
1085 root := t.TempDir()
1086 wd, err := os.Getwd()
1087 if err != nil {
1088 t.Fatal("getwd failed: ", err)
1090 err = os.Chdir(root)
1091 if err != nil {
1092 t.Fatal("chdir failed: ", err)
1094 defer os.Chdir(wd)
1096 for _, dir := range absTestDirs {
1097 err = os.Mkdir(dir, 0777)
1098 if err != nil {
1099 t.Fatal("Mkdir failed: ", err)
1103 if runtime.GOOS == "windows" {
1104 vol := filepath.VolumeName(root)
1105 var extra []string
1106 for _, path := range absTests {
1107 if strings.Contains(path, "$") {
1108 continue
1110 path = vol + path
1111 extra = append(extra, path)
1113 absTests = append(absTests, extra...)
1116 err = os.Chdir(absTestDirs[0])
1117 if err != nil {
1118 t.Fatal("chdir failed: ", err)
1121 for _, path := range absTests {
1122 path = strings.ReplaceAll(path, "$", root)
1123 info, err := os.Stat(path)
1124 if err != nil {
1125 t.Errorf("%s: %s", path, err)
1126 continue
1129 abspath, err := filepath.Abs(path)
1130 if err != nil {
1131 t.Errorf("Abs(%q) error: %v", path, err)
1132 continue
1134 absinfo, err := os.Stat(abspath)
1135 if err != nil || !os.SameFile(absinfo, info) {
1136 t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1138 if !filepath.IsAbs(abspath) {
1139 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1141 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1142 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1147 // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
1148 // We test it separately from all other absTests because the empty string is not
1149 // a valid path, so it can't be used with os.Stat.
1150 func TestAbsEmptyString(t *testing.T) {
1151 root := t.TempDir()
1153 wd, err := os.Getwd()
1154 if err != nil {
1155 t.Fatal("getwd failed: ", err)
1157 err = os.Chdir(root)
1158 if err != nil {
1159 t.Fatal("chdir failed: ", err)
1161 defer os.Chdir(wd)
1163 info, err := os.Stat(root)
1164 if err != nil {
1165 t.Fatalf("%s: %s", root, err)
1168 abspath, err := filepath.Abs("")
1169 if err != nil {
1170 t.Fatalf(`Abs("") error: %v`, err)
1172 absinfo, err := os.Stat(abspath)
1173 if err != nil || !os.SameFile(absinfo, info) {
1174 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1176 if !filepath.IsAbs(abspath) {
1177 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1179 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1180 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1184 type RelTests struct {
1185 root, path, want string
1188 var reltests = []RelTests{
1189 {"a/b", "a/b", "."},
1190 {"a/b/.", "a/b", "."},
1191 {"a/b", "a/b/.", "."},
1192 {"./a/b", "a/b", "."},
1193 {"a/b", "./a/b", "."},
1194 {"ab/cd", "ab/cde", "../cde"},
1195 {"ab/cd", "ab/c", "../c"},
1196 {"a/b", "a/b/c/d", "c/d"},
1197 {"a/b", "a/b/../c", "../c"},
1198 {"a/b/../c", "a/b", "../b"},
1199 {"a/b/c", "a/c/d", "../../c/d"},
1200 {"a/b", "c/d", "../../c/d"},
1201 {"a/b/c/d", "a/b", "../.."},
1202 {"a/b/c/d", "a/b/", "../.."},
1203 {"a/b/c/d/", "a/b", "../.."},
1204 {"a/b/c/d/", "a/b/", "../.."},
1205 {"../../a/b", "../../a/b/c/d", "c/d"},
1206 {"/a/b", "/a/b", "."},
1207 {"/a/b/.", "/a/b", "."},
1208 {"/a/b", "/a/b/.", "."},
1209 {"/ab/cd", "/ab/cde", "../cde"},
1210 {"/ab/cd", "/ab/c", "../c"},
1211 {"/a/b", "/a/b/c/d", "c/d"},
1212 {"/a/b", "/a/b/../c", "../c"},
1213 {"/a/b/../c", "/a/b", "../b"},
1214 {"/a/b/c", "/a/c/d", "../../c/d"},
1215 {"/a/b", "/c/d", "../../c/d"},
1216 {"/a/b/c/d", "/a/b", "../.."},
1217 {"/a/b/c/d", "/a/b/", "../.."},
1218 {"/a/b/c/d/", "/a/b", "../.."},
1219 {"/a/b/c/d/", "/a/b/", "../.."},
1220 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1221 {".", "a/b", "a/b"},
1222 {".", "..", ".."},
1224 // can't do purely lexically
1225 {"..", ".", "err"},
1226 {"..", "a", "err"},
1227 {"../..", "..", "err"},
1228 {"a", "/a", "err"},
1229 {"/a", "a", "err"},
1232 var winreltests = []RelTests{
1233 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1234 {`C:\`, `D:\`, `err`},
1235 {`C:`, `D:`, `err`},
1236 {`C:\Projects`, `c:\projects\src`, `src`},
1237 {`C:\Projects`, `c:\projects`, `.`},
1238 {`C:\Projects\a\..`, `c:\projects`, `.`},
1239 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1242 func TestRel(t *testing.T) {
1243 tests := append([]RelTests{}, reltests...)
1244 if runtime.GOOS == "windows" {
1245 for i := range tests {
1246 tests[i].want = filepath.FromSlash(tests[i].want)
1248 tests = append(tests, winreltests...)
1250 for _, test := range tests {
1251 got, err := filepath.Rel(test.root, test.path)
1252 if test.want == "err" {
1253 if err == nil {
1254 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1256 continue
1258 if err != nil {
1259 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1261 if got != test.want {
1262 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1267 type VolumeNameTest struct {
1268 path string
1269 vol string
1272 var volumenametests = []VolumeNameTest{
1273 {`c:/foo/bar`, `c:`},
1274 {`c:`, `c:`},
1275 {`2:`, ``},
1276 {``, ``},
1277 {`\\\host`, ``},
1278 {`\\\host\`, ``},
1279 {`\\\host\share`, ``},
1280 {`\\\host\\share`, ``},
1281 {`\\host`, ``},
1282 {`//host`, ``},
1283 {`\\host\`, ``},
1284 {`//host/`, ``},
1285 {`\\host\share`, `\\host\share`},
1286 {`//host/share`, `//host/share`},
1287 {`\\host\share\`, `\\host\share`},
1288 {`//host/share/`, `//host/share`},
1289 {`\\host\share\foo`, `\\host\share`},
1290 {`//host/share/foo`, `//host/share`},
1291 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1292 {`//host/share//foo///bar////baz`, `//host/share`},
1293 {`\\host\share\foo\..\bar`, `\\host\share`},
1294 {`//host/share/foo/../bar`, `//host/share`},
1297 func TestVolumeName(t *testing.T) {
1298 if runtime.GOOS != "windows" {
1299 return
1301 for _, v := range volumenametests {
1302 if vol := filepath.VolumeName(v.path); vol != v.vol {
1303 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1308 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1309 if runtime.GOOS != "windows" {
1310 return
1312 wd, _ := os.Getwd()
1313 if len(wd) < 3 {
1314 t.Errorf("Current directory path %q is too short", wd)
1316 lp := strings.ToLower(wd)
1317 up := strings.ToUpper(wd)
1318 flp, err := filepath.EvalSymlinks(lp)
1319 if err != nil {
1320 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1322 fup, err := filepath.EvalSymlinks(up)
1323 if err != nil {
1324 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1326 if flp != fup {
1327 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1331 func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
1332 t.Skip("skipping test because gccgo sources are arranged differently.")
1333 if runtime.GOOS == "ios" {
1334 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1336 root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test")
1337 if err != nil {
1338 t.Fatal(err)
1340 bugs := filepath.Join(root, "fixedbugs")
1341 ken := filepath.Join(root, "ken")
1342 seenBugs := false
1343 seenKen := false
1344 err = filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1345 if err != nil {
1346 t.Fatal(err)
1349 switch pth {
1350 case bugs:
1351 seenBugs = true
1352 return filepath.SkipDir
1353 case ken:
1354 if !seenBugs {
1355 t.Fatal("filepath.Walk out of order - ken before fixedbugs")
1357 seenKen = true
1359 return nil
1361 if err != nil {
1362 t.Fatal(err)
1364 if !seenKen {
1365 t.Fatalf("%q not seen", ken)
1369 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1370 tmpdir := t.TempDir()
1372 wd, err := os.Getwd()
1373 if err != nil {
1374 t.Fatal(err)
1376 defer os.Chdir(wd)
1378 err = os.Chdir(tmpdir)
1379 if err != nil {
1380 t.Fatal(err)
1383 err = mklink(tmpdir, "link")
1384 if err != nil {
1385 t.Fatal(err)
1388 var visited []string
1389 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1390 if err != nil {
1391 t.Fatal(err)
1393 rel, err := filepath.Rel(tmpdir, path)
1394 if err != nil {
1395 t.Fatal(err)
1397 visited = append(visited, rel)
1398 return nil
1400 if err != nil {
1401 t.Fatal(err)
1403 sort.Strings(visited)
1404 want := []string{".", "link"}
1405 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1406 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1410 func TestWalkSymlink(t *testing.T) {
1411 testenv.MustHaveSymlink(t)
1412 testWalkSymlink(t, os.Symlink)
1415 func TestIssue29372(t *testing.T) {
1416 tmpDir := t.TempDir()
1418 path := filepath.Join(tmpDir, "file.txt")
1419 err := os.WriteFile(path, nil, 0644)
1420 if err != nil {
1421 t.Fatal(err)
1424 pathSeparator := string(filepath.Separator)
1425 tests := []string{
1426 path + strings.Repeat(pathSeparator, 1),
1427 path + strings.Repeat(pathSeparator, 2),
1428 path + strings.Repeat(pathSeparator, 1) + ".",
1429 path + strings.Repeat(pathSeparator, 2) + ".",
1430 path + strings.Repeat(pathSeparator, 1) + "..",
1431 path + strings.Repeat(pathSeparator, 2) + "..",
1434 for i, test := range tests {
1435 _, err = filepath.EvalSymlinks(test)
1436 if err != syscall.ENOTDIR {
1437 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1442 // Issue 30520 part 1.
1443 func TestEvalSymlinksAboveRoot(t *testing.T) {
1444 testenv.MustHaveSymlink(t)
1446 t.Parallel()
1448 tmpDir := t.TempDir()
1450 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1451 if err != nil {
1452 t.Fatal(err)
1455 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1456 t.Fatal(err)
1458 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1459 t.Fatal(err)
1461 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1462 t.Fatal(err)
1465 // Count the number of ".." elements to get to the root directory.
1466 vol := filepath.VolumeName(evalTmpDir)
1467 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1468 var dd []string
1469 for i := 0; i < c+2; i++ {
1470 dd = append(dd, "..")
1473 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1475 // Try different numbers of "..".
1476 for _, i := range []int{c, c + 1, c + 2} {
1477 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1478 resolved, err := filepath.EvalSymlinks(check)
1479 switch {
1480 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1481 // On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
1482 testenv.SkipFlaky(t, 37910)
1483 case err != nil:
1484 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1485 case !strings.HasSuffix(resolved, wantSuffix):
1486 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1487 default:
1488 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1493 // Issue 30520 part 2.
1494 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1495 testenv.MustHaveSymlink(t)
1497 tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1498 if err != nil {
1499 t.Fatal(err)
1501 defer os.RemoveAll(tmpDir)
1502 chdir(t, tmpDir)
1504 subdir := filepath.Join("a", "b")
1505 if err := os.MkdirAll(subdir, 0777); err != nil {
1506 t.Fatal(err)
1508 if err := os.Symlink(subdir, "c"); err != nil {
1509 t.Fatal(err)
1511 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1512 t.Fatal(err)
1515 subdir = filepath.Join("d", "e", "f")
1516 if err := os.MkdirAll(subdir, 0777); err != nil {
1517 t.Fatal(err)
1519 if err := os.Chdir(subdir); err != nil {
1520 t.Fatal(err)
1523 check := filepath.Join("..", "..", "..", "c", "file")
1524 wantSuffix := filepath.Join("a", "b", "file")
1525 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1526 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1527 } else if !strings.HasSuffix(resolved, wantSuffix) {
1528 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1529 } else {
1530 t.Logf("EvalSymlinks(%q) = %q", check, resolved)