1
2
3
4
5 package filepath_test
6
7 import (
8 "errors"
9 "fmt"
10 "internal/testenv"
11 "io/fs"
12 "os"
13 "path/filepath"
14 "reflect"
15 "runtime"
16 "sort"
17 "strings"
18 "syscall"
19 "testing"
20 )
21
22 type PathTest struct {
23 path, result string
24 }
25
26 var cleantests = []PathTest{
27
28 {"abc", "abc"},
29 {"abc/def", "abc/def"},
30 {"a/b/c", "a/b/c"},
31 {".", "."},
32 {"..", ".."},
33 {"../..", "../.."},
34 {"../../abc", "../../abc"},
35 {"/abc", "/abc"},
36 {"/", "/"},
37
38
39 {"", "."},
40
41
42 {"abc/", "abc"},
43 {"abc/def/", "abc/def"},
44 {"a/b/c/", "a/b/c"},
45 {"./", "."},
46 {"../", ".."},
47 {"../../", "../.."},
48 {"/abc/", "/abc"},
49
50
51 {"abc//def//ghi", "abc/def/ghi"},
52 {"//abc", "/abc"},
53 {"///abc", "/abc"},
54 {"//abc//", "/abc"},
55 {"abc//", "abc"},
56
57
58 {"abc/./def", "abc/def"},
59 {"/./abc/def", "/abc/def"},
60 {"abc/.", "abc"},
61
62
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"},
72
73
74 {"abc/./../def", "def"},
75 {"abc//./../def", "def"},
76 {"abc/../../././../def", "../../def"},
77 }
78
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`},
96 }
97
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)
103 }
104 tests = append(tests, wincleantests...)
105 }
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)
109 }
110 if s := filepath.Clean(test.result); s != test.result {
111 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
112 }
113 }
114
115 if testing.Short() {
116 t.Skip("skipping malloc count in short mode")
117 }
118 if runtime.GOMAXPROCS(0) > 1 {
119 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
120 return
121 }
122
123 for _, test := range tests {
124 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
125 if allocs > 0 {
126 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
127 }
128 }
129 }
130
131 const sep = filepath.Separator
132
133 var slashtests = []PathTest{
134 {"", ""},
135 {"/", string(sep)},
136 {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
137 {"a//b", string([]byte{'a', sep, sep, 'b'})},
138 }
139
140 func TestFromAndToSlash(t *testing.T) {
141 for _, test := range slashtests {
142 if s := filepath.FromSlash(test.path); s != test.result {
143 t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
144 }
145 if s := filepath.ToSlash(test.result); s != test.path {
146 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
147 }
148 }
149 }
150
151 type SplitListTest struct {
152 list string
153 result []string
154 }
155
156 const lsep = filepath.ListSeparator
157
158 var splitlisttests = []SplitListTest{
159 {"", []string{}},
160 {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
161 {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
162 }
163
164 var winsplitlisttests = []SplitListTest{
165
166 {`"a"`, []string{`a`}},
167
168
169 {`";"`, []string{`;`}},
170 {`"a;b"`, []string{`a;b`}},
171 {`";";`, []string{`;`, ``}},
172 {`;";"`, []string{``, `;`}},
173
174
175 {`a";"b`, []string{`a;b`}},
176 {`a; ""b`, []string{`a`, ` b`}},
177 {`"a;b`, []string{`a;b`}},
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";c`, []string{`a`, `b;c`}},
183 {`"a";b";c`, []string{`a`, `b;c`}},
184 }
185
186 func TestSplitList(t *testing.T) {
187 tests := splitlisttests
188 if runtime.GOOS == "windows" {
189 tests = append(tests, winsplitlisttests...)
190 }
191 for _, test := range tests {
192 if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
193 t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
194 }
195 }
196 }
197
198 type SplitTest struct {
199 path, dir, file string
200 }
201
202 var unixsplittests = []SplitTest{
203 {"a/b", "a/", "b"},
204 {"a/b/", "a/b/", ""},
205 {"a/", "a/", ""},
206 {"a", "", "a"},
207 {"/", "/", ""},
208 }
209
210 var winsplittests = []SplitTest{
211 {`c:`, `c:`, ``},
212 {`c:/`, `c:/`, ``},
213 {`c:/foo`, `c:/`, `foo`},
214 {`c:/foo/bar`, `c:/foo/`, `bar`},
215 {`//host/share`, `//host/share`, ``},
216 {`//host/share/`, `//host/share/`, ``},
217 {`//host/share/foo`, `//host/share/`, `foo`},
218 {`\\host\share`, `\\host\share`, ``},
219 {`\\host\share\`, `\\host\share\`, ``},
220 {`\\host\share\foo`, `\\host\share\`, `foo`},
221 }
222
223 func TestSplit(t *testing.T) {
224 var splittests []SplitTest
225 splittests = unixsplittests
226 if runtime.GOOS == "windows" {
227 splittests = append(splittests, winsplittests...)
228 }
229 for _, test := range splittests {
230 if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
231 t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
232 }
233 }
234 }
235
236 type JoinTest struct {
237 elem []string
238 path string
239 }
240
241 var jointests = []JoinTest{
242
243 {[]string{}, ""},
244
245
246 {[]string{""}, ""},
247 {[]string{"/"}, "/"},
248 {[]string{"a"}, "a"},
249
250
251 {[]string{"a", "b"}, "a/b"},
252 {[]string{"a", ""}, "a"},
253 {[]string{"", "b"}, "b"},
254 {[]string{"/", "a"}, "/a"},
255 {[]string{"/", "a/b"}, "/a/b"},
256 {[]string{"/", ""}, "/"},
257 {[]string{"//", "a"}, "/a"},
258 {[]string{"/a", "b"}, "/a/b"},
259 {[]string{"a/", "b"}, "a/b"},
260 {[]string{"a/", ""}, "a"},
261 {[]string{"", ""}, ""},
262
263
264 {[]string{"/", "a", "b"}, "/a/b"},
265 }
266
267 var winjointests = []JoinTest{
268 {[]string{`directory`, `file`}, `directory\file`},
269 {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
270 {[]string{`C:\Windows\`, ``}, `C:\Windows`},
271 {[]string{`C:\`, `Windows`}, `C:\Windows`},
272 {[]string{`C:`, `a`}, `C:a`},
273 {[]string{`C:`, `a\b`}, `C:a\b`},
274 {[]string{`C:`, `a`, `b`}, `C:a\b`},
275 {[]string{`C:`, ``, `b`}, `C:b`},
276 {[]string{`C:`, ``, ``, `b`}, `C:b`},
277 {[]string{`C:`, ``}, `C:.`},
278 {[]string{`C:`, ``, ``}, `C:.`},
279 {[]string{`C:.`, `a`}, `C:a`},
280 {[]string{`C:a`, `b`}, `C:a\b`},
281 {[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
282 {[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
283 {[]string{`\\host\share\foo`}, `\\host\share\foo`},
284 {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
285 {[]string{`\`}, `\`},
286 {[]string{`\`, ``}, `\`},
287 {[]string{`\`, `a`}, `\a`},
288 {[]string{`\\`, `a`}, `\a`},
289 {[]string{`\`, `a`, `b`}, `\a\b`},
290 {[]string{`\\`, `a`, `b`}, `\a\b`},
291 {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
292 {[]string{`\\a`, `b`, `c`}, `\a\b\c`},
293 {[]string{`\\a\`, `b`, `c`}, `\a\b\c`},
294 }
295
296 func TestJoin(t *testing.T) {
297 if runtime.GOOS == "windows" {
298 jointests = append(jointests, winjointests...)
299 }
300 for _, test := range jointests {
301 expected := filepath.FromSlash(test.path)
302 if p := filepath.Join(test.elem...); p != expected {
303 t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
304 }
305 }
306 }
307
308 type ExtTest struct {
309 path, ext string
310 }
311
312 var exttests = []ExtTest{
313 {"path.go", ".go"},
314 {"path.pb.go", ".go"},
315 {"a.dir/b", ""},
316 {"a.dir/b.go", ".go"},
317 {"a.dir/", ""},
318 }
319
320 func TestExt(t *testing.T) {
321 for _, test := range exttests {
322 if x := filepath.Ext(test.path); x != test.ext {
323 t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
324 }
325 }
326 }
327
328 type Node struct {
329 name string
330 entries []*Node
331 mark int
332 }
333
334 var tree = &Node{
335 "testdata",
336 []*Node{
337 {"a", nil, 0},
338 {"b", []*Node{}, 0},
339 {"c", nil, 0},
340 {
341 "d",
342 []*Node{
343 {"x", nil, 0},
344 {"y", []*Node{}, 0},
345 {
346 "z",
347 []*Node{
348 {"u", nil, 0},
349 {"v", nil, 0},
350 },
351 0,
352 },
353 },
354 0,
355 },
356 },
357 0,
358 }
359
360 func walkTree(n *Node, path string, f func(path string, n *Node)) {
361 f(path, n)
362 for _, e := range n.entries {
363 walkTree(e, filepath.Join(path, e.name), f)
364 }
365 }
366
367 func makeTree(t *testing.T) {
368 walkTree(tree, tree.name, func(path string, n *Node) {
369 if n.entries == nil {
370 fd, err := os.Create(path)
371 if err != nil {
372 t.Errorf("makeTree: %v", err)
373 return
374 }
375 fd.Close()
376 } else {
377 os.Mkdir(path, 0770)
378 }
379 })
380 }
381
382 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
383
384 func checkMarks(t *testing.T, report bool) {
385 walkTree(tree, tree.name, func(path string, n *Node) {
386 if n.mark != 1 && report {
387 t.Errorf("node %s mark = %d; expected 1", path, n.mark)
388 }
389 n.mark = 0
390 })
391 }
392
393
394
395
396 func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
397 name := d.Name()
398 walkTree(tree, tree.name, func(path string, n *Node) {
399 if n.name == name {
400 n.mark++
401 }
402 })
403 if err != nil {
404 *errors = append(*errors, err)
405 if clear {
406 return nil
407 }
408 return err
409 }
410 return nil
411 }
412
413
414
415 func chdir(t *testing.T, dir string) {
416 olddir, err := os.Getwd()
417 if err != nil {
418 t.Fatalf("getwd %s: %v", dir, err)
419 }
420 if err := os.Chdir(dir); err != nil {
421 t.Fatalf("chdir %s: %v", dir, err)
422 }
423
424 t.Cleanup(func() {
425 if err := os.Chdir(olddir); err != nil {
426 t.Errorf("restore original working directory %s: %v", olddir, err)
427 os.Exit(1)
428 }
429 })
430 }
431
432 func chtmpdir(t *testing.T) (restore func()) {
433 oldwd, err := os.Getwd()
434 if err != nil {
435 t.Fatalf("chtmpdir: %v", err)
436 }
437 d, err := os.MkdirTemp("", "test")
438 if err != nil {
439 t.Fatalf("chtmpdir: %v", err)
440 }
441 if err := os.Chdir(d); err != nil {
442 t.Fatalf("chtmpdir: %v", err)
443 }
444 return func() {
445 if err := os.Chdir(oldwd); err != nil {
446 t.Fatalf("chtmpdir: %v", err)
447 }
448 os.RemoveAll(d)
449 }
450 }
451
452
453
454 func tempDirCanonical(t *testing.T) string {
455 dir := t.TempDir()
456
457 cdir, err := filepath.EvalSymlinks(dir)
458 if err != nil {
459 t.Errorf("tempDirCanonical: %v", err)
460 }
461
462 return cdir
463 }
464
465 func TestWalk(t *testing.T) {
466 walk := func(root string, fn fs.WalkDirFunc) error {
467 return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
468 return fn(path, &statDirEntry{info}, err)
469 })
470 }
471 testWalk(t, walk, 1)
472 }
473
474 type statDirEntry struct {
475 info fs.FileInfo
476 }
477
478 func (d *statDirEntry) Name() string { return d.info.Name() }
479 func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
480 func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
481 func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
482
483 func TestWalkDir(t *testing.T) {
484 testWalk(t, filepath.WalkDir, 2)
485 }
486
487 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
488 if runtime.GOOS == "ios" {
489 restore := chtmpdir(t)
490 defer restore()
491 }
492
493 tmpDir := t.TempDir()
494
495 origDir, err := os.Getwd()
496 if err != nil {
497 t.Fatal("finding working dir:", err)
498 }
499 if err = os.Chdir(tmpDir); err != nil {
500 t.Fatal("entering temp dir:", err)
501 }
502 defer os.Chdir(origDir)
503
504 makeTree(t)
505 errors := make([]error, 0, 10)
506 clear := true
507 markFn := func(path string, d fs.DirEntry, err error) error {
508 return mark(d, err, &errors, clear)
509 }
510
511 err = walk(tree.name, markFn)
512 if err != nil {
513 t.Fatalf("no error expected, found: %s", err)
514 }
515 if len(errors) != 0 {
516 t.Fatalf("unexpected errors: %s", errors)
517 }
518 checkMarks(t, true)
519 errors = errors[0:0]
520
521 t.Run("PermErr", func(t *testing.T) {
522
523
524
525 if runtime.GOOS == "windows" {
526 t.Skip("skipping on Windows")
527 }
528 if os.Getuid() == 0 {
529 t.Skip("skipping as root")
530 }
531 if testing.Short() {
532 t.Skip("skipping in short mode")
533 }
534
535
536 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
537 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
538
539
540
541 markTree(tree.entries[1])
542 markTree(tree.entries[3])
543
544 tree.entries[1].mark -= errVisit
545 tree.entries[3].mark -= errVisit
546 err := walk(tree.name, markFn)
547 if err != nil {
548 t.Fatalf("expected no error return from Walk, got %s", err)
549 }
550 if len(errors) != 2 {
551 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
552 }
553
554 checkMarks(t, true)
555 errors = errors[0:0]
556
557
558
559 markTree(tree.entries[1])
560 markTree(tree.entries[3])
561
562 tree.entries[1].mark -= errVisit
563 tree.entries[3].mark -= errVisit
564 clear = false
565 err = walk(tree.name, markFn)
566 if err == nil {
567 t.Fatalf("expected error return from Walk")
568 }
569 if len(errors) != 1 {
570 t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
571 }
572
573 checkMarks(t, false)
574 errors = errors[0:0]
575
576
577 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
578 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
579 })
580 }
581
582 func touch(t *testing.T, name string) {
583 f, err := os.Create(name)
584 if err != nil {
585 t.Fatal(err)
586 }
587 if err := f.Close(); err != nil {
588 t.Fatal(err)
589 }
590 }
591
592 func TestWalkSkipDirOnFile(t *testing.T) {
593 td := t.TempDir()
594
595 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
596 t.Fatal(err)
597 }
598 touch(t, filepath.Join(td, "dir/foo1"))
599 touch(t, filepath.Join(td, "dir/foo2"))
600
601 sawFoo2 := false
602 walker := func(path string) error {
603 if strings.HasSuffix(path, "foo2") {
604 sawFoo2 = true
605 }
606 if strings.HasSuffix(path, "foo1") {
607 return filepath.SkipDir
608 }
609 return nil
610 }
611 walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
612 walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
613
614 check := func(t *testing.T, walk func(root string) error, root string) {
615 t.Helper()
616 sawFoo2 = false
617 err := walk(root)
618 if err != nil {
619 t.Fatal(err)
620 }
621 if sawFoo2 {
622 t.Errorf("SkipDir on file foo1 did not block processing of foo2")
623 }
624 }
625
626 t.Run("Walk", func(t *testing.T) {
627 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
628 check(t, Walk, td)
629 check(t, Walk, filepath.Join(td, "dir"))
630 })
631 t.Run("WalkDir", func(t *testing.T) {
632 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
633 check(t, WalkDir, td)
634 check(t, WalkDir, filepath.Join(td, "dir"))
635 })
636 }
637
638 func TestWalkFileError(t *testing.T) {
639 td := t.TempDir()
640
641 touch(t, filepath.Join(td, "foo"))
642 touch(t, filepath.Join(td, "bar"))
643 dir := filepath.Join(td, "dir")
644 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
645 t.Fatal(err)
646 }
647 touch(t, filepath.Join(dir, "baz"))
648 touch(t, filepath.Join(dir, "stat-error"))
649 defer func() {
650 *filepath.LstatP = os.Lstat
651 }()
652 statErr := errors.New("some stat error")
653 *filepath.LstatP = func(path string) (fs.FileInfo, error) {
654 if strings.HasSuffix(path, "stat-error") {
655 return nil, statErr
656 }
657 return os.Lstat(path)
658 }
659 got := map[string]error{}
660 err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
661 rel, _ := filepath.Rel(td, path)
662 got[filepath.ToSlash(rel)] = err
663 return nil
664 })
665 if err != nil {
666 t.Errorf("Walk error: %v", err)
667 }
668 want := map[string]error{
669 ".": nil,
670 "foo": nil,
671 "bar": nil,
672 "dir": nil,
673 "dir/baz": nil,
674 "dir/stat-error": statErr,
675 }
676 if !reflect.DeepEqual(got, want) {
677 t.Errorf("Walked %#v; want %#v", got, want)
678 }
679 }
680
681 var basetests = []PathTest{
682 {"", "."},
683 {".", "."},
684 {"/.", "."},
685 {"/", "/"},
686 {"////", "/"},
687 {"x/", "x"},
688 {"abc", "abc"},
689 {"abc/def", "def"},
690 {"a/b/.x", ".x"},
691 {"a/b/c.", "c."},
692 {"a/b/c.x", "c.x"},
693 }
694
695 var winbasetests = []PathTest{
696 {`c:\`, `\`},
697 {`c:.`, `.`},
698 {`c:\a\b`, `b`},
699 {`c:a\b`, `b`},
700 {`c:a\b\c`, `c`},
701 {`\\host\share\`, `\`},
702 {`\\host\share\a`, `a`},
703 {`\\host\share\a\b`, `b`},
704 }
705
706 func TestBase(t *testing.T) {
707 tests := basetests
708 if runtime.GOOS == "windows" {
709
710 for i := range tests {
711 tests[i].result = filepath.Clean(tests[i].result)
712 }
713
714 tests = append(tests, winbasetests...)
715 }
716 for _, test := range tests {
717 if s := filepath.Base(test.path); s != test.result {
718 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
719 }
720 }
721 }
722
723 var dirtests = []PathTest{
724 {"", "."},
725 {".", "."},
726 {"/.", "/"},
727 {"/", "/"},
728 {"////", "/"},
729 {"/foo", "/"},
730 {"x/", "x"},
731 {"abc", "."},
732 {"abc/def", "abc"},
733 {"a/b/.x", "a/b"},
734 {"a/b/c.", "a/b"},
735 {"a/b/c.x", "a/b"},
736 }
737
738 var windirtests = []PathTest{
739 {`c:\`, `c:\`},
740 {`c:.`, `c:.`},
741 {`c:\a\b`, `c:\a`},
742 {`c:a\b`, `c:a`},
743 {`c:a\b\c`, `c:a\b`},
744 {`\\host\share`, `\\host\share`},
745 {`\\host\share\`, `\\host\share\`},
746 {`\\host\share\a`, `\\host\share\`},
747 {`\\host\share\a\b`, `\\host\share\a`},
748 }
749
750 func TestDir(t *testing.T) {
751 tests := dirtests
752 if runtime.GOOS == "windows" {
753
754 for i := range tests {
755 tests[i].result = filepath.Clean(tests[i].result)
756 }
757
758 tests = append(tests, windirtests...)
759 }
760 for _, test := range tests {
761 if s := filepath.Dir(test.path); s != test.result {
762 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
763 }
764 }
765 }
766
767 type IsAbsTest struct {
768 path string
769 isAbs bool
770 }
771
772 var isabstests = []IsAbsTest{
773 {"", false},
774 {"/", true},
775 {"/usr/bin/gcc", true},
776 {"..", false},
777 {"/a/../bb", true},
778 {".", false},
779 {"./", false},
780 {"lala", false},
781 }
782
783 var winisabstests = []IsAbsTest{
784 {`C:\`, true},
785 {`c\`, false},
786 {`c::`, false},
787 {`c:`, false},
788 {`/`, false},
789 {`\`, false},
790 {`\Windows`, false},
791 {`c:a\b`, false},
792 {`c:\a\b`, true},
793 {`c:/a/b`, true},
794 {`\\host\share\foo`, true},
795 {`//host/share/foo/bar`, true},
796 }
797
798 func TestIsAbs(t *testing.T) {
799 var tests []IsAbsTest
800 if runtime.GOOS == "windows" {
801 tests = append(tests, winisabstests...)
802
803 for _, test := range isabstests {
804 tests = append(tests, IsAbsTest{test.path, false})
805 }
806
807 for _, test := range isabstests {
808 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
809 }
810
811 tests = append(tests, IsAbsTest{os.DevNull, true})
812 tests = append(tests, IsAbsTest{"NUL", true})
813 tests = append(tests, IsAbsTest{"nul", true})
814 tests = append(tests, IsAbsTest{"CON", true})
815 } else {
816 tests = isabstests
817 }
818
819 for _, test := range tests {
820 if r := filepath.IsAbs(test.path); r != test.isAbs {
821 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
822 }
823 }
824 }
825
826 type EvalSymlinksTest struct {
827
828 path, dest string
829 }
830
831 var EvalSymlinksTestDirs = []EvalSymlinksTest{
832 {"test", ""},
833 {"test/dir", ""},
834 {"test/dir/link3", "../../"},
835 {"test/link1", "../test"},
836 {"test/link2", "dir"},
837 {"test/linkabs", "/"},
838 {"test/link4", "../test2"},
839 {"test2", "test/dir"},
840
841 {"src", ""},
842 {"src/pool", ""},
843 {"src/pool/test", ""},
844 {"src/versions", ""},
845 {"src/versions/current", "../../version"},
846 {"src/versions/v1", ""},
847 {"src/versions/v1/modules", ""},
848 {"src/versions/v1/modules/test", "../../../pool/test"},
849 {"version", "src/versions/v1"},
850 }
851
852 var EvalSymlinksTests = []EvalSymlinksTest{
853 {"test", "test"},
854 {"test/dir", "test/dir"},
855 {"test/dir/../..", "."},
856 {"test/link1", "test"},
857 {"test/link2", "test/dir"},
858 {"test/link1/dir", "test/dir"},
859 {"test/link2/..", "test"},
860 {"test/dir/link3", "."},
861 {"test/link2/link3/test", "test"},
862 {"test/linkabs", "/"},
863 {"test/link4/..", "test"},
864 {"src/versions/current/modules/test", "src/pool/test"},
865 }
866
867
868
869 func simpleJoin(dir, path string) string {
870 return dir + string(filepath.Separator) + path
871 }
872
873 func testEvalSymlinks(t *testing.T, path, want string) {
874 have, err := filepath.EvalSymlinks(path)
875 if err != nil {
876 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
877 return
878 }
879 if filepath.Clean(have) != filepath.Clean(want) {
880 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
881 }
882 }
883
884 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
885 cwd, err := os.Getwd()
886 if err != nil {
887 t.Fatal(err)
888 }
889 defer func() {
890 err := os.Chdir(cwd)
891 if err != nil {
892 t.Fatal(err)
893 }
894 }()
895
896 err = os.Chdir(wd)
897 if err != nil {
898 t.Fatal(err)
899 }
900
901 have, err := filepath.EvalSymlinks(path)
902 if err != nil {
903 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
904 return
905 }
906 if filepath.Clean(have) != filepath.Clean(want) {
907 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
908 }
909 }
910
911 func TestEvalSymlinks(t *testing.T) {
912 testenv.MustHaveSymlink(t)
913
914 tmpDir := t.TempDir()
915
916
917
918 var err error
919 tmpDir, err = filepath.EvalSymlinks(tmpDir)
920 if err != nil {
921 t.Fatal("eval symlink for tmp dir:", err)
922 }
923
924
925 for _, d := range EvalSymlinksTestDirs {
926 var err error
927 path := simpleJoin(tmpDir, d.path)
928 if d.dest == "" {
929 err = os.Mkdir(path, 0755)
930 } else {
931 err = os.Symlink(d.dest, path)
932 }
933 if err != nil {
934 t.Fatal(err)
935 }
936 }
937
938
939 for _, test := range EvalSymlinksTests {
940 path := simpleJoin(tmpDir, test.path)
941
942 dest := simpleJoin(tmpDir, test.dest)
943 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
944 dest = test.dest
945 }
946 testEvalSymlinks(t, path, dest)
947
948
949 testEvalSymlinksAfterChdir(t, path, ".", ".")
950
951
952 if runtime.GOOS == "windows" {
953 volDot := filepath.VolumeName(tmpDir) + "."
954 testEvalSymlinksAfterChdir(t, path, volDot, volDot)
955 }
956
957
958 dotdotPath := simpleJoin("..", test.dest)
959 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
960 dotdotPath = test.dest
961 }
962 testEvalSymlinksAfterChdir(t,
963 simpleJoin(tmpDir, "test"),
964 simpleJoin("..", test.path),
965 dotdotPath)
966
967
968 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
969 }
970 }
971
972 func TestEvalSymlinksIsNotExist(t *testing.T) {
973 testenv.MustHaveSymlink(t)
974
975 defer chtmpdir(t)()
976
977 _, err := filepath.EvalSymlinks("notexist")
978 if !os.IsNotExist(err) {
979 t.Errorf("expected the file is not found, got %v\n", err)
980 }
981
982 err = os.Symlink("notexist", "link")
983 if err != nil {
984 t.Fatal(err)
985 }
986 defer os.Remove("link")
987
988 _, err = filepath.EvalSymlinks("link")
989 if !os.IsNotExist(err) {
990 t.Errorf("expected the file is not found, got %v\n", err)
991 }
992 }
993
994 func TestIssue13582(t *testing.T) {
995 testenv.MustHaveSymlink(t)
996
997 tmpDir := t.TempDir()
998
999 dir := filepath.Join(tmpDir, "dir")
1000 err := os.Mkdir(dir, 0755)
1001 if err != nil {
1002 t.Fatal(err)
1003 }
1004 linkToDir := filepath.Join(tmpDir, "link_to_dir")
1005 err = os.Symlink(dir, linkToDir)
1006 if err != nil {
1007 t.Fatal(err)
1008 }
1009 file := filepath.Join(linkToDir, "file")
1010 err = os.WriteFile(file, nil, 0644)
1011 if err != nil {
1012 t.Fatal(err)
1013 }
1014 link1 := filepath.Join(linkToDir, "link1")
1015 err = os.Symlink(file, link1)
1016 if err != nil {
1017 t.Fatal(err)
1018 }
1019 link2 := filepath.Join(linkToDir, "link2")
1020 err = os.Symlink(link1, link2)
1021 if err != nil {
1022 t.Fatal(err)
1023 }
1024
1025
1026 realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1027 if err != nil {
1028 t.Fatal(err)
1029 }
1030 realDir := filepath.Join(realTmpDir, "dir")
1031 realFile := filepath.Join(realDir, "file")
1032
1033 tests := []struct {
1034 path, want string
1035 }{
1036 {dir, realDir},
1037 {linkToDir, realDir},
1038 {file, realFile},
1039 {link1, realFile},
1040 {link2, realFile},
1041 }
1042 for i, test := range tests {
1043 have, err := filepath.EvalSymlinks(test.path)
1044 if err != nil {
1045 t.Fatal(err)
1046 }
1047 if have != test.want {
1048 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1049 }
1050 }
1051 }
1052
1053
1054
1055 var absTestDirs = []string{
1056 "a",
1057 "a/b",
1058 "a/b/c",
1059 }
1060
1061
1062
1063
1064 var absTests = []string{
1065 ".",
1066 "b",
1067 "b/",
1068 "../a",
1069 "../a/b",
1070 "../a/b/./c/../../.././a",
1071 "../a/b/./c/../../.././a/",
1072 "$",
1073 "$/.",
1074 "$/a/../a/b",
1075 "$/a/b/c/../../.././a",
1076 "$/a/b/c/../../.././a/",
1077 }
1078
1079 func TestAbs(t *testing.T) {
1080 root := t.TempDir()
1081 wd, err := os.Getwd()
1082 if err != nil {
1083 t.Fatal("getwd failed: ", err)
1084 }
1085 err = os.Chdir(root)
1086 if err != nil {
1087 t.Fatal("chdir failed: ", err)
1088 }
1089 defer os.Chdir(wd)
1090
1091 for _, dir := range absTestDirs {
1092 err = os.Mkdir(dir, 0777)
1093 if err != nil {
1094 t.Fatal("Mkdir failed: ", err)
1095 }
1096 }
1097
1098 if runtime.GOOS == "windows" {
1099 vol := filepath.VolumeName(root)
1100 var extra []string
1101 for _, path := range absTests {
1102 if strings.Contains(path, "$") {
1103 continue
1104 }
1105 path = vol + path
1106 extra = append(extra, path)
1107 }
1108 absTests = append(absTests, extra...)
1109 }
1110
1111 err = os.Chdir(absTestDirs[0])
1112 if err != nil {
1113 t.Fatal("chdir failed: ", err)
1114 }
1115
1116 for _, path := range absTests {
1117 path = strings.ReplaceAll(path, "$", root)
1118 info, err := os.Stat(path)
1119 if err != nil {
1120 t.Errorf("%s: %s", path, err)
1121 continue
1122 }
1123
1124 abspath, err := filepath.Abs(path)
1125 if err != nil {
1126 t.Errorf("Abs(%q) error: %v", path, err)
1127 continue
1128 }
1129 absinfo, err := os.Stat(abspath)
1130 if err != nil || !os.SameFile(absinfo, info) {
1131 t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1132 }
1133 if !filepath.IsAbs(abspath) {
1134 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1135 }
1136 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1137 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1138 }
1139 }
1140 }
1141
1142
1143
1144
1145 func TestAbsEmptyString(t *testing.T) {
1146 root := t.TempDir()
1147
1148 wd, err := os.Getwd()
1149 if err != nil {
1150 t.Fatal("getwd failed: ", err)
1151 }
1152 err = os.Chdir(root)
1153 if err != nil {
1154 t.Fatal("chdir failed: ", err)
1155 }
1156 defer os.Chdir(wd)
1157
1158 info, err := os.Stat(root)
1159 if err != nil {
1160 t.Fatalf("%s: %s", root, err)
1161 }
1162
1163 abspath, err := filepath.Abs("")
1164 if err != nil {
1165 t.Fatalf(`Abs("") error: %v`, err)
1166 }
1167 absinfo, err := os.Stat(abspath)
1168 if err != nil || !os.SameFile(absinfo, info) {
1169 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1170 }
1171 if !filepath.IsAbs(abspath) {
1172 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1173 }
1174 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1175 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1176 }
1177 }
1178
1179 type RelTests struct {
1180 root, path, want string
1181 }
1182
1183 var reltests = []RelTests{
1184 {"a/b", "a/b", "."},
1185 {"a/b/.", "a/b", "."},
1186 {"a/b", "a/b/.", "."},
1187 {"./a/b", "a/b", "."},
1188 {"a/b", "./a/b", "."},
1189 {"ab/cd", "ab/cde", "../cde"},
1190 {"ab/cd", "ab/c", "../c"},
1191 {"a/b", "a/b/c/d", "c/d"},
1192 {"a/b", "a/b/../c", "../c"},
1193 {"a/b/../c", "a/b", "../b"},
1194 {"a/b/c", "a/c/d", "../../c/d"},
1195 {"a/b", "c/d", "../../c/d"},
1196 {"a/b/c/d", "a/b", "../.."},
1197 {"a/b/c/d", "a/b/", "../.."},
1198 {"a/b/c/d/", "a/b", "../.."},
1199 {"a/b/c/d/", "a/b/", "../.."},
1200 {"../../a/b", "../../a/b/c/d", "c/d"},
1201 {"/a/b", "/a/b", "."},
1202 {"/a/b/.", "/a/b", "."},
1203 {"/a/b", "/a/b/.", "."},
1204 {"/ab/cd", "/ab/cde", "../cde"},
1205 {"/ab/cd", "/ab/c", "../c"},
1206 {"/a/b", "/a/b/c/d", "c/d"},
1207 {"/a/b", "/a/b/../c", "../c"},
1208 {"/a/b/../c", "/a/b", "../b"},
1209 {"/a/b/c", "/a/c/d", "../../c/d"},
1210 {"/a/b", "/c/d", "../../c/d"},
1211 {"/a/b/c/d", "/a/b", "../.."},
1212 {"/a/b/c/d", "/a/b/", "../.."},
1213 {"/a/b/c/d/", "/a/b", "../.."},
1214 {"/a/b/c/d/", "/a/b/", "../.."},
1215 {"/../../a/b", "/../../a/b/c/d", "c/d"},
1216 {".", "a/b", "a/b"},
1217 {".", "..", ".."},
1218
1219
1220 {"..", ".", "err"},
1221 {"..", "a", "err"},
1222 {"../..", "..", "err"},
1223 {"a", "/a", "err"},
1224 {"/a", "a", "err"},
1225 }
1226
1227 var winreltests = []RelTests{
1228 {`C:a\b\c`, `C:a/b/d`, `..\d`},
1229 {`C:\`, `D:\`, `err`},
1230 {`C:`, `D:`, `err`},
1231 {`C:\Projects`, `c:\projects\src`, `src`},
1232 {`C:\Projects`, `c:\projects`, `.`},
1233 {`C:\Projects\a\..`, `c:\projects`, `.`},
1234 {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1235 }
1236
1237 func TestRel(t *testing.T) {
1238 tests := append([]RelTests{}, reltests...)
1239 if runtime.GOOS == "windows" {
1240 for i := range tests {
1241 tests[i].want = filepath.FromSlash(tests[i].want)
1242 }
1243 tests = append(tests, winreltests...)
1244 }
1245 for _, test := range tests {
1246 got, err := filepath.Rel(test.root, test.path)
1247 if test.want == "err" {
1248 if err == nil {
1249 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1250 }
1251 continue
1252 }
1253 if err != nil {
1254 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1255 }
1256 if got != test.want {
1257 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1258 }
1259 }
1260 }
1261
1262 type VolumeNameTest struct {
1263 path string
1264 vol string
1265 }
1266
1267 var volumenametests = []VolumeNameTest{
1268 {`c:/foo/bar`, `c:`},
1269 {`c:`, `c:`},
1270 {`2:`, ``},
1271 {``, ``},
1272 {`\\\host`, ``},
1273 {`\\\host\`, ``},
1274 {`\\\host\share`, ``},
1275 {`\\\host\\share`, ``},
1276 {`\\host`, ``},
1277 {`//host`, ``},
1278 {`\\host\`, ``},
1279 {`//host/`, ``},
1280 {`\\host\share`, `\\host\share`},
1281 {`//host/share`, `//host/share`},
1282 {`\\host\share\`, `\\host\share`},
1283 {`//host/share/`, `//host/share`},
1284 {`\\host\share\foo`, `\\host\share`},
1285 {`//host/share/foo`, `//host/share`},
1286 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1287 {`//host/share//foo///bar////baz`, `//host/share`},
1288 {`\\host\share\foo\..\bar`, `\\host\share`},
1289 {`//host/share/foo/../bar`, `//host/share`},
1290 }
1291
1292 func TestVolumeName(t *testing.T) {
1293 if runtime.GOOS != "windows" {
1294 return
1295 }
1296 for _, v := range volumenametests {
1297 if vol := filepath.VolumeName(v.path); vol != v.vol {
1298 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1299 }
1300 }
1301 }
1302
1303 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1304 if runtime.GOOS != "windows" {
1305 return
1306 }
1307 wd, _ := os.Getwd()
1308 if len(wd) < 3 {
1309 t.Errorf("Current directory path %q is too short", wd)
1310 }
1311 lp := strings.ToLower(wd)
1312 up := strings.ToUpper(wd)
1313 flp, err := filepath.EvalSymlinks(lp)
1314 if err != nil {
1315 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1316 }
1317 fup, err := filepath.EvalSymlinks(up)
1318 if err != nil {
1319 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1320 }
1321 if flp != fup {
1322 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1323 }
1324 }
1325
1326 func TestBug3486(t *testing.T) {
1327 if runtime.GOOS == "ios" {
1328 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1329 }
1330 root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test")
1331 if err != nil {
1332 t.Fatal(err)
1333 }
1334 bugs := filepath.Join(root, "fixedbugs")
1335 ken := filepath.Join(root, "ken")
1336 seenBugs := false
1337 seenKen := false
1338 err = filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1339 if err != nil {
1340 t.Fatal(err)
1341 }
1342
1343 switch pth {
1344 case bugs:
1345 seenBugs = true
1346 return filepath.SkipDir
1347 case ken:
1348 if !seenBugs {
1349 t.Fatal("filepath.Walk out of order - ken before fixedbugs")
1350 }
1351 seenKen = true
1352 }
1353 return nil
1354 })
1355 if err != nil {
1356 t.Fatal(err)
1357 }
1358 if !seenKen {
1359 t.Fatalf("%q not seen", ken)
1360 }
1361 }
1362
1363 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1364 tmpdir := t.TempDir()
1365
1366 wd, err := os.Getwd()
1367 if err != nil {
1368 t.Fatal(err)
1369 }
1370 defer os.Chdir(wd)
1371
1372 err = os.Chdir(tmpdir)
1373 if err != nil {
1374 t.Fatal(err)
1375 }
1376
1377 err = mklink(tmpdir, "link")
1378 if err != nil {
1379 t.Fatal(err)
1380 }
1381
1382 var visited []string
1383 err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1384 if err != nil {
1385 t.Fatal(err)
1386 }
1387 rel, err := filepath.Rel(tmpdir, path)
1388 if err != nil {
1389 t.Fatal(err)
1390 }
1391 visited = append(visited, rel)
1392 return nil
1393 })
1394 if err != nil {
1395 t.Fatal(err)
1396 }
1397 sort.Strings(visited)
1398 want := []string{".", "link"}
1399 if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1400 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1401 }
1402 }
1403
1404 func TestWalkSymlink(t *testing.T) {
1405 testenv.MustHaveSymlink(t)
1406 testWalkSymlink(t, os.Symlink)
1407 }
1408
1409 func TestIssue29372(t *testing.T) {
1410 tmpDir := t.TempDir()
1411
1412 path := filepath.Join(tmpDir, "file.txt")
1413 err := os.WriteFile(path, nil, 0644)
1414 if err != nil {
1415 t.Fatal(err)
1416 }
1417
1418 pathSeparator := string(filepath.Separator)
1419 tests := []string{
1420 path + strings.Repeat(pathSeparator, 1),
1421 path + strings.Repeat(pathSeparator, 2),
1422 path + strings.Repeat(pathSeparator, 1) + ".",
1423 path + strings.Repeat(pathSeparator, 2) + ".",
1424 path + strings.Repeat(pathSeparator, 1) + "..",
1425 path + strings.Repeat(pathSeparator, 2) + "..",
1426 }
1427
1428 for i, test := range tests {
1429 _, err = filepath.EvalSymlinks(test)
1430 if err != syscall.ENOTDIR {
1431 t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1432 }
1433 }
1434 }
1435
1436
1437 func TestEvalSymlinksAboveRoot(t *testing.T) {
1438 testenv.MustHaveSymlink(t)
1439
1440 t.Parallel()
1441
1442 tmpDir := t.TempDir()
1443
1444 evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1445 if err != nil {
1446 t.Fatal(err)
1447 }
1448
1449 if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1450 t.Fatal(err)
1451 }
1452 if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1453 t.Fatal(err)
1454 }
1455 if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1456 t.Fatal(err)
1457 }
1458
1459
1460 vol := filepath.VolumeName(evalTmpDir)
1461 c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1462 var dd []string
1463 for i := 0; i < c+2; i++ {
1464 dd = append(dd, "..")
1465 }
1466
1467 wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1468
1469
1470 for _, i := range []int{c, c + 1, c + 2} {
1471 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1472 resolved, err := filepath.EvalSymlinks(check)
1473 switch {
1474 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1475
1476 testenv.SkipFlaky(t, 37910)
1477 case err != nil:
1478 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1479 case !strings.HasSuffix(resolved, wantSuffix):
1480 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1481 default:
1482 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1483 }
1484 }
1485 }
1486
1487
1488 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1489 testenv.MustHaveSymlink(t)
1490
1491 tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1492 if err != nil {
1493 t.Fatal(err)
1494 }
1495 defer os.RemoveAll(tmpDir)
1496 chdir(t, tmpDir)
1497
1498 subdir := filepath.Join("a", "b")
1499 if err := os.MkdirAll(subdir, 0777); err != nil {
1500 t.Fatal(err)
1501 }
1502 if err := os.Symlink(subdir, "c"); err != nil {
1503 t.Fatal(err)
1504 }
1505 if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1506 t.Fatal(err)
1507 }
1508
1509 subdir = filepath.Join("d", "e", "f")
1510 if err := os.MkdirAll(subdir, 0777); err != nil {
1511 t.Fatal(err)
1512 }
1513 if err := os.Chdir(subdir); err != nil {
1514 t.Fatal(err)
1515 }
1516
1517 check := filepath.Join("..", "..", "..", "c", "file")
1518 wantSuffix := filepath.Join("a", "b", "file")
1519 if resolved, err := filepath.EvalSymlinks(check); err != nil {
1520 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1521 } else if !strings.HasSuffix(resolved, wantSuffix) {
1522 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1523 } else {
1524 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1525 }
1526 }
1527
View as plain text