]> Cypherpunks.ru repositories - gostls13.git/blob - src/path/filepath/path_test.go
path/filepath: consider \\?\c: as a volume on Windows
[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         {`\\?\C:\`, `\\?\C:\`},
113         {`\\?\C:\a`, `\\?\C:\a`},
114
115         // Don't allow cleaning to move an element with a colon to the start of the path.
116         {`a/../c:`, `.\c:`},
117         {`a\..\c:`, `.\c:`},
118         {`a/../c:/a`, `.\c:\a`},
119         {`a/../../c:`, `..\c:`},
120         {`foo:bar`, `foo:bar`},
121
122         // Don't allow cleaning to create a Root Local Device path like \??\a.
123         {`/a/../??/a`, `\.\??\a`},
124 }
125
126 func TestClean(t *testing.T) {
127         tests := cleantests
128         if runtime.GOOS == "windows" {
129                 for i := range tests {
130                         tests[i].result = filepath.FromSlash(tests[i].result)
131                 }
132                 tests = append(tests, wincleantests...)
133         } else {
134                 tests = append(tests, nonwincleantests...)
135         }
136         for _, test := range tests {
137                 if s := filepath.Clean(test.path); s != test.result {
138                         t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
139                 }
140                 if s := filepath.Clean(test.result); s != test.result {
141                         t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
142                 }
143         }
144
145         if testing.Short() {
146                 t.Skip("skipping malloc count in short mode")
147         }
148         if runtime.GOMAXPROCS(0) > 1 {
149                 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
150                 return
151         }
152
153         for _, test := range tests {
154                 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
155                 if allocs > 0 {
156                         t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
157                 }
158         }
159 }
160
161 type IsLocalTest struct {
162         path    string
163         isLocal bool
164 }
165
166 var islocaltests = []IsLocalTest{
167         {"", false},
168         {".", true},
169         {"..", false},
170         {"../a", false},
171         {"/", false},
172         {"/a", false},
173         {"/a/../..", false},
174         {"a", true},
175         {"a/../a", true},
176         {"a/", true},
177         {"a/.", true},
178         {"a/./b/./c", true},
179         {`a/../b:/../../c`, false},
180 }
181
182 var winislocaltests = []IsLocalTest{
183         {"NUL", false},
184         {"nul", false},
185         {"nul ", false},
186         {"nul.", false},
187         {"a/nul:", false},
188         {"a/nul : a", false},
189         {"com0", true},
190         {"com1", false},
191         {"com2", false},
192         {"com3", false},
193         {"com4", false},
194         {"com5", false},
195         {"com6", false},
196         {"com7", false},
197         {"com8", false},
198         {"com9", false},
199         {"com¹", false},
200         {"com²", false},
201         {"com³", false},
202         {"com¹ : a", false},
203         {"cOm1", false},
204         {"lpt1", false},
205         {"LPT1", false},
206         {"lpt³", false},
207         {"./nul", false},
208         {`\`, false},
209         {`\a`, false},
210         {`C:`, false},
211         {`C:\a`, false},
212         {`..\a`, false},
213         {`a/../c:`, false},
214         {`CONIN$`, false},
215         {`conin$`, false},
216         {`CONOUT$`, false},
217         {`conout$`, false},
218         {`dollar$`, true}, // not a special file name
219 }
220
221 var plan9islocaltests = []IsLocalTest{
222         {"#a", false},
223 }
224
225 func TestIsLocal(t *testing.T) {
226         tests := islocaltests
227         if runtime.GOOS == "windows" {
228                 tests = append(tests, winislocaltests...)
229         }
230         if runtime.GOOS == "plan9" {
231                 tests = append(tests, plan9islocaltests...)
232         }
233         for _, test := range tests {
234                 if got := filepath.IsLocal(test.path); got != test.isLocal {
235                         t.Errorf("IsLocal(%q) = %v, want %v", test.path, got, test.isLocal)
236                 }
237         }
238 }
239
240 const sep = filepath.Separator
241
242 var slashtests = []PathTest{
243         {"", ""},
244         {"/", string(sep)},
245         {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
246         {"a//b", string([]byte{'a', sep, sep, 'b'})},
247 }
248
249 func TestFromAndToSlash(t *testing.T) {
250         for _, test := range slashtests {
251                 if s := filepath.FromSlash(test.path); s != test.result {
252                         t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
253                 }
254                 if s := filepath.ToSlash(test.result); s != test.path {
255                         t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
256                 }
257         }
258 }
259
260 type SplitListTest struct {
261         list   string
262         result []string
263 }
264
265 const lsep = filepath.ListSeparator
266
267 var splitlisttests = []SplitListTest{
268         {"", []string{}},
269         {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
270         {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
271 }
272
273 var winsplitlisttests = []SplitListTest{
274         // quoted
275         {`"a"`, []string{`a`}},
276
277         // semicolon
278         {`";"`, []string{`;`}},
279         {`"a;b"`, []string{`a;b`}},
280         {`";";`, []string{`;`, ``}},
281         {`;";"`, []string{``, `;`}},
282
283         // partially quoted
284         {`a";"b`, []string{`a;b`}},
285         {`a; ""b`, []string{`a`, ` b`}},
286         {`"a;b`, []string{`a;b`}},
287         {`""a;b`, []string{`a`, `b`}},
288         {`"""a;b`, []string{`a;b`}},
289         {`""""a;b`, []string{`a`, `b`}},
290         {`a";b`, []string{`a;b`}},
291         {`a;b";c`, []string{`a`, `b;c`}},
292         {`"a";b";c`, []string{`a`, `b;c`}},
293 }
294
295 func TestSplitList(t *testing.T) {
296         tests := splitlisttests
297         if runtime.GOOS == "windows" {
298                 tests = append(tests, winsplitlisttests...)
299         }
300         for _, test := range tests {
301                 if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
302                         t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
303                 }
304         }
305 }
306
307 type SplitTest struct {
308         path, dir, file string
309 }
310
311 var unixsplittests = []SplitTest{
312         {"a/b", "a/", "b"},
313         {"a/b/", "a/b/", ""},
314         {"a/", "a/", ""},
315         {"a", "", "a"},
316         {"/", "/", ""},
317 }
318
319 var winsplittests = []SplitTest{
320         {`c:`, `c:`, ``},
321         {`c:/`, `c:/`, ``},
322         {`c:/foo`, `c:/`, `foo`},
323         {`c:/foo/bar`, `c:/foo/`, `bar`},
324         {`//host/share`, `//host/share`, ``},
325         {`//host/share/`, `//host/share/`, ``},
326         {`//host/share/foo`, `//host/share/`, `foo`},
327         {`\\host\share`, `\\host\share`, ``},
328         {`\\host\share\`, `\\host\share\`, ``},
329         {`\\host\share\foo`, `\\host\share\`, `foo`},
330 }
331
332 func TestSplit(t *testing.T) {
333         var splittests []SplitTest
334         splittests = unixsplittests
335         if runtime.GOOS == "windows" {
336                 splittests = append(splittests, winsplittests...)
337         }
338         for _, test := range splittests {
339                 if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
340                         t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
341                 }
342         }
343 }
344
345 type JoinTest struct {
346         elem []string
347         path string
348 }
349
350 var jointests = []JoinTest{
351         // zero parameters
352         {[]string{}, ""},
353
354         // one parameter
355         {[]string{""}, ""},
356         {[]string{"/"}, "/"},
357         {[]string{"a"}, "a"},
358
359         // two parameters
360         {[]string{"a", "b"}, "a/b"},
361         {[]string{"a", ""}, "a"},
362         {[]string{"", "b"}, "b"},
363         {[]string{"/", "a"}, "/a"},
364         {[]string{"/", "a/b"}, "/a/b"},
365         {[]string{"/", ""}, "/"},
366         {[]string{"/a", "b"}, "/a/b"},
367         {[]string{"a", "/b"}, "a/b"},
368         {[]string{"/a", "/b"}, "/a/b"},
369         {[]string{"a/", "b"}, "a/b"},
370         {[]string{"a/", ""}, "a"},
371         {[]string{"", ""}, ""},
372
373         // three parameters
374         {[]string{"/", "a", "b"}, "/a/b"},
375 }
376
377 var nonwinjointests = []JoinTest{
378         {[]string{"//", "a"}, "/a"},
379 }
380
381 var winjointests = []JoinTest{
382         {[]string{`directory`, `file`}, `directory\file`},
383         {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
384         {[]string{`C:\Windows\`, ``}, `C:\Windows`},
385         {[]string{`C:\`, `Windows`}, `C:\Windows`},
386         {[]string{`C:`, `a`}, `C:a`},
387         {[]string{`C:`, `a\b`}, `C:a\b`},
388         {[]string{`C:`, `a`, `b`}, `C:a\b`},
389         {[]string{`C:`, ``, `b`}, `C:b`},
390         {[]string{`C:`, ``, ``, `b`}, `C:b`},
391         {[]string{`C:`, ``}, `C:.`},
392         {[]string{`C:`, ``, ``}, `C:.`},
393         {[]string{`C:`, `\a`}, `C:\a`},
394         {[]string{`C:`, ``, `\a`}, `C:\a`},
395         {[]string{`C:.`, `a`}, `C:a`},
396         {[]string{`C:a`, `b`}, `C:a\b`},
397         {[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
398         {[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
399         {[]string{`\\host\share\foo`}, `\\host\share\foo`},
400         {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
401         {[]string{`\`}, `\`},
402         {[]string{`\`, ``}, `\`},
403         {[]string{`\`, `a`}, `\a`},
404         {[]string{`\\`, `a`}, `\\a`},
405         {[]string{`\`, `a`, `b`}, `\a\b`},
406         {[]string{`\\`, `a`, `b`}, `\\a\b`},
407         {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
408         {[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
409         {[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
410         {[]string{`//`, `a`}, `\\a`},
411         {[]string{`a:\b\c`, `x\..\y:\..\..\z`}, `a:\b\z`},
412         {[]string{`\`, `??\a`}, `\.\??\a`},
413 }
414
415 func TestJoin(t *testing.T) {
416         if runtime.GOOS == "windows" {
417                 jointests = append(jointests, winjointests...)
418         } else {
419                 jointests = append(jointests, nonwinjointests...)
420         }
421         for _, test := range jointests {
422                 expected := filepath.FromSlash(test.path)
423                 if p := filepath.Join(test.elem...); p != expected {
424                         t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
425                 }
426         }
427 }
428
429 type ExtTest struct {
430         path, ext string
431 }
432
433 var exttests = []ExtTest{
434         {"path.go", ".go"},
435         {"path.pb.go", ".go"},
436         {"a.dir/b", ""},
437         {"a.dir/b.go", ".go"},
438         {"a.dir/", ""},
439 }
440
441 func TestExt(t *testing.T) {
442         for _, test := range exttests {
443                 if x := filepath.Ext(test.path); x != test.ext {
444                         t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
445                 }
446         }
447 }
448
449 type Node struct {
450         name    string
451         entries []*Node // nil if the entry is a file
452         mark    int
453 }
454
455 var tree = &Node{
456         "testdata",
457         []*Node{
458                 {"a", nil, 0},
459                 {"b", []*Node{}, 0},
460                 {"c", nil, 0},
461                 {
462                         "d",
463                         []*Node{
464                                 {"x", nil, 0},
465                                 {"y", []*Node{}, 0},
466                                 {
467                                         "z",
468                                         []*Node{
469                                                 {"u", nil, 0},
470                                                 {"v", nil, 0},
471                                         },
472                                         0,
473                                 },
474                         },
475                         0,
476                 },
477         },
478         0,
479 }
480
481 func walkTree(n *Node, path string, f func(path string, n *Node)) {
482         f(path, n)
483         for _, e := range n.entries {
484                 walkTree(e, filepath.Join(path, e.name), f)
485         }
486 }
487
488 func makeTree(t *testing.T) {
489         walkTree(tree, tree.name, func(path string, n *Node) {
490                 if n.entries == nil {
491                         fd, err := os.Create(path)
492                         if err != nil {
493                                 t.Errorf("makeTree: %v", err)
494                                 return
495                         }
496                         fd.Close()
497                 } else {
498                         os.Mkdir(path, 0770)
499                 }
500         })
501 }
502
503 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
504
505 func checkMarks(t *testing.T, report bool) {
506         walkTree(tree, tree.name, func(path string, n *Node) {
507                 if n.mark != 1 && report {
508                         t.Errorf("node %s mark = %d; expected 1", path, n.mark)
509                 }
510                 n.mark = 0
511         })
512 }
513
514 // Assumes that each node name is unique. Good enough for a test.
515 // If clear is true, any incoming error is cleared before return. The errors
516 // are always accumulated, though.
517 func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
518         name := d.Name()
519         walkTree(tree, tree.name, func(path string, n *Node) {
520                 if n.name == name {
521                         n.mark++
522                 }
523         })
524         if err != nil {
525                 *errors = append(*errors, err)
526                 if clear {
527                         return nil
528                 }
529                 return err
530         }
531         return nil
532 }
533
534 // chdir changes the current working directory to the named directory,
535 // and then restore the original working directory at the end of the test.
536 func chdir(t *testing.T, dir string) {
537         olddir, err := os.Getwd()
538         if err != nil {
539                 t.Fatalf("getwd %s: %v", dir, err)
540         }
541         if err := os.Chdir(dir); err != nil {
542                 t.Fatalf("chdir %s: %v", dir, err)
543         }
544
545         t.Cleanup(func() {
546                 if err := os.Chdir(olddir); err != nil {
547                         t.Errorf("restore original working directory %s: %v", olddir, err)
548                         os.Exit(1)
549                 }
550         })
551 }
552
553 func chtmpdir(t *testing.T) (restore func()) {
554         oldwd, err := os.Getwd()
555         if err != nil {
556                 t.Fatalf("chtmpdir: %v", err)
557         }
558         d, err := os.MkdirTemp("", "test")
559         if err != nil {
560                 t.Fatalf("chtmpdir: %v", err)
561         }
562         if err := os.Chdir(d); err != nil {
563                 t.Fatalf("chtmpdir: %v", err)
564         }
565         return func() {
566                 if err := os.Chdir(oldwd); err != nil {
567                         t.Fatalf("chtmpdir: %v", err)
568                 }
569                 os.RemoveAll(d)
570         }
571 }
572
573 // tempDirCanonical returns a temporary directory for the test to use, ensuring
574 // that the returned path does not contain symlinks.
575 func tempDirCanonical(t *testing.T) string {
576         dir := t.TempDir()
577
578         cdir, err := filepath.EvalSymlinks(dir)
579         if err != nil {
580                 t.Errorf("tempDirCanonical: %v", err)
581         }
582
583         return cdir
584 }
585
586 func TestWalk(t *testing.T) {
587         walk := func(root string, fn fs.WalkDirFunc) error {
588                 return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
589                         return fn(path, fs.FileInfoToDirEntry(info), err)
590                 })
591         }
592         testWalk(t, walk, 1)
593 }
594
595 func TestWalkDir(t *testing.T) {
596         testWalk(t, filepath.WalkDir, 2)
597 }
598
599 func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
600         if runtime.GOOS == "ios" {
601                 restore := chtmpdir(t)
602                 defer restore()
603         }
604
605         tmpDir := t.TempDir()
606
607         origDir, err := os.Getwd()
608         if err != nil {
609                 t.Fatal("finding working dir:", err)
610         }
611         if err = os.Chdir(tmpDir); err != nil {
612                 t.Fatal("entering temp dir:", err)
613         }
614         defer os.Chdir(origDir)
615
616         makeTree(t)
617         errors := make([]error, 0, 10)
618         clear := true
619         markFn := func(path string, d fs.DirEntry, err error) error {
620                 return mark(d, err, &errors, clear)
621         }
622         // Expect no errors.
623         err = walk(tree.name, markFn)
624         if err != nil {
625                 t.Fatalf("no error expected, found: %s", err)
626         }
627         if len(errors) != 0 {
628                 t.Fatalf("unexpected errors: %s", errors)
629         }
630         checkMarks(t, true)
631         errors = errors[0:0]
632
633         t.Run("PermErr", func(t *testing.T) {
634                 // Test permission errors. Only possible if we're not root
635                 // and only on some file systems (AFS, FAT).  To avoid errors during
636                 // all.bash on those file systems, skip during go test -short.
637                 // Chmod is not supported on wasip1.
638                 if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
639                         t.Skip("skipping on " + runtime.GOOS)
640                 }
641                 if os.Getuid() == 0 {
642                         t.Skip("skipping as root")
643                 }
644                 if testing.Short() {
645                         t.Skip("skipping in short mode")
646                 }
647
648                 // introduce 2 errors: chmod top-level directories to 0
649                 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
650                 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
651
652                 // 3) capture errors, expect two.
653                 // mark respective subtrees manually
654                 markTree(tree.entries[1])
655                 markTree(tree.entries[3])
656                 // correct double-marking of directory itself
657                 tree.entries[1].mark -= errVisit
658                 tree.entries[3].mark -= errVisit
659                 err := walk(tree.name, markFn)
660                 if err != nil {
661                         t.Fatalf("expected no error return from Walk, got %s", err)
662                 }
663                 if len(errors) != 2 {
664                         t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
665                 }
666                 // the inaccessible subtrees were marked manually
667                 checkMarks(t, true)
668                 errors = errors[0:0]
669
670                 // 4) capture errors, stop after first error.
671                 // mark respective subtrees manually
672                 markTree(tree.entries[1])
673                 markTree(tree.entries[3])
674                 // correct double-marking of directory itself
675                 tree.entries[1].mark -= errVisit
676                 tree.entries[3].mark -= errVisit
677                 clear = false // error will stop processing
678                 err = walk(tree.name, markFn)
679                 if err == nil {
680                         t.Fatalf("expected error return from Walk")
681                 }
682                 if len(errors) != 1 {
683                         t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
684                 }
685                 // the inaccessible subtrees were marked manually
686                 checkMarks(t, false)
687                 errors = errors[0:0]
688
689                 // restore permissions
690                 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
691                 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
692         })
693 }
694
695 func touch(t *testing.T, name string) {
696         f, err := os.Create(name)
697         if err != nil {
698                 t.Fatal(err)
699         }
700         if err := f.Close(); err != nil {
701                 t.Fatal(err)
702         }
703 }
704
705 func TestWalkSkipDirOnFile(t *testing.T) {
706         td := t.TempDir()
707
708         if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
709                 t.Fatal(err)
710         }
711         touch(t, filepath.Join(td, "dir/foo1"))
712         touch(t, filepath.Join(td, "dir/foo2"))
713
714         sawFoo2 := false
715         walker := func(path string) error {
716                 if strings.HasSuffix(path, "foo2") {
717                         sawFoo2 = true
718                 }
719                 if strings.HasSuffix(path, "foo1") {
720                         return filepath.SkipDir
721                 }
722                 return nil
723         }
724         walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
725         walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
726
727         check := func(t *testing.T, walk func(root string) error, root string) {
728                 t.Helper()
729                 sawFoo2 = false
730                 err := walk(root)
731                 if err != nil {
732                         t.Fatal(err)
733                 }
734                 if sawFoo2 {
735                         t.Errorf("SkipDir on file foo1 did not block processing of foo2")
736                 }
737         }
738
739         t.Run("Walk", func(t *testing.T) {
740                 Walk := func(root string) error { return filepath.Walk(td, walkFn) }
741                 check(t, Walk, td)
742                 check(t, Walk, filepath.Join(td, "dir"))
743         })
744         t.Run("WalkDir", func(t *testing.T) {
745                 WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
746                 check(t, WalkDir, td)
747                 check(t, WalkDir, filepath.Join(td, "dir"))
748         })
749 }
750
751 func TestWalkSkipAllOnFile(t *testing.T) {
752         td := t.TempDir()
753
754         if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
755                 t.Fatal(err)
756         }
757         if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
758                 t.Fatal(err)
759         }
760
761         touch(t, filepath.Join(td, "dir", "foo1"))
762         touch(t, filepath.Join(td, "dir", "foo2"))
763         touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
764         touch(t, filepath.Join(td, "dir", "foo4"))
765         touch(t, filepath.Join(td, "dir2", "bar"))
766         touch(t, filepath.Join(td, "last"))
767
768         remainingWereSkipped := true
769         walker := func(path string) error {
770                 if strings.HasSuffix(path, "foo2") {
771                         return filepath.SkipAll
772                 }
773
774                 if strings.HasSuffix(path, "foo3") ||
775                         strings.HasSuffix(path, "foo4") ||
776                         strings.HasSuffix(path, "bar") ||
777                         strings.HasSuffix(path, "last") {
778                         remainingWereSkipped = false
779                 }
780                 return nil
781         }
782
783         walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
784         walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
785
786         check := func(t *testing.T, walk func(root string) error, root string) {
787                 t.Helper()
788                 remainingWereSkipped = true
789                 if err := walk(root); err != nil {
790                         t.Fatal(err)
791                 }
792                 if !remainingWereSkipped {
793                         t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
794                 }
795         }
796
797         t.Run("Walk", func(t *testing.T) {
798                 Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
799                 check(t, Walk, td)
800                 check(t, Walk, filepath.Join(td, "dir"))
801         })
802         t.Run("WalkDir", func(t *testing.T) {
803                 WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
804                 check(t, WalkDir, td)
805                 check(t, WalkDir, filepath.Join(td, "dir"))
806         })
807 }
808
809 func TestWalkFileError(t *testing.T) {
810         td := t.TempDir()
811
812         touch(t, filepath.Join(td, "foo"))
813         touch(t, filepath.Join(td, "bar"))
814         dir := filepath.Join(td, "dir")
815         if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
816                 t.Fatal(err)
817         }
818         touch(t, filepath.Join(dir, "baz"))
819         touch(t, filepath.Join(dir, "stat-error"))
820         defer func() {
821                 *filepath.LstatP = os.Lstat
822         }()
823         statErr := errors.New("some stat error")
824         *filepath.LstatP = func(path string) (fs.FileInfo, error) {
825                 if strings.HasSuffix(path, "stat-error") {
826                         return nil, statErr
827                 }
828                 return os.Lstat(path)
829         }
830         got := map[string]error{}
831         err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
832                 rel, _ := filepath.Rel(td, path)
833                 got[filepath.ToSlash(rel)] = err
834                 return nil
835         })
836         if err != nil {
837                 t.Errorf("Walk error: %v", err)
838         }
839         want := map[string]error{
840                 ".":              nil,
841                 "foo":            nil,
842                 "bar":            nil,
843                 "dir":            nil,
844                 "dir/baz":        nil,
845                 "dir/stat-error": statErr,
846         }
847         if !reflect.DeepEqual(got, want) {
848                 t.Errorf("Walked %#v; want %#v", got, want)
849         }
850 }
851
852 func TestWalkSymlinkRoot(t *testing.T) {
853         testenv.MustHaveSymlink(t)
854
855         td := t.TempDir()
856         dir := filepath.Join(td, "dir")
857         if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
858                 t.Fatal(err)
859         }
860         touch(t, filepath.Join(dir, "foo"))
861
862         link := filepath.Join(td, "link")
863         if err := os.Symlink("dir", link); err != nil {
864                 t.Fatal(err)
865         }
866
867         abslink := filepath.Join(td, "abslink")
868         if err := os.Symlink(dir, abslink); err != nil {
869                 t.Fatal(err)
870         }
871
872         linklink := filepath.Join(td, "linklink")
873         if err := os.Symlink("link", linklink); err != nil {
874                 t.Fatal(err)
875         }
876
877         // Per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12:
878         // “A pathname that contains at least one non- <slash> character and that ends
879         // with one or more trailing <slash> characters shall not be resolved
880         // successfully unless the last pathname component before the trailing <slash>
881         // characters names an existing directory [...].”
882         //
883         // Since Walk does not traverse symlinks itself, its behavior should depend on
884         // whether the path passed to Walk ends in a slash: if it does not end in a slash,
885         // Walk should report the symlink itself (since it is the last pathname component);
886         // but if it does end in a slash, Walk should walk the directory to which the symlink
887         // refers (since it must be fully resolved before walking).
888         for _, tt := range []struct {
889                 desc      string
890                 root      string
891                 want      []string
892                 buggyGOOS []string
893         }{
894                 {
895                         desc: "no slash",
896                         root: link,
897                         want: []string{link},
898                 },
899                 {
900                         desc: "slash",
901                         root: link + string(filepath.Separator),
902                         want: []string{link, filepath.Join(link, "foo")},
903                 },
904                 {
905                         desc: "abs no slash",
906                         root: abslink,
907                         want: []string{abslink},
908                 },
909                 {
910                         desc: "abs with slash",
911                         root: abslink + string(filepath.Separator),
912                         want: []string{abslink, filepath.Join(abslink, "foo")},
913                 },
914                 {
915                         desc: "double link no slash",
916                         root: linklink,
917                         want: []string{linklink},
918                 },
919                 {
920                         desc:      "double link with slash",
921                         root:      linklink + string(filepath.Separator),
922                         want:      []string{linklink, filepath.Join(linklink, "foo")},
923                         buggyGOOS: []string{"darwin", "ios"}, // https://go.dev/issue/59586
924                 },
925         } {
926                 tt := tt
927                 t.Run(tt.desc, func(t *testing.T) {
928                         var walked []string
929                         err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
930                                 if err != nil {
931                                         return err
932                                 }
933                                 t.Logf("%#q: %v", path, info.Mode())
934                                 walked = append(walked, filepath.Clean(path))
935                                 return nil
936                         })
937                         if err != nil {
938                                 t.Fatal(err)
939                         }
940
941                         if !reflect.DeepEqual(walked, tt.want) {
942                                 t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
943                                 if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
944                                         t.Logf("(ignoring known bug on %v)", runtime.GOOS)
945                                 } else {
946                                         t.Fail()
947                                 }
948                         }
949                 })
950         }
951 }
952
953 var basetests = []PathTest{
954         {"", "."},
955         {".", "."},
956         {"/.", "."},
957         {"/", "/"},
958         {"////", "/"},
959         {"x/", "x"},
960         {"abc", "abc"},
961         {"abc/def", "def"},
962         {"a/b/.x", ".x"},
963         {"a/b/c.", "c."},
964         {"a/b/c.x", "c.x"},
965 }
966
967 var winbasetests = []PathTest{
968         {`c:\`, `\`},
969         {`c:.`, `.`},
970         {`c:\a\b`, `b`},
971         {`c:a\b`, `b`},
972         {`c:a\b\c`, `c`},
973         {`\\host\share\`, `\`},
974         {`\\host\share\a`, `a`},
975         {`\\host\share\a\b`, `b`},
976 }
977
978 func TestBase(t *testing.T) {
979         tests := basetests
980         if runtime.GOOS == "windows" {
981                 // make unix tests work on windows
982                 for i := range tests {
983                         tests[i].result = filepath.Clean(tests[i].result)
984                 }
985                 // add windows specific tests
986                 tests = append(tests, winbasetests...)
987         }
988         for _, test := range tests {
989                 if s := filepath.Base(test.path); s != test.result {
990                         t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
991                 }
992         }
993 }
994
995 var dirtests = []PathTest{
996         {"", "."},
997         {".", "."},
998         {"/.", "/"},
999         {"/", "/"},
1000         {"/foo", "/"},
1001         {"x/", "x"},
1002         {"abc", "."},
1003         {"abc/def", "abc"},
1004         {"a/b/.x", "a/b"},
1005         {"a/b/c.", "a/b"},
1006         {"a/b/c.x", "a/b"},
1007 }
1008
1009 var nonwindirtests = []PathTest{
1010         {"////", "/"},
1011 }
1012
1013 var windirtests = []PathTest{
1014         {`c:\`, `c:\`},
1015         {`c:.`, `c:.`},
1016         {`c:\a\b`, `c:\a`},
1017         {`c:a\b`, `c:a`},
1018         {`c:a\b\c`, `c:a\b`},
1019         {`\\host\share`, `\\host\share`},
1020         {`\\host\share\`, `\\host\share\`},
1021         {`\\host\share\a`, `\\host\share\`},
1022         {`\\host\share\a\b`, `\\host\share\a`},
1023         {`\\\\`, `\\\\`},
1024 }
1025
1026 func TestDir(t *testing.T) {
1027         tests := dirtests
1028         if runtime.GOOS == "windows" {
1029                 // make unix tests work on windows
1030                 for i := range tests {
1031                         tests[i].result = filepath.Clean(tests[i].result)
1032                 }
1033                 // add windows specific tests
1034                 tests = append(tests, windirtests...)
1035         } else {
1036                 tests = append(tests, nonwindirtests...)
1037         }
1038         for _, test := range tests {
1039                 if s := filepath.Dir(test.path); s != test.result {
1040                         t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
1041                 }
1042         }
1043 }
1044
1045 type IsAbsTest struct {
1046         path  string
1047         isAbs bool
1048 }
1049
1050 var isabstests = []IsAbsTest{
1051         {"", false},
1052         {"/", true},
1053         {"/usr/bin/gcc", true},
1054         {"..", false},
1055         {"/a/../bb", true},
1056         {".", false},
1057         {"./", false},
1058         {"lala", false},
1059 }
1060
1061 var winisabstests = []IsAbsTest{
1062         {`C:\`, true},
1063         {`c\`, false},
1064         {`c::`, false},
1065         {`c:`, false},
1066         {`/`, false},
1067         {`\`, false},
1068         {`\Windows`, false},
1069         {`c:a\b`, false},
1070         {`c:\a\b`, true},
1071         {`c:/a/b`, true},
1072         {`\\host\share`, true},
1073         {`\\host\share\`, true},
1074         {`\\host\share\foo`, true},
1075         {`//host/share/foo/bar`, true},
1076         {`\\?\a\b\c`, true},
1077         {`\??\a\b\c`, true},
1078 }
1079
1080 func TestIsAbs(t *testing.T) {
1081         var tests []IsAbsTest
1082         if runtime.GOOS == "windows" {
1083                 tests = append(tests, winisabstests...)
1084                 // All non-windows tests should fail, because they have no volume letter.
1085                 for _, test := range isabstests {
1086                         tests = append(tests, IsAbsTest{test.path, false})
1087                 }
1088                 // All non-windows test should work as intended if prefixed with volume letter.
1089                 for _, test := range isabstests {
1090                         tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
1091                 }
1092         } else {
1093                 tests = isabstests
1094         }
1095
1096         for _, test := range tests {
1097                 if r := filepath.IsAbs(test.path); r != test.isAbs {
1098                         t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
1099                 }
1100         }
1101 }
1102
1103 type EvalSymlinksTest struct {
1104         // If dest is empty, the path is created; otherwise the dest is symlinked to the path.
1105         path, dest string
1106 }
1107
1108 var EvalSymlinksTestDirs = []EvalSymlinksTest{
1109         {"test", ""},
1110         {"test/dir", ""},
1111         {"test/dir/link3", "../../"},
1112         {"test/link1", "../test"},
1113         {"test/link2", "dir"},
1114         {"test/linkabs", "/"},
1115         {"test/link4", "../test2"},
1116         {"test2", "test/dir"},
1117         // Issue 23444.
1118         {"src", ""},
1119         {"src/pool", ""},
1120         {"src/pool/test", ""},
1121         {"src/versions", ""},
1122         {"src/versions/current", "../../version"},
1123         {"src/versions/v1", ""},
1124         {"src/versions/v1/modules", ""},
1125         {"src/versions/v1/modules/test", "../../../pool/test"},
1126         {"version", "src/versions/v1"},
1127 }
1128
1129 var EvalSymlinksTests = []EvalSymlinksTest{
1130         {"test", "test"},
1131         {"test/dir", "test/dir"},
1132         {"test/dir/../..", "."},
1133         {"test/link1", "test"},
1134         {"test/link2", "test/dir"},
1135         {"test/link1/dir", "test/dir"},
1136         {"test/link2/..", "test"},
1137         {"test/dir/link3", "."},
1138         {"test/link2/link3/test", "test"},
1139         {"test/linkabs", "/"},
1140         {"test/link4/..", "test"},
1141         {"src/versions/current/modules/test", "src/pool/test"},
1142 }
1143
1144 // simpleJoin builds a file name from the directory and path.
1145 // It does not use Join because we don't want ".." to be evaluated.
1146 func simpleJoin(dir, path string) string {
1147         return dir + string(filepath.Separator) + path
1148 }
1149
1150 func testEvalSymlinks(t *testing.T, path, want string) {
1151         have, err := filepath.EvalSymlinks(path)
1152         if err != nil {
1153                 t.Errorf("EvalSymlinks(%q) error: %v", path, err)
1154                 return
1155         }
1156         if filepath.Clean(have) != filepath.Clean(want) {
1157                 t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
1158         }
1159 }
1160
1161 func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
1162         cwd, err := os.Getwd()
1163         if err != nil {
1164                 t.Fatal(err)
1165         }
1166         defer func() {
1167                 err := os.Chdir(cwd)
1168                 if err != nil {
1169                         t.Fatal(err)
1170                 }
1171         }()
1172
1173         err = os.Chdir(wd)
1174         if err != nil {
1175                 t.Fatal(err)
1176         }
1177
1178         have, err := filepath.EvalSymlinks(path)
1179         if err != nil {
1180                 t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
1181                 return
1182         }
1183         if filepath.Clean(have) != filepath.Clean(want) {
1184                 t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
1185         }
1186 }
1187
1188 func TestEvalSymlinks(t *testing.T) {
1189         testenv.MustHaveSymlink(t)
1190
1191         tmpDir := t.TempDir()
1192
1193         // /tmp may itself be a symlink! Avoid the confusion, although
1194         // it means trusting the thing we're testing.
1195         var err error
1196         tmpDir, err = filepath.EvalSymlinks(tmpDir)
1197         if err != nil {
1198                 t.Fatal("eval symlink for tmp dir:", err)
1199         }
1200
1201         // Create the symlink farm using relative paths.
1202         for _, d := range EvalSymlinksTestDirs {
1203                 var err error
1204                 path := simpleJoin(tmpDir, d.path)
1205                 if d.dest == "" {
1206                         err = os.Mkdir(path, 0755)
1207                 } else {
1208                         err = os.Symlink(d.dest, path)
1209                 }
1210                 if err != nil {
1211                         t.Fatal(err)
1212                 }
1213         }
1214
1215         // Evaluate the symlink farm.
1216         for _, test := range EvalSymlinksTests {
1217                 path := simpleJoin(tmpDir, test.path)
1218
1219                 dest := simpleJoin(tmpDir, test.dest)
1220                 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1221                         dest = test.dest
1222                 }
1223                 testEvalSymlinks(t, path, dest)
1224
1225                 // test EvalSymlinks(".")
1226                 testEvalSymlinksAfterChdir(t, path, ".", ".")
1227
1228                 // test EvalSymlinks("C:.") on Windows
1229                 if runtime.GOOS == "windows" {
1230                         volDot := filepath.VolumeName(tmpDir) + "."
1231                         testEvalSymlinksAfterChdir(t, path, volDot, volDot)
1232                 }
1233
1234                 // test EvalSymlinks(".."+path)
1235                 dotdotPath := simpleJoin("..", test.dest)
1236                 if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
1237                         dotdotPath = test.dest
1238                 }
1239                 testEvalSymlinksAfterChdir(t,
1240                         simpleJoin(tmpDir, "test"),
1241                         simpleJoin("..", test.path),
1242                         dotdotPath)
1243
1244                 // test EvalSymlinks(p) where p is relative path
1245                 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
1246         }
1247 }
1248
1249 func TestEvalSymlinksIsNotExist(t *testing.T) {
1250         testenv.MustHaveSymlink(t)
1251
1252         defer chtmpdir(t)()
1253
1254         _, err := filepath.EvalSymlinks("notexist")
1255         if !os.IsNotExist(err) {
1256                 t.Errorf("expected the file is not found, got %v\n", err)
1257         }
1258
1259         err = os.Symlink("notexist", "link")
1260         if err != nil {
1261                 t.Fatal(err)
1262         }
1263         defer os.Remove("link")
1264
1265         _, err = filepath.EvalSymlinks("link")
1266         if !os.IsNotExist(err) {
1267                 t.Errorf("expected the file is not found, got %v\n", err)
1268         }
1269 }
1270
1271 func TestIssue13582(t *testing.T) {
1272         testenv.MustHaveSymlink(t)
1273
1274         tmpDir := t.TempDir()
1275
1276         dir := filepath.Join(tmpDir, "dir")
1277         err := os.Mkdir(dir, 0755)
1278         if err != nil {
1279                 t.Fatal(err)
1280         }
1281         linkToDir := filepath.Join(tmpDir, "link_to_dir")
1282         err = os.Symlink(dir, linkToDir)
1283         if err != nil {
1284                 t.Fatal(err)
1285         }
1286         file := filepath.Join(linkToDir, "file")
1287         err = os.WriteFile(file, nil, 0644)
1288         if err != nil {
1289                 t.Fatal(err)
1290         }
1291         link1 := filepath.Join(linkToDir, "link1")
1292         err = os.Symlink(file, link1)
1293         if err != nil {
1294                 t.Fatal(err)
1295         }
1296         link2 := filepath.Join(linkToDir, "link2")
1297         err = os.Symlink(link1, link2)
1298         if err != nil {
1299                 t.Fatal(err)
1300         }
1301
1302         // /tmp may itself be a symlink!
1303         realTmpDir, err := filepath.EvalSymlinks(tmpDir)
1304         if err != nil {
1305                 t.Fatal(err)
1306         }
1307         realDir := filepath.Join(realTmpDir, "dir")
1308         realFile := filepath.Join(realDir, "file")
1309
1310         tests := []struct {
1311                 path, want string
1312         }{
1313                 {dir, realDir},
1314                 {linkToDir, realDir},
1315                 {file, realFile},
1316                 {link1, realFile},
1317                 {link2, realFile},
1318         }
1319         for i, test := range tests {
1320                 have, err := filepath.EvalSymlinks(test.path)
1321                 if err != nil {
1322                         t.Fatal(err)
1323                 }
1324                 if have != test.want {
1325                         t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
1326                 }
1327         }
1328 }
1329
1330 // Issue 57905.
1331 func TestRelativeSymlinkToAbsolute(t *testing.T) {
1332         testenv.MustHaveSymlink(t)
1333         // Not parallel: uses os.Chdir.
1334
1335         tmpDir := t.TempDir()
1336         chdir(t, tmpDir)
1337
1338         // Create "link" in the current working directory as a symlink to an arbitrary
1339         // absolute path. On macOS, this path is likely to begin with a symlink
1340         // itself: generally either in /var (symlinked to "private/var") or /tmp
1341         // (symlinked to "private/tmp").
1342         if err := os.Symlink(tmpDir, "link"); err != nil {
1343                 t.Fatal(err)
1344         }
1345         t.Logf(`os.Symlink(%q, "link")`, tmpDir)
1346
1347         p, err := filepath.EvalSymlinks("link")
1348         if err != nil {
1349                 t.Fatalf(`EvalSymlinks("link"): %v`, err)
1350         }
1351         want, err := filepath.EvalSymlinks(tmpDir)
1352         if err != nil {
1353                 t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
1354         }
1355         if p != want {
1356                 t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
1357         }
1358         t.Logf(`EvalSymlinks("link") = %q`, p)
1359 }
1360
1361 // Test directories relative to temporary directory.
1362 // The tests are run in absTestDirs[0].
1363 var absTestDirs = []string{
1364         "a",
1365         "a/b",
1366         "a/b/c",
1367 }
1368
1369 // Test paths relative to temporary directory. $ expands to the directory.
1370 // The tests are run in absTestDirs[0].
1371 // We create absTestDirs first.
1372 var absTests = []string{
1373         ".",
1374         "b",
1375         "b/",
1376         "../a",
1377         "../a/b",
1378         "../a/b/./c/../../.././a",
1379         "../a/b/./c/../../.././a/",
1380         "$",
1381         "$/.",
1382         "$/a/../a/b",
1383         "$/a/b/c/../../.././a",
1384         "$/a/b/c/../../.././a/",
1385 }
1386
1387 func TestAbs(t *testing.T) {
1388         root := t.TempDir()
1389         wd, err := os.Getwd()
1390         if err != nil {
1391                 t.Fatal("getwd failed: ", err)
1392         }
1393         err = os.Chdir(root)
1394         if err != nil {
1395                 t.Fatal("chdir failed: ", err)
1396         }
1397         defer os.Chdir(wd)
1398
1399         for _, dir := range absTestDirs {
1400                 err = os.Mkdir(dir, 0777)
1401                 if err != nil {
1402                         t.Fatal("Mkdir failed: ", err)
1403                 }
1404         }
1405
1406         if runtime.GOOS == "windows" {
1407                 vol := filepath.VolumeName(root)
1408                 var extra []string
1409                 for _, path := range absTests {
1410                         if strings.Contains(path, "$") {
1411                                 continue
1412                         }
1413                         path = vol + path
1414                         extra = append(extra, path)
1415                 }
1416                 absTests = append(absTests, extra...)
1417         }
1418
1419         err = os.Chdir(absTestDirs[0])
1420         if err != nil {
1421                 t.Fatal("chdir failed: ", err)
1422         }
1423
1424         for _, path := range absTests {
1425                 path = strings.ReplaceAll(path, "$", root)
1426                 info, err := os.Stat(path)
1427                 if err != nil {
1428                         t.Errorf("%s: %s", path, err)
1429                         continue
1430                 }
1431
1432                 abspath, err := filepath.Abs(path)
1433                 if err != nil {
1434                         t.Errorf("Abs(%q) error: %v", path, err)
1435                         continue
1436                 }
1437                 absinfo, err := os.Stat(abspath)
1438                 if err != nil || !os.SameFile(absinfo, info) {
1439                         t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
1440                 }
1441                 if !filepath.IsAbs(abspath) {
1442                         t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
1443                 }
1444                 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1445                         t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
1446                 }
1447         }
1448 }
1449
1450 // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
1451 // We test it separately from all other absTests because the empty string is not
1452 // a valid path, so it can't be used with os.Stat.
1453 func TestAbsEmptyString(t *testing.T) {
1454         root := t.TempDir()
1455
1456         wd, err := os.Getwd()
1457         if err != nil {
1458                 t.Fatal("getwd failed: ", err)
1459         }
1460         err = os.Chdir(root)
1461         if err != nil {
1462                 t.Fatal("chdir failed: ", err)
1463         }
1464         defer os.Chdir(wd)
1465
1466         info, err := os.Stat(root)
1467         if err != nil {
1468                 t.Fatalf("%s: %s", root, err)
1469         }
1470
1471         abspath, err := filepath.Abs("")
1472         if err != nil {
1473                 t.Fatalf(`Abs("") error: %v`, err)
1474         }
1475         absinfo, err := os.Stat(abspath)
1476         if err != nil || !os.SameFile(absinfo, info) {
1477                 t.Errorf(`Abs("")=%q, not the same file`, abspath)
1478         }
1479         if !filepath.IsAbs(abspath) {
1480                 t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
1481         }
1482         if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
1483                 t.Errorf(`Abs("")=%q, isn't clean`, abspath)
1484         }
1485 }
1486
1487 type RelTests struct {
1488         root, path, want string
1489 }
1490
1491 var reltests = []RelTests{
1492         {"a/b", "a/b", "."},
1493         {"a/b/.", "a/b", "."},
1494         {"a/b", "a/b/.", "."},
1495         {"./a/b", "a/b", "."},
1496         {"a/b", "./a/b", "."},
1497         {"ab/cd", "ab/cde", "../cde"},
1498         {"ab/cd", "ab/c", "../c"},
1499         {"a/b", "a/b/c/d", "c/d"},
1500         {"a/b", "a/b/../c", "../c"},
1501         {"a/b/../c", "a/b", "../b"},
1502         {"a/b/c", "a/c/d", "../../c/d"},
1503         {"a/b", "c/d", "../../c/d"},
1504         {"a/b/c/d", "a/b", "../.."},
1505         {"a/b/c/d", "a/b/", "../.."},
1506         {"a/b/c/d/", "a/b", "../.."},
1507         {"a/b/c/d/", "a/b/", "../.."},
1508         {"../../a/b", "../../a/b/c/d", "c/d"},
1509         {"/a/b", "/a/b", "."},
1510         {"/a/b/.", "/a/b", "."},
1511         {"/a/b", "/a/b/.", "."},
1512         {"/ab/cd", "/ab/cde", "../cde"},
1513         {"/ab/cd", "/ab/c", "../c"},
1514         {"/a/b", "/a/b/c/d", "c/d"},
1515         {"/a/b", "/a/b/../c", "../c"},
1516         {"/a/b/../c", "/a/b", "../b"},
1517         {"/a/b/c", "/a/c/d", "../../c/d"},
1518         {"/a/b", "/c/d", "../../c/d"},
1519         {"/a/b/c/d", "/a/b", "../.."},
1520         {"/a/b/c/d", "/a/b/", "../.."},
1521         {"/a/b/c/d/", "/a/b", "../.."},
1522         {"/a/b/c/d/", "/a/b/", "../.."},
1523         {"/../../a/b", "/../../a/b/c/d", "c/d"},
1524         {".", "a/b", "a/b"},
1525         {".", "..", ".."},
1526
1527         // can't do purely lexically
1528         {"..", ".", "err"},
1529         {"..", "a", "err"},
1530         {"../..", "..", "err"},
1531         {"a", "/a", "err"},
1532         {"/a", "a", "err"},
1533 }
1534
1535 var winreltests = []RelTests{
1536         {`C:a\b\c`, `C:a/b/d`, `..\d`},
1537         {`C:\`, `D:\`, `err`},
1538         {`C:`, `D:`, `err`},
1539         {`C:\Projects`, `c:\projects\src`, `src`},
1540         {`C:\Projects`, `c:\projects`, `.`},
1541         {`C:\Projects\a\..`, `c:\projects`, `.`},
1542         {`\\host\share`, `\\host\share\file.txt`, `file.txt`},
1543 }
1544
1545 func TestRel(t *testing.T) {
1546         tests := append([]RelTests{}, reltests...)
1547         if runtime.GOOS == "windows" {
1548                 for i := range tests {
1549                         tests[i].want = filepath.FromSlash(tests[i].want)
1550                 }
1551                 tests = append(tests, winreltests...)
1552         }
1553         for _, test := range tests {
1554                 got, err := filepath.Rel(test.root, test.path)
1555                 if test.want == "err" {
1556                         if err == nil {
1557                                 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
1558                         }
1559                         continue
1560                 }
1561                 if err != nil {
1562                         t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
1563                 }
1564                 if got != test.want {
1565                         t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
1566                 }
1567         }
1568 }
1569
1570 type VolumeNameTest struct {
1571         path string
1572         vol  string
1573 }
1574
1575 var volumenametests = []VolumeNameTest{
1576         {`c:/foo/bar`, `c:`},
1577         {`c:`, `c:`},
1578         {`c:\`, `c:`},
1579         {`2:`, `2:`},
1580         {``, ``},
1581         {`\\\host`, `\\\host`},
1582         {`\\\host\`, `\\\host`},
1583         {`\\\host\share`, `\\\host`},
1584         {`\\\host\\share`, `\\\host`},
1585         {`\\host`, `\\host`},
1586         {`//host`, `\\host`},
1587         {`\\host\`, `\\host\`},
1588         {`//host/`, `\\host\`},
1589         {`\\host\share`, `\\host\share`},
1590         {`//host/share`, `\\host\share`},
1591         {`\\host\share\`, `\\host\share`},
1592         {`//host/share/`, `\\host\share`},
1593         {`\\host\share\foo`, `\\host\share`},
1594         {`//host/share/foo`, `\\host\share`},
1595         {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
1596         {`//host/share//foo///bar////baz`, `\\host\share`},
1597         {`\\host\share\foo\..\bar`, `\\host\share`},
1598         {`//host/share/foo/../bar`, `\\host\share`},
1599         {`//.`, `\\.`},
1600         {`//./`, `\\.\`},
1601         {`//./NUL`, `\\.\NUL`},
1602         {`//?`, `\\?`},
1603         {`//?/`, `\\?\`},
1604         {`//?/NUL`, `\\?\NUL`},
1605         {`/??`, `\??`},
1606         {`/??/`, `\??\`},
1607         {`/??/NUL`, `\??\NUL`},
1608         {`//./a/b`, `\\.\a`},
1609         {`//./C:`, `\\.\C:`},
1610         {`//./C:/`, `\\.\C:`},
1611         {`//./C:/a/b/c`, `\\.\C:`},
1612         {`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
1613         {`//./UNC/host`, `\\.\UNC\host`},
1614         {`//./UNC/host\`, `\\.\UNC\host\`},
1615         {`//./UNC`, `\\.\UNC`},
1616         {`//./UNC/`, `\\.\UNC\`},
1617         {`\\?\x`, `\\?\x`},
1618         {`\??\x`, `\??\x`},
1619 }
1620
1621 func TestVolumeName(t *testing.T) {
1622         if runtime.GOOS != "windows" {
1623                 return
1624         }
1625         for _, v := range volumenametests {
1626                 if vol := filepath.VolumeName(v.path); vol != v.vol {
1627                         t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
1628                 }
1629         }
1630 }
1631
1632 func TestDriveLetterInEvalSymlinks(t *testing.T) {
1633         if runtime.GOOS != "windows" {
1634                 return
1635         }
1636         wd, _ := os.Getwd()
1637         if len(wd) < 3 {
1638                 t.Errorf("Current directory path %q is too short", wd)
1639         }
1640         lp := strings.ToLower(wd)
1641         up := strings.ToUpper(wd)
1642         flp, err := filepath.EvalSymlinks(lp)
1643         if err != nil {
1644                 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
1645         }
1646         fup, err := filepath.EvalSymlinks(up)
1647         if err != nil {
1648                 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
1649         }
1650         if flp != fup {
1651                 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
1652         }
1653 }
1654
1655 func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
1656         if runtime.GOOS == "ios" {
1657                 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
1658         }
1659         root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
1660         utf16 := filepath.Join(root, "utf16")
1661         utf8 := filepath.Join(root, "utf8")
1662         seenUTF16 := false
1663         seenUTF8 := false
1664         err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
1665                 if err != nil {
1666                         t.Fatal(err)
1667                 }
1668
1669                 switch pth {
1670                 case utf16:
1671                         seenUTF16 = true
1672                         return filepath.SkipDir
1673                 case utf8:
1674                         if !seenUTF16 {
1675                                 t.Fatal("filepath.Walk out of order - utf8 before utf16")
1676                         }
1677                         seenUTF8 = true
1678                 }
1679                 return nil
1680         })
1681         if err != nil {
1682                 t.Fatal(err)
1683         }
1684         if !seenUTF8 {
1685                 t.Fatalf("%q not seen", utf8)
1686         }
1687 }
1688
1689 func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
1690         tmpdir := t.TempDir()
1691
1692         wd, err := os.Getwd()
1693         if err != nil {
1694                 t.Fatal(err)
1695         }
1696         defer os.Chdir(wd)
1697
1698         err = os.Chdir(tmpdir)
1699         if err != nil {
1700                 t.Fatal(err)
1701         }
1702
1703         err = mklink(tmpdir, "link")
1704         if err != nil {
1705                 t.Fatal(err)
1706         }
1707
1708         var visited []string
1709         err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
1710                 if err != nil {
1711                         t.Fatal(err)
1712                 }
1713                 rel, err := filepath.Rel(tmpdir, path)
1714                 if err != nil {
1715                         t.Fatal(err)
1716                 }
1717                 visited = append(visited, rel)
1718                 return nil
1719         })
1720         if err != nil {
1721                 t.Fatal(err)
1722         }
1723         sort.Strings(visited)
1724         want := []string{".", "link"}
1725         if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
1726                 t.Errorf("unexpected paths visited %q, want %q", visited, want)
1727         }
1728 }
1729
1730 func TestWalkSymlink(t *testing.T) {
1731         testenv.MustHaveSymlink(t)
1732         testWalkSymlink(t, os.Symlink)
1733 }
1734
1735 func TestIssue29372(t *testing.T) {
1736         tmpDir := t.TempDir()
1737
1738         path := filepath.Join(tmpDir, "file.txt")
1739         err := os.WriteFile(path, nil, 0644)
1740         if err != nil {
1741                 t.Fatal(err)
1742         }
1743
1744         pathSeparator := string(filepath.Separator)
1745         tests := []string{
1746                 path + strings.Repeat(pathSeparator, 1),
1747                 path + strings.Repeat(pathSeparator, 2),
1748                 path + strings.Repeat(pathSeparator, 1) + ".",
1749                 path + strings.Repeat(pathSeparator, 2) + ".",
1750                 path + strings.Repeat(pathSeparator, 1) + "..",
1751                 path + strings.Repeat(pathSeparator, 2) + "..",
1752         }
1753
1754         for i, test := range tests {
1755                 _, err = filepath.EvalSymlinks(test)
1756                 if err != syscall.ENOTDIR {
1757                         t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
1758                 }
1759         }
1760 }
1761
1762 // Issue 30520 part 1.
1763 func TestEvalSymlinksAboveRoot(t *testing.T) {
1764         testenv.MustHaveSymlink(t)
1765
1766         t.Parallel()
1767
1768         tmpDir := t.TempDir()
1769
1770         evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
1771         if err != nil {
1772                 t.Fatal(err)
1773         }
1774
1775         if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
1776                 t.Fatal(err)
1777         }
1778         if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
1779                 t.Fatal(err)
1780         }
1781         if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
1782                 t.Fatal(err)
1783         }
1784
1785         // Count the number of ".." elements to get to the root directory.
1786         vol := filepath.VolumeName(evalTmpDir)
1787         c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
1788         var dd []string
1789         for i := 0; i < c+2; i++ {
1790                 dd = append(dd, "..")
1791         }
1792
1793         wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
1794
1795         // Try different numbers of "..".
1796         for _, i := range []int{c, c + 1, c + 2} {
1797                 check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
1798                 resolved, err := filepath.EvalSymlinks(check)
1799                 switch {
1800                 case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
1801                         // On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
1802                         testenv.SkipFlaky(t, 37910)
1803                 case err != nil:
1804                         t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1805                 case !strings.HasSuffix(resolved, wantSuffix):
1806                         t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1807                 default:
1808                         t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1809                 }
1810         }
1811 }
1812
1813 // Issue 30520 part 2.
1814 func TestEvalSymlinksAboveRootChdir(t *testing.T) {
1815         testenv.MustHaveSymlink(t)
1816
1817         tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
1818         if err != nil {
1819                 t.Fatal(err)
1820         }
1821         defer os.RemoveAll(tmpDir)
1822         chdir(t, tmpDir)
1823
1824         subdir := filepath.Join("a", "b")
1825         if err := os.MkdirAll(subdir, 0777); err != nil {
1826                 t.Fatal(err)
1827         }
1828         if err := os.Symlink(subdir, "c"); err != nil {
1829                 t.Fatal(err)
1830         }
1831         if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
1832                 t.Fatal(err)
1833         }
1834
1835         subdir = filepath.Join("d", "e", "f")
1836         if err := os.MkdirAll(subdir, 0777); err != nil {
1837                 t.Fatal(err)
1838         }
1839         if err := os.Chdir(subdir); err != nil {
1840                 t.Fatal(err)
1841         }
1842
1843         check := filepath.Join("..", "..", "..", "c", "file")
1844         wantSuffix := filepath.Join("a", "b", "file")
1845         if resolved, err := filepath.EvalSymlinks(check); err != nil {
1846                 t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
1847         } else if !strings.HasSuffix(resolved, wantSuffix) {
1848                 t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
1849         } else {
1850                 t.Logf("EvalSymlinks(%q) = %q", check, resolved)
1851         }
1852 }
1853
1854 func TestIssue51617(t *testing.T) {
1855         dir := t.TempDir()
1856         for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
1857                 if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
1858                         t.Fatal(err)
1859                 }
1860         }
1861         bad := filepath.Join(dir, "a", "bad")
1862         if err := os.Chmod(bad, 0); err != nil {
1863                 t.Fatal(err)
1864         }
1865         defer os.Chmod(bad, 0700) // avoid errors on cleanup
1866         var saw []string
1867         err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1868                 if err != nil {
1869                         return filepath.SkipDir
1870                 }
1871                 if d.IsDir() {
1872                         rel, err := filepath.Rel(dir, path)
1873                         if err != nil {
1874                                 t.Fatal(err)
1875                         }
1876                         saw = append(saw, rel)
1877                 }
1878                 return nil
1879         })
1880         if err != nil {
1881                 t.Fatal(err)
1882         }
1883         want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
1884         if !reflect.DeepEqual(saw, want) {
1885                 t.Errorf("got directories %v, want %v", saw, want)
1886         }
1887 }
1888
1889 func TestEscaping(t *testing.T) {
1890         dir1 := t.TempDir()
1891         dir2 := t.TempDir()
1892         chdir(t, dir1)
1893
1894         for _, p := range []string{
1895                 filepath.Join(dir2, "x"),
1896         } {
1897                 if !filepath.IsLocal(p) {
1898                         continue
1899                 }
1900                 f, err := os.Create(p)
1901                 if err != nil {
1902                         f.Close()
1903                 }
1904                 ents, err := os.ReadDir(dir2)
1905                 if err != nil {
1906                         t.Fatal(err)
1907                 }
1908                 for _, e := range ents {
1909                         t.Fatalf("found: %v", e.Name())
1910                 }
1911         }
1912 }