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