]> Cypherpunks.ru repositories - gostls13.git/blob - src/path/filepath/path_test.go
51e6a205547e27b5b0c7dd2c373cf4fdc4484d8f
[gostls13.git] / src / path / filepath / path_test.go
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.
4
5 package filepath_test
6
7 import (
8         "errors"
9         "fmt"
10         "internal/testenv"
11         "io/fs"
12         "os"
13         "path/filepath"
14         "reflect"
15         "runtime"
16         "slices"
17         "sort"
18         "strings"
19         "syscall"
20         "testing"
21 )
22
23 type PathTest struct {
24         path, result string
25 }
26
27 var cleantests = []PathTest{
28         // Already clean
29         {"abc", "abc"},
30         {"abc/def", "abc/def"},
31         {"a/b/c", "a/b/c"},
32         {".", "."},
33         {"..", ".."},
34         {"../..", "../.."},
35         {"../../abc", "../../abc"},
36         {"/abc", "/abc"},
37         {"/", "/"},
38
39         // Empty is current dir
40         {"", "."},
41
42         // Remove trailing slash
43         {"abc/", "abc"},
44         {"abc/def/", "abc/def"},
45         {"a/b/c/", "a/b/c"},
46         {"./", "."},
47         {"../", ".."},
48         {"../../", "../.."},
49         {"/abc/", "/abc"},
50
51         // Remove doubled slash
52         {"abc//def//ghi", "abc/def/ghi"},
53         {"abc//", "abc"},
54
55         // Remove . elements
56         {"abc/./def", "abc/def"},
57         {"/./abc/def", "/abc/def"},
58         {"abc/.", "abc"},
59
60         // Remove .. elements
61         {"abc/def/ghi/../jkl", "abc/def/jkl"},
62         {"abc/def/../ghi/../jkl", "abc/jkl"},
63         {"abc/def/..", "abc"},
64         {"abc/def/../..", "."},
65         {"/abc/def/../..", "/"},
66         {"abc/def/../../..", ".."},
67         {"/abc/def/../../..", "/"},
68         {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
69         {"/../abc", "/abc"},
70         {"a/../b:/../../c", `../c`},
71
72         // Combinations
73         {"abc/./../def", "def"},
74         {"abc//./../def", "def"},
75         {"abc/../../././../def", "../../def"},
76 }
77
78 var nonwincleantests = []PathTest{
79         // Remove leading doubled slash
80         {"//abc", "/abc"},
81         {"///abc", "/abc"},
82         {"//abc//", "/abc"},
83 }
84
85 var wincleantests = []PathTest{
86         {`c:`, `c:.`},
87         {`c:\`, `c:\`},
88         {`c:\abc`, `c:\abc`},
89         {`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
90         {`c:\abc\def\..\..`, `c:\`},
91         {`c:\..\abc`, `c:\abc`},
92         {`c:..\abc`, `c:..\abc`},
93         {`c:\b:\..\..\..\d`, `c:\d`},
94         {`\`, `\`},
95         {`/`, `\`},
96         {`\\i\..\c$`, `\\i\..\c$`},
97         {`\\i\..\i\c$`, `\\i\..\i\c$`},
98         {`\\i\..\I\c$`, `\\i\..\I\c$`},
99         {`\\host\share\foo\..\bar`, `\\host\share\bar`},
100         {`//host/share/foo/../baz`, `\\host\share\baz`},
101         {`\\host\share\foo\..\..\..\..\bar`, `\\host\share\bar`},
102         {`\\.\C:\a\..\..\..\..\bar`, `\\.\C:\bar`},
103         {`\\.\C:\\\\a`, `\\.\C:\a`},
104         {`\\a\b\..\c`, `\\a\b\c`},
105         {`\\a\b`, `\\a\b`},
106         {`.\c:`, `.\c:`},
107         {`.\c:\foo`, `.\c:\foo`},
108         {`.\c:foo`, `.\c:foo`},
109         {`//abc`, `\\abc`},
110         {`///abc`, `\\\abc`},
111         {`//abc//`, `\\abc\\`},
112
113         // Don't allow cleaning to move an element with a colon to the start of the path.
114         {`a/../c:`, `.\c:`},
115         {`a\..\c:`, `.\c:`},
116         {`a/../c:/a`, `.\c:\a`},
117         {`a/../../c:`, `..\c:`},
118         {`foo:bar`, `foo:bar`},
119 }
120
121 func TestClean(t *testing.T) {
122         tests := cleantests
123         if runtime.GOOS == "windows" {
124                 for i := range tests {
125                         tests[i].result = filepath.FromSlash(tests[i].result)
126                 }
127                 tests = append(tests, wincleantests...)
128         } else {
129                 tests = append(tests, nonwincleantests...)
130         }
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)
134                 }
135                 if s := filepath.Clean(test.result); s != test.result {
136                         t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
137                 }
138         }
139
140         if testing.Short() {
141                 t.Skip("skipping malloc count in short mode")
142         }
143         if runtime.GOMAXPROCS(0) > 1 {
144                 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
145                 return
146         }
147
148         for _, test := range tests {
149                 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
150                 if allocs > 0 {
151                         t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
152                 }
153         }
154 }
155
156 type IsLocalTest struct {
157         path    string
158         isLocal bool
159 }
160
161 var islocaltests = []IsLocalTest{
162         {"", false},
163         {".", true},
164         {"..", false},
165         {"../a", false},
166         {"/", false},
167         {"/a", false},
168         {"/a/../..", false},
169         {"a", true},
170         {"a/../a", true},
171         {"a/", true},
172         {"a/.", true},
173         {"a/./b/./c", true},
174         {`a/../b:/../../c`, false},
175 }
176
177 var winislocaltests = []IsLocalTest{
178         {"NUL", false},
179         {"nul", false},
180         {"nul.", false},
181         {"com1", false},
182         {"./nul", false},
183         {`\`, false},
184         {`\a`, false},
185         {`C:`, false},
186         {`C:\a`, false},
187         {`..\a`, false},
188         {`a/../c:`, false},
189         {`CONIN$`, false},
190         {`conin$`, false},
191         {`CONOUT$`, false},
192         {`conout$`, false},
193         {`dollar$`, true}, // not a special file name
194 }
195
196 var plan9islocaltests = []IsLocalTest{
197         {"#a", false},
198 }
199
200 func TestIsLocal(t *testing.T) {
201         tests := islocaltests
202         if runtime.GOOS == "windows" {
203                 tests = append(tests, winislocaltests...)
204         }
205         if runtime.GOOS == "plan9" {
206                 tests = append(tests, plan9islocaltests...)
207         }
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)
211                 }
212         }
213 }
214
215 const sep = filepath.Separator
216
217 var slashtests = []PathTest{
218         {"", ""},
219         {"/", string(sep)},
220         {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
221         {"a//b", string([]byte{'a', sep, sep, 'b'})},
222 }
223
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)
228                 }
229                 if s := filepath.ToSlash(test.result); s != test.path {
230                         t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
231                 }
232         }
233 }
234
235 type SplitListTest struct {
236         list   string
237         result []string
238 }
239
240 const lsep = filepath.ListSeparator
241
242 var splitlisttests = []SplitListTest{
243         {"", []string{}},
244         {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
245         {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
246 }
247
248 var winsplitlisttests = []SplitListTest{
249         // quoted
250         {`"a"`, []string{`a`}},
251
252         // semicolon
253         {`";"`, []string{`;`}},
254         {`"a;b"`, []string{`a;b`}},
255         {`";";`, []string{`;`, ``}},
256         {`;";"`, []string{``, `;`}},
257
258         // partially quoted
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`}},
268 }
269
270 func TestSplitList(t *testing.T) {
271         tests := splitlisttests
272         if runtime.GOOS == "windows" {
273                 tests = append(tests, winsplitlisttests...)
274         }
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)
278                 }
279         }
280 }
281
282 type SplitTest struct {
283         path, dir, file string
284 }
285
286 var unixsplittests = []SplitTest{
287         {"a/b", "a/", "b"},
288         {"a/b/", "a/b/", ""},
289         {"a/", "a/", ""},
290         {"a", "", "a"},
291         {"/", "/", ""},
292 }
293
294 var winsplittests = []SplitTest{
295         {`c:`, `c:`, ``},
296         {`c:/`, `c:/`, ``},
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`},
305 }
306
307 func TestSplit(t *testing.T) {
308         var splittests []SplitTest
309         splittests = unixsplittests
310         if runtime.GOOS == "windows" {
311                 splittests = append(splittests, winsplittests...)
312         }
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)
316                 }
317         }
318 }
319
320 type JoinTest struct {
321         elem []string
322         path string
323 }
324
325 var jointests = []JoinTest{
326         // zero parameters
327         {[]string{}, ""},
328
329         // one parameter
330         {[]string{""}, ""},
331         {[]string{"/"}, "/"},
332         {[]string{"a"}, "a"},
333
334         // two parameters
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{"", ""}, ""},
347
348         // three parameters
349         {[]string{"/", "a", "b"}, "/a/b"},
350 }
351
352 var nonwinjointests = []JoinTest{
353         {[]string{"//", "a"}, "/a"},
354 }
355
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`},
387 }
388
389 func TestJoin(t *testing.T) {
390         if runtime.GOOS == "windows" {
391                 jointests = append(jointests, winjointests...)
392         } else {
393                 jointests = append(jointests, nonwinjointests...)
394         }
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)
399                 }
400         }
401 }
402
403 type ExtTest struct {
404         path, ext string
405 }
406
407 var exttests = []ExtTest{
408         {"path.go", ".go"},
409         {"path.pb.go", ".go"},
410         {"a.dir/b", ""},
411         {"a.dir/b.go", ".go"},
412         {"a.dir/", ""},
413 }
414
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)
419                 }
420         }
421 }
422
423 type Node struct {
424         name    string
425         entries []*Node // nil if the entry is a file
426         mark    int
427 }
428
429 var tree = &Node{
430         "testdata",
431         []*Node{
432                 {"a", nil, 0},
433                 {"b", []*Node{}, 0},
434                 {"c", nil, 0},
435                 {
436                         "d",
437                         []*Node{
438                                 {"x", nil, 0},
439                                 {"y", []*Node{}, 0},
440                                 {
441                                         "z",
442                                         []*Node{
443                                                 {"u", nil, 0},
444                                                 {"v", nil, 0},
445                                         },
446                                         0,
447                                 },
448                         },
449                         0,
450                 },
451         },
452         0,
453 }
454
455 func walkTree(n *Node, path string, f func(path string, n *Node)) {
456         f(path, n)
457         for _, e := range n.entries {
458                 walkTree(e, filepath.Join(path, e.name), f)
459         }
460 }
461
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)
466                         if err != nil {
467                                 t.Errorf("makeTree: %v", err)
468                                 return
469                         }
470                         fd.Close()
471                 } else {
472                         os.Mkdir(path, 0770)
473                 }
474         })
475 }
476
477 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
478
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)
483                 }
484                 n.mark = 0
485         })
486 }
487
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 {
492         name := d.Name()
493         walkTree(tree, tree.name, func(path string, n *Node) {
494                 if n.name == name {
495                         n.mark++
496                 }
497         })
498         if err != nil {
499                 *errors = append(*errors, err)
500                 if clear {
501                         return nil
502                 }
503                 return err
504         }
505         return nil
506 }
507
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()
512         if err != nil {
513                 t.Fatalf("getwd %s: %v", dir, err)
514         }
515         if err := os.Chdir(dir); err != nil {
516                 t.Fatalf("chdir %s: %v", dir, err)
517         }
518
519         t.Cleanup(func() {
520                 if err := os.Chdir(olddir); err != nil {
521                         t.Errorf("restore original working directory %s: %v", olddir, err)
522                         os.Exit(1)
523                 }
524         })
525 }
526
527 func chtmpdir(t *testing.T) (restore func()) {
528         oldwd, err := os.Getwd()
529         if err != nil {
530                 t.Fatalf("chtmpdir: %v", err)
531         }
532         d, err := os.MkdirTemp("", "test")
533         if err != nil {
534                 t.Fatalf("chtmpdir: %v", err)
535         }
536         if err := os.Chdir(d); err != nil {
537                 t.Fatalf("chtmpdir: %v", err)
538         }
539         return func() {
540                 if err := os.Chdir(oldwd); err != nil {
541                         t.Fatalf("chtmpdir: %v", err)
542                 }
543                 os.RemoveAll(d)
544         }
545 }
546
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 {
550         dir := t.TempDir()
551
552         cdir, err := filepath.EvalSymlinks(dir)
553         if err != nil {
554                 t.Errorf("tempDirCanonical: %v", err)
555         }
556
557         return cdir
558 }
559
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)
564                 })
565         }
566         testWalk(t, walk, 1)
567 }
568
569 func TestWalkDir(t *testing.T) {
570         testWalk(t, filepath.WalkDir, 2)
571 }
572
573 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
574         if runtime.GOOS == "ios" {
575                 restore := chtmpdir(t)
576                 defer restore()
577         }
578
579         tmpDir := t.TempDir()
580
581         origDir, err := os.Getwd()
582         if err != nil {
583                 t.Fatal("finding working dir:", err)
584         }
585         if err = os.Chdir(tmpDir); err != nil {
586                 t.Fatal("entering temp dir:", err)
587         }
588         defer os.Chdir(origDir)
589
590         makeTree(t)
591         errors := make([]error, 0, 10)
592         clear := true
593         markFn := func(path string, d fs.DirEntry, err error) error {
594                 return mark(d, err, &errors, clear)
595         }
596         // Expect no errors.
597         err = walk(tree.name, markFn)
598         if err != nil {
599                 t.Fatalf("no error expected, found: %s", err)
600         }
601         if len(errors) != 0 {
602                 t.Fatalf("unexpected errors: %s", errors)
603         }
604         checkMarks(t, true)
605         errors = errors[0:0]
606
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)
614                 }
615                 if os.Getuid() == 0 {
616                         t.Skip("skipping as root")
617                 }
618                 if testing.Short() {
619                         t.Skip("skipping in short mode")
620                 }
621
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)
625
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)
634                 if err != nil {
635                         t.Fatalf("expected no error return from Walk, got %s", err)
636                 }
637                 if len(errors) != 2 {
638                         t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
639                 }
640                 // the inaccessible subtrees were marked manually
641                 checkMarks(t, true)
642                 errors = errors[0:0]
643
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)
653                 if err == nil {
654                         t.Fatalf("expected error return from Walk")
655                 }
656                 if len(errors) != 1 {
657                         t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
658                 }
659                 // the inaccessible subtrees were marked manually
660                 checkMarks(t, false)
661                 errors = errors[0:0]
662
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)
666         })
667 }
668
669 func touch(t *testing.T, name string) {
670         f, err := os.Create(name)
671         if err != nil {
672                 t.Fatal(err)
673         }
674         if err := f.Close(); err != nil {
675                 t.Fatal(err)
676         }
677 }
678
679 func TestWalkSkipDirOnFile(t *testing.T) {
680         td := t.TempDir()
681
682         if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
683                 t.Fatal(err)
684         }
685         touch(t, filepath.Join(td, "dir/foo1"))
686         touch(t, filepath.Join(td, "dir/foo2"))
687
688         sawFoo2 := false
689         walker := func(path string) error {
690                 if strings.HasSuffix(path, "foo2") {
691                         sawFoo2 = true
692                 }
693                 if strings.HasSuffix(path, "foo1") {
694                         return filepath.SkipDir
695                 }
696                 return nil
697         }
698         walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
699         walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
700
701         check := func(t *testing.T, walk func(root string) error, root string) {
702                 t.Helper()
703                 sawFoo2 = false
704                 err := walk(root)
705                 if err != nil {
706                         t.Fatal(err)
707                 }
708                 if sawFoo2 {
709                         t.Errorf("SkipDir on file foo1 did not block processing of foo2")
710                 }
711         }
712
713         t.Run("Walk", func(t *testing.T) {
714                 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
715                 check(t, Walk, td)
716                 check(t, Walk, filepath.Join(td, "dir"))
717         })
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"))
722         })
723 }
724
725 func TestWalkSkipAllOnFile(t *testing.T) {
726         td := t.TempDir()
727
728         if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
729                 t.Fatal(err)
730         }
731         if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
732                 t.Fatal(err)
733         }
734
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"))
741
742         remainingWereSkipped := true
743         walker := func(path string) error {
744                 if strings.HasSuffix(path, "foo2") {
745                         return filepath.SkipAll
746                 }
747
748                 if strings.HasSuffix(path, "foo3") ||
749                         strings.HasSuffix(path, "foo4") ||
750                         strings.HasSuffix(path, "bar") ||
751                         strings.HasSuffix(path, "last") {
752                         remainingWereSkipped = false
753                 }
754                 return nil
755         }
756
757         walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
758         walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
759
760         check := func(t *testing.T, walk func(root string) error, root string) {
761                 t.Helper()
762                 remainingWereSkipped = true
763                 if err := walk(root); err != nil {
764                         t.Fatal(err)
765                 }
766                 if !remainingWereSkipped {
767                         t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
768                 }
769         }
770
771         t.Run("Walk", func(t *testing.T) {
772                 Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
773                 check(t, Walk, td)
774                 check(t, Walk, filepath.Join(td, "dir"))
775         })
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"))
780         })
781 }
782
783 func TestWalkFileError(t *testing.T) {
784         td := t.TempDir()
785
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 {
790                 t.Fatal(err)
791         }
792         touch(t, filepath.Join(dir, "baz"))
793         touch(t, filepath.Join(dir, "stat-error"))
794         defer func() {
795                 *filepath.LstatP = os.Lstat
796         }()
797         statErr := errors.New("some stat error")
798         *filepath.LstatP = func(path string) (fs.FileInfo, error) {
799                 if strings.HasSuffix(path, "stat-error") {
800                         return nil, statErr
801                 }
802                 return os.Lstat(path)
803         }
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
808                 return nil
809         })
810         if err != nil {
811                 t.Errorf("Walk error: %v", err)
812         }
813         want := map[string]error{
814                 ".":              nil,
815                 "foo":            nil,
816                 "bar":            nil,
817                 "dir":            nil,
818                 "dir/baz":        nil,
819                 "dir/stat-error": statErr,
820         }
821         if !reflect.DeepEqual(got, want) {
822                 t.Errorf("Walked %#v; want %#v", got, want)
823         }
824 }
825
826 func TestWalkSymlinkRoot(t *testing.T) {
827         testenv.MustHaveSymlink(t)
828
829         td := t.TempDir()
830         dir := filepath.Join(td, "dir")
831         if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
832                 t.Fatal(err)
833         }
834         touch(t, filepath.Join(dir, "foo"))
835
836         link := filepath.Join(td, "link")
837         if err := os.Symlink("dir", link); err != nil {
838                 t.Fatal(err)
839         }
840
841         abslink := filepath.Join(td, "abslink")
842         if err := os.Symlink(dir, abslink); err != nil {
843                 t.Fatal(err)
844         }
845
846         linklink := filepath.Join(td, "linklink")
847         if err := os.Symlink("link", linklink); err != nil {
848                 t.Fatal(err)
849         }
850
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 [...].”
856         //
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 {
863                 desc      string
864                 root      string
865                 want      []string
866                 buggyGOOS []string
867         }{
868                 {
869                         desc: "no slash",
870                         root: link,
871                         want: []string{link},
872                 },
873                 {
874                         desc: "slash",
875                         root: link + string(filepath.Separator),
876                         want: []string{link, filepath.Join(link, "foo")},
877                 },
878                 {
879                         desc: "abs no slash",
880                         root: abslink,
881                         want: []string{abslink},
882                 },
883                 {
884                         desc: "abs with slash",
885                         root: abslink + string(filepath.Separator),
886                         want: []string{abslink, filepath.Join(abslink, "foo")},
887                 },
888                 {
889                         desc: "double link no slash",
890                         root: linklink,
891                         want: []string{linklink},
892                 },
893                 {
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
898                 },
899         } {
900                 tt := tt
901                 t.Run(tt.desc, func(t *testing.T) {
902                         var walked []string
903                         err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
904                                 if err != nil {
905                                         return err
906                                 }
907                                 t.Logf("%#q: %v", path, info.Mode())
908                                 walked = append(walked, filepath.Clean(path))
909                                 return nil
910                         })
911                         if err != nil {
912                                 t.Fatal(err)
913                         }
914
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)
919                                 } else {
920                                         t.Fail()
921                                 }
922                         }
923                 })
924         }
925 }
926
927 var basetests = []PathTest{
928         {"", "."},
929         {".", "."},
930         {"/.", "."},
931         {"/", "/"},
932         {"////", "/"},
933         {"x/", "x"},
934         {"abc", "abc"},
935         {"abc/def", "def"},
936         {"a/b/.x", ".x"},
937         {"a/b/c.", "c."},
938         {"a/b/c.x", "c.x"},
939 }
940
941 var winbasetests = []PathTest{
942         {`c:\`, `\`},
943         {`c:.`, `.`},
944         {`c:\a\b`, `b`},
945         {`c:a\b`, `b`},
946         {`c:a\b\c`, `c`},
947         {`\\host\share\`, `\`},
948         {`\\host\share\a`, `a`},
949         {`\\host\share\a\b`, `b`},
950 }
951
952 func TestBase(t *testing.T) {
953         tests := basetests
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)
958                 }
959                 // add windows specific tests
960                 tests = append(tests, winbasetests...)
961         }
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)
965                 }
966         }
967 }
968
969 var dirtests = []PathTest{
970         {"", "."},
971         {".", "."},
972         {"/.", "/"},
973         {"/", "/"},
974         {"/foo", "/"},
975         {"x/", "x"},
976         {"abc", "."},
977         {"abc/def", "abc"},
978         {"a/b/.x", "a/b"},
979         {"a/b/c.", "a/b"},
980         {"a/b/c.x", "a/b"},
981 }
982
983 var nonwindirtests = []PathTest{
984         {"////", "/"},
985 }
986
987 var windirtests = []PathTest{
988         {`c:\`, `c:\`},
989         {`c:.`, `c:.`},
990         {`c:\a\b`, `c:\a`},
991         {`c:a\b`, `c:a`},
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`},
997         {`\\\\`, `\\\\`},
998 }
999
1000 func TestDir(t *testing.T) {
1001         tests := dirtests
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)
1006                 }
1007                 // add windows specific tests
1008                 tests = append(tests, windirtests...)
1009         } else {
1010                 tests = append(tests, nonwindirtests...)
1011         }
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)
1015                 }
1016         }
1017 }
1018
1019 type IsAbsTest struct {
1020         path  string
1021         isAbs bool
1022 }
1023
1024 var isabstests = []IsAbsTest{
1025         {"", false},
1026         {"/", true},
1027         {"/usr/bin/gcc", true},
1028         {"..", false},
1029         {"/a/../bb", true},
1030         {".", false},
1031         {"./", false},
1032         {"lala", false},
1033 }
1034
1035 var winisabstests = []IsAbsTest{
1036         {`C:\`, true},
1037         {`c\`, false},
1038         {`c::`, false},
1039         {`c:`, false},
1040         {`/`, false},
1041         {`\`, false},
1042         {`\Windows`, false},
1043         {`c:a\b`, false},
1044         {`c:\a\b`, true},
1045         {`c:/a/b`, true},
1046         {`\\host\share`, true},
1047         {`\\host\share\`, true},
1048         {`\\host\share\foo`, true},
1049         {`//host/share/foo/bar`, true},
1050 }
1051
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})
1059                 }
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})
1063                 }
1064         } else {
1065                 tests = isabstests
1066         }
1067
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)
1071                 }
1072         }
1073 }
1074
1075 type EvalSymlinksTest struct {
1076         // If dest is empty, the path is created; otherwise the dest is symlinked to the path.
1077         path, dest string
1078 }
1079
1080 var EvalSymlinksTestDirs = []EvalSymlinksTest{
1081         {"test", ""},
1082         {"test/dir", ""},
1083         {"test/dir/link3", "../../"},
1084         {"test/link1", "../test"},
1085         {"test/link2", "dir"},
1086         {"test/linkabs", "/"},
1087         {"test/link4", "../test2"},
1088         {"test2", "test/dir"},
1089         // Issue 23444.
1090         {"src", ""},
1091         {"src/pool", ""},
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"},
1099 }
1100
1101 var EvalSymlinksTests = []EvalSymlinksTest{
1102         {"test", "test"},
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"},
1114 }
1115
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
1120 }
1121
1122 func testEvalSymlinks(t *testing.T, path, want string) {
1123         have, err := filepath.EvalSymlinks(path)
1124         if err != nil {
1125                 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1126                 return
1127         }
1128         if filepath.Clean(have) != filepath.Clean(want) {
1129                 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1130         }
1131 }
1132
1133 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1134         cwd, err := os.Getwd()
1135         if err != nil {
1136                 t.Fatal(err)
1137         }
1138         defer func() {
1139                 err := os.Chdir(cwd)
1140                 if err != nil {
1141                         t.Fatal(err)
1142                 }
1143         }()
1144
1145         err = os.Chdir(wd)
1146         if err != nil {
1147                 t.Fatal(err)
1148         }
1149
1150         have, err := filepath.EvalSymlinks(path)
1151         if err != nil {
1152                 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1153                 return
1154         }
1155         if filepath.Clean(have) != filepath.Clean(want) {
1156                 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1157         }
1158 }
1159
1160 func TestEvalSymlinks(t *testing.T) {
1161         testenv.MustHaveSymlink(t)
1162
1163         tmpDir := t.TempDir()
1164
1165         // /tmp may itself be a symlink! Avoid the confusion, although
1166         // it means trusting the thing we're testing.
1167         var err error
1168         tmpDir, err = filepath.EvalSymlinks(tmpDir)
1169         if err != nil {
1170                 t.Fatal("eval symlink for tmp dir:", err)
1171         }
1172
1173         // Create the symlink farm using relative paths.
1174         for _, d := range EvalSymlinksTestDirs {
1175                 var err error
1176                 path := simpleJoin(tmpDir, d.path)
1177                 if d.dest == "" {
1178                         err = os.Mkdir(path, 0755)
1179                 } else {
1180                         err = os.Symlink(d.dest, path)
1181                 }
1182                 if err != nil {
1183                         t.Fatal(err)
1184                 }
1185         }
1186
1187         // Evaluate the symlink farm.
1188         for _, test := range EvalSymlinksTests {
1189                 path := simpleJoin(tmpDir, test.path)
1190
1191                 dest := simpleJoin(tmpDir, test.dest)
1192                 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1193                         dest = test.dest
1194                 }
1195                 testEvalSymlinks(t, path, dest)
1196
1197                 // test EvalSymlinks(".")
1198                 testEvalSymlinksAfterChdir(t, path, ".", ".")
1199
1200                 // test EvalSymlinks("C:.") on Windows
1201                 if runtime.GOOS == "windows" {
1202                         volDot := filepath.VolumeName(tmpDir) + "."
1203                         testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1204                 }
1205
1206                 // test EvalSymlinks(".."+path)
1207                 dotdotPath := simpleJoin("..", test.dest)
1208                 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1209                         dotdotPath = test.dest
1210                 }
1211                 testEvalSymlinksAfterChdir(t,
1212                         simpleJoin(tmpDir, "test"),
1213                         simpleJoin("..", test.path),
1214                         dotdotPath)
1215
1216                 // test EvalSymlinks(p) where p is relative path
1217                 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1218         }
1219 }
1220
1221 func TestEvalSymlinksIsNotExist(t *testing.T) {
1222         testenv.MustHaveSymlink(t)
1223
1224         defer chtmpdir(t)()
1225
1226         _, err := filepath.EvalSymlinks("notexist")
1227         if !os.IsNotExist(err) {
1228                 t.Errorf("expected the file is not found, got %v\n", err)
1229         }
1230
1231         err = os.Symlink("notexist", "link")
1232         if err != nil {
1233                 t.Fatal(err)
1234         }
1235         defer os.Remove("link")
1236
1237         _, err = filepath.EvalSymlinks("link")
1238         if !os.IsNotExist(err) {
1239                 t.Errorf("expected the file is not found, got %v\n", err)
1240         }
1241 }
1242
1243 func TestIssue13582(t *testing.T) {
1244         testenv.MustHaveSymlink(t)
1245
1246         tmpDir := t.TempDir()
1247
1248         dir := filepath.Join(tmpDir, "dir")
1249         err := os.Mkdir(dir, 0755)
1250         if err != nil {
1251                 t.Fatal(err)
1252         }
1253         linkToDir := filepath.Join(tmpDir, "link_to_dir")
1254         err = os.Symlink(dir, linkToDir)
1255         if err != nil {
1256                 t.Fatal(err)
1257         }
1258         file := filepath.Join(linkToDir, "file")
1259         err = os.WriteFile(file, nil, 0644)
1260         if err != nil {
1261                 t.Fatal(err)
1262         }
1263         link1 := filepath.Join(linkToDir, "link1")
1264         err = os.Symlink(file, link1)
1265         if err != nil {
1266                 t.Fatal(err)
1267         }
1268         link2 := filepath.Join(linkToDir, "link2")
1269         err = os.Symlink(link1, link2)
1270         if err != nil {
1271                 t.Fatal(err)
1272         }
1273
1274         // /tmp may itself be a symlink!
1275         realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1276         if err != nil {
1277                 t.Fatal(err)
1278         }
1279         realDir := filepath.Join(realTmpDir, "dir")
1280         realFile := filepath.Join(realDir, "file")
1281
1282         tests := []struct {
1283                 path, want string
1284         }{
1285                 {dir, realDir},
1286                 {linkToDir, realDir},
1287                 {file, realFile},
1288                 {link1, realFile},
1289                 {link2, realFile},
1290         }
1291         for i, test := range tests {
1292                 have, err := filepath.EvalSymlinks(test.path)
1293                 if err != nil {
1294                         t.Fatal(err)
1295                 }
1296                 if have != test.want {
1297                         t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1298                 }
1299         }
1300 }
1301
1302 // Issue 57905.
1303 func TestRelativeSymlinkToAbsolute(t *testing.T) {
1304         testenv.MustHaveSymlink(t)
1305         // Not parallel: uses os.Chdir.
1306
1307         tmpDir := t.TempDir()
1308         chdir(t, tmpDir)
1309
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 {
1315                 t.Fatal(err)
1316         }
1317         t.Logf(`os.Symlink(%q, "link")`, tmpDir)
1318
1319         p, err := filepath.EvalSymlinks("link")
1320         if err != nil {
1321                 t.Fatalf(`EvalSymlinks("link"): %v`, err)
1322         }
1323         want, err := filepath.EvalSymlinks(tmpDir)
1324         if err != nil {
1325                 t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
1326         }
1327         if p != want {
1328                 t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
1329         }
1330         t.Logf(`EvalSymlinks("link") = %q`, p)
1331 }
1332
1333 // Test directories relative to temporary directory.
1334 // The tests are run in absTestDirs[0].
1335 var absTestDirs = []string{
1336         "a",
1337         "a/b",
1338         "a/b/c",
1339 }
1340
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{
1345         ".",
1346         "b",
1347         "b/",
1348         "../a",
1349         "../a/b",
1350         "../a/b/./c/../../.././a",
1351         "../a/b/./c/../../.././a/",
1352         "$",
1353         "$/.",
1354         "$/a/../a/b",
1355         "$/a/b/c/../../.././a",
1356         "$/a/b/c/../../.././a/",
1357 }
1358
1359 func TestAbs(t *testing.T) {
1360         root := t.TempDir()
1361         wd, err := os.Getwd()
1362         if err != nil {
1363                 t.Fatal("getwd failed: ", err)
1364         }
1365         err = os.Chdir(root)
1366         if err != nil {
1367                 t.Fatal("chdir failed: ", err)
1368         }
1369         defer os.Chdir(wd)
1370
1371         for _, dir := range absTestDirs {
1372                 err = os.Mkdir(dir, 0777)
1373                 if err != nil {
1374                         t.Fatal("Mkdir failed: ", err)
1375                 }
1376         }
1377
1378         if runtime.GOOS == "windows" {
1379                 vol := filepath.VolumeName(root)
1380                 var extra []string
1381                 for _, path := range absTests {
1382                         if strings.Contains(path, "$") {
1383                                 continue
1384                         }
1385                         path = vol + path
1386                         extra = append(extra, path)
1387                 }
1388                 absTests = append(absTests, extra...)
1389         }
1390
1391         err = os.Chdir(absTestDirs[0])
1392         if err != nil {
1393                 t.Fatal("chdir failed: ", err)
1394         }
1395
1396         for _, path := range absTests {
1397                 path = strings.ReplaceAll(path, "$", root)
1398                 info, err := os.Stat(path)
1399                 if err != nil {
1400                         t.Errorf("%s: %s", path, err)
1401                         continue
1402                 }
1403
1404                 abspath, err := filepath.Abs(path)
1405                 if err != nil {
1406                         t.Errorf("Abs(%q) error: %v", path, err)
1407                         continue
1408                 }
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)
1412                 }
1413                 if !filepath.IsAbs(abspath) {
1414                         t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1415                 }
1416                 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1417                         t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1418                 }
1419         }
1420 }
1421
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) {
1426         root := t.TempDir()
1427
1428         wd, err := os.Getwd()
1429         if err != nil {
1430                 t.Fatal("getwd failed: ", err)
1431         }
1432         err = os.Chdir(root)
1433         if err != nil {
1434                 t.Fatal("chdir failed: ", err)
1435         }
1436         defer os.Chdir(wd)
1437
1438         info, err := os.Stat(root)
1439         if err != nil {
1440                 t.Fatalf("%s: %s", root, err)
1441         }
1442
1443         abspath, err := filepath.Abs("")
1444         if err != nil {
1445                 t.Fatalf(`Abs("") error: %v`, err)
1446         }
1447         absinfo, err := os.Stat(abspath)
1448         if err != nil || !os.SameFile(absinfo, info) {
1449                 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1450         }
1451         if !filepath.IsAbs(abspath) {
1452                 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1453         }
1454         if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1455                 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1456         }
1457 }
1458
1459 type RelTests struct {
1460         root, path, want string
1461 }
1462
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"},
1497         {".", "..", ".."},
1498
1499         // can't do purely lexically
1500         {"..", ".", "err"},
1501         {"..", "a", "err"},
1502         {"../..", "..", "err"},
1503         {"a", "/a", "err"},
1504         {"/a", "a", "err"},
1505 }
1506
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`},
1515 }
1516
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)
1522                 }
1523                 tests = append(tests, winreltests...)
1524         }
1525         for _, test := range tests {
1526                 got, err := filepath.Rel(test.root, test.path)
1527                 if test.want == "err" {
1528                         if err == nil {
1529                                 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1530                         }
1531                         continue
1532                 }
1533                 if err != nil {
1534                         t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1535                 }
1536                 if got != test.want {
1537                         t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1538                 }
1539         }
1540 }
1541
1542 type VolumeNameTest struct {
1543         path string
1544         vol  string
1545 }
1546
1547 var volumenametests = []VolumeNameTest{
1548         {`c:/foo/bar`, `c:`},
1549         {`c:`, `c:`},
1550         {`2:`, ``},
1551         {``, ``},
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`},
1576 }
1577
1578 func TestVolumeName(t *testing.T) {
1579         if runtime.GOOS != "windows" {
1580                 return
1581         }
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)
1585                 }
1586         }
1587 }
1588
1589 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1590         if runtime.GOOS != "windows" {
1591                 return
1592         }
1593         wd, _ := os.Getwd()
1594         if len(wd) < 3 {
1595                 t.Errorf("Current directory path %q is too short", wd)
1596         }
1597         lp := strings.ToLower(wd)
1598         up := strings.ToUpper(wd)
1599         flp, err := filepath.EvalSymlinks(lp)
1600         if err != nil {
1601                 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1602         }
1603         fup, err := filepath.EvalSymlinks(up)
1604         if err != nil {
1605                 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1606         }
1607         if flp != fup {
1608                 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1609         }
1610 }
1611
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)
1615         }
1616         root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
1617         utf16 := filepath.Join(root, "utf16")
1618         utf8 := filepath.Join(root, "utf8")
1619         seenUTF16 := false
1620         seenUTF8 := false
1621         err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1622                 if err != nil {
1623                         t.Fatal(err)
1624                 }
1625
1626                 switch pth {
1627                 case utf16:
1628                         seenUTF16 = true
1629                         return filepath.SkipDir
1630                 case utf8:
1631                         if !seenUTF16 {
1632                                 t.Fatal("filepath.Walk out of order - utf8 before utf16")
1633                         }
1634                         seenUTF8 = true
1635                 }
1636                 return nil
1637         })
1638         if err != nil {
1639                 t.Fatal(err)
1640         }
1641         if !seenUTF8 {
1642                 t.Fatalf("%q not seen", utf8)
1643         }
1644 }
1645
1646 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1647         tmpdir := t.TempDir()
1648
1649         wd, err := os.Getwd()
1650         if err != nil {
1651                 t.Fatal(err)
1652         }
1653         defer os.Chdir(wd)
1654
1655         err = os.Chdir(tmpdir)
1656         if err != nil {
1657                 t.Fatal(err)
1658         }
1659
1660         err = mklink(tmpdir, "link")
1661         if err != nil {
1662                 t.Fatal(err)
1663         }
1664
1665         var visited []string
1666         err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1667                 if err != nil {
1668                         t.Fatal(err)
1669                 }
1670                 rel, err := filepath.Rel(tmpdir, path)
1671                 if err != nil {
1672                         t.Fatal(err)
1673                 }
1674                 visited = append(visited, rel)
1675                 return nil
1676         })
1677         if err != nil {
1678                 t.Fatal(err)
1679         }
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)
1684         }
1685 }
1686
1687 func TestWalkSymlink(t *testing.T) {
1688         testenv.MustHaveSymlink(t)
1689         testWalkSymlink(t, os.Symlink)
1690 }
1691
1692 func TestIssue29372(t *testing.T) {
1693         tmpDir := t.TempDir()
1694
1695         path := filepath.Join(tmpDir, "file.txt")
1696         err := os.WriteFile(path, nil, 0644)
1697         if err != nil {
1698                 t.Fatal(err)
1699         }
1700
1701         pathSeparator := string(filepath.Separator)
1702         tests := []string{
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) + "..",
1709         }
1710
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)
1715                 }
1716         }
1717 }
1718
1719 // Issue 30520 part 1.
1720 func TestEvalSymlinksAboveRoot(t *testing.T) {
1721         testenv.MustHaveSymlink(t)
1722
1723         t.Parallel()
1724
1725         tmpDir := t.TempDir()
1726
1727         evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1728         if err != nil {
1729                 t.Fatal(err)
1730         }
1731
1732         if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1733                 t.Fatal(err)
1734         }
1735         if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1736                 t.Fatal(err)
1737         }
1738         if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1739                 t.Fatal(err)
1740         }
1741
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))
1745         var dd []string
1746         for i := 0; i < c+2; i++ {
1747                 dd = append(dd, "..")
1748         }
1749
1750         wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1751
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)
1756                 switch {
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)
1760                 case err != nil:
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)
1764                 default:
1765                         t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1766                 }
1767         }
1768 }
1769
1770 // Issue 30520 part 2.
1771 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1772         testenv.MustHaveSymlink(t)
1773
1774         tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1775         if err != nil {
1776                 t.Fatal(err)
1777         }
1778         defer os.RemoveAll(tmpDir)
1779         chdir(t, tmpDir)
1780
1781         subdir := filepath.Join("a", "b")
1782         if err := os.MkdirAll(subdir, 0777); err != nil {
1783                 t.Fatal(err)
1784         }
1785         if err := os.Symlink(subdir, "c"); err != nil {
1786                 t.Fatal(err)
1787         }
1788         if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1789                 t.Fatal(err)
1790         }
1791
1792         subdir = filepath.Join("d", "e", "f")
1793         if err := os.MkdirAll(subdir, 0777); err != nil {
1794                 t.Fatal(err)
1795         }
1796         if err := os.Chdir(subdir); err != nil {
1797                 t.Fatal(err)
1798         }
1799
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)
1806         } else {
1807                 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1808         }
1809 }
1810
1811 func TestIssue51617(t *testing.T) {
1812         dir := t.TempDir()
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 {
1815                         t.Fatal(err)
1816                 }
1817         }
1818         bad := filepath.Join(dir, "a", "bad")
1819         if err := os.Chmod(bad, 0); err != nil {
1820                 t.Fatal(err)
1821         }
1822         defer os.Chmod(bad, 0700) // avoid errors on cleanup
1823         var saw []string
1824         err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1825                 if err != nil {
1826                         return filepath.SkipDir
1827                 }
1828                 if d.IsDir() {
1829                         rel, err := filepath.Rel(dir, path)
1830                         if err != nil {
1831                                 t.Fatal(err)
1832                         }
1833                         saw = append(saw, rel)
1834                 }
1835                 return nil
1836         })
1837         if err != nil {
1838                 t.Fatal(err)
1839         }
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)
1843         }
1844 }