Source file 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  	"strings"
    18  	"syscall"
    19  	"testing"
    20  )
    21  
    22  type PathTest struct {
    23  	path, result string
    24  }
    25  
    26  var cleantests = []PathTest{
    27  	// Already clean
    28  	{"abc", "abc"},
    29  	{"abc/def", "abc/def"},
    30  	{"a/b/c", "a/b/c"},
    31  	{".", "."},
    32  	{"..", ".."},
    33  	{"../..", "../.."},
    34  	{"../../abc", "../../abc"},
    35  	{"/abc", "/abc"},
    36  	{"/", "/"},
    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  
    77  var nonwincleantests = []PathTest{
    78  	// Remove leading doubled slash
    79  	{"//abc", "/abc"},
    80  	{"///abc", "/abc"},
    81  	{"//abc//", "/abc"},
    82  }
    83  
    84  var 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  
   125  func 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  
   160  type IsLocalTest struct {
   161  	path    string
   162  	isLocal bool
   163  }
   164  
   165  var 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  
   181  var 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  
   220  var plan9islocaltests = []IsLocalTest{
   221  	{"#a", false},
   222  }
   223  
   224  func 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  
   239  type LocalizeTest struct {
   240  	path string
   241  	want string
   242  }
   243  
   244  var 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  
   259  var plan9localizetests = []LocalizeTest{
   260  	{"#a", ""},
   261  	{`a\b:c`, `a\b:c`},
   262  }
   263  
   264  var unixlocalizetests = []LocalizeTest{
   265  	{"#a", "#a"},
   266  	{`a\b:c`, `a\b:c`},
   267  }
   268  
   269  var 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  
   281  func 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  
   306  const sep = filepath.Separator
   307  
   308  var 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  
   315  func 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  
   326  type SplitListTest struct {
   327  	list   string
   328  	result []string
   329  }
   330  
   331  const lsep = filepath.ListSeparator
   332  
   333  var 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  
   339  var 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  
   361  func 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); !slices.Equal(l, test.result) {
   368  			t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
   369  		}
   370  	}
   371  }
   372  
   373  type SplitTest struct {
   374  	path, dir, file string
   375  }
   376  
   377  var unixsplittests = []SplitTest{
   378  	{"a/b", "a/", "b"},
   379  	{"a/b/", "a/b/", ""},
   380  	{"a/", "a/", ""},
   381  	{"a", "", "a"},
   382  	{"/", "/", ""},
   383  }
   384  
   385  var 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  
   398  func 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  
   411  type JoinTest struct {
   412  	elem []string
   413  	path string
   414  }
   415  
   416  var 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  
   443  var nonwinjointests = []JoinTest{
   444  	{[]string{"//", "a"}, "/a"},
   445  }
   446  
   447  var 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  
   481  func 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  
   495  type ExtTest struct {
   496  	path, ext string
   497  }
   498  
   499  var 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  
   507  func 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  
   515  type Node struct {
   516  	name    string
   517  	entries []*Node // nil if the entry is a file
   518  	mark    int
   519  }
   520  
   521  var 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  
   547  func 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  
   554  func 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  
   569  func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
   570  
   571  func 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.
   583  func 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  // tempDirCanonical returns a temporary directory for the test to use, ensuring
   601  // that the returned path does not contain symlinks.
   602  func tempDirCanonical(t *testing.T) string {
   603  	dir := t.TempDir()
   604  
   605  	cdir, err := filepath.EvalSymlinks(dir)
   606  	if err != nil {
   607  		t.Errorf("tempDirCanonical: %v", err)
   608  	}
   609  
   610  	return cdir
   611  }
   612  
   613  func TestWalk(t *testing.T) {
   614  	walk := func(root string, fn fs.WalkDirFunc) error {
   615  		return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
   616  			return fn(path, fs.FileInfoToDirEntry(info), err)
   617  		})
   618  	}
   619  	testWalk(t, walk, 1)
   620  }
   621  
   622  func TestWalkDir(t *testing.T) {
   623  	testWalk(t, filepath.WalkDir, 2)
   624  }
   625  
   626  func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
   627  	t.Chdir(t.TempDir())
   628  
   629  	makeTree(t)
   630  	errors := make([]error, 0, 10)
   631  	clear := true
   632  	markFn := func(path string, d fs.DirEntry, err error) error {
   633  		return mark(d, err, &errors, clear)
   634  	}
   635  	// Expect no errors.
   636  	err := walk(tree.name, markFn)
   637  	if err != nil {
   638  		t.Fatalf("no error expected, found: %s", err)
   639  	}
   640  	if len(errors) != 0 {
   641  		t.Fatalf("unexpected errors: %s", errors)
   642  	}
   643  	checkMarks(t, true)
   644  	errors = errors[0:0]
   645  
   646  	t.Run("PermErr", func(t *testing.T) {
   647  		// Test permission errors. Only possible if we're not root
   648  		// and only on some file systems (AFS, FAT).  To avoid errors during
   649  		// all.bash on those file systems, skip during go test -short.
   650  		// Chmod is not supported on wasip1.
   651  		if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
   652  			t.Skip("skipping on " + runtime.GOOS)
   653  		}
   654  		if os.Getuid() == 0 {
   655  			t.Skip("skipping as root")
   656  		}
   657  		if testing.Short() {
   658  			t.Skip("skipping in short mode")
   659  		}
   660  
   661  		// introduce 2 errors: chmod top-level directories to 0
   662  		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
   663  		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
   664  
   665  		// 3) capture errors, expect two.
   666  		// mark respective subtrees manually
   667  		markTree(tree.entries[1])
   668  		markTree(tree.entries[3])
   669  		// correct double-marking of directory itself
   670  		tree.entries[1].mark -= errVisit
   671  		tree.entries[3].mark -= errVisit
   672  		err := walk(tree.name, markFn)
   673  		if err != nil {
   674  			t.Fatalf("expected no error return from Walk, got %s", err)
   675  		}
   676  		if len(errors) != 2 {
   677  			t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
   678  		}
   679  		// the inaccessible subtrees were marked manually
   680  		checkMarks(t, true)
   681  		errors = errors[0:0]
   682  
   683  		// 4) capture errors, stop after first error.
   684  		// mark respective subtrees manually
   685  		markTree(tree.entries[1])
   686  		markTree(tree.entries[3])
   687  		// correct double-marking of directory itself
   688  		tree.entries[1].mark -= errVisit
   689  		tree.entries[3].mark -= errVisit
   690  		clear = false // error will stop processing
   691  		err = walk(tree.name, markFn)
   692  		if err == nil {
   693  			t.Fatalf("expected error return from Walk")
   694  		}
   695  		if len(errors) != 1 {
   696  			t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
   697  		}
   698  		// the inaccessible subtrees were marked manually
   699  		checkMarks(t, false)
   700  		errors = errors[0:0]
   701  
   702  		// restore permissions
   703  		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
   704  		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
   705  	})
   706  }
   707  
   708  func touch(t *testing.T, name string) {
   709  	f, err := os.Create(name)
   710  	if err != nil {
   711  		t.Fatal(err)
   712  	}
   713  	if err := f.Close(); err != nil {
   714  		t.Fatal(err)
   715  	}
   716  }
   717  
   718  func TestWalkSkipDirOnFile(t *testing.T) {
   719  	td := t.TempDir()
   720  
   721  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   722  		t.Fatal(err)
   723  	}
   724  	touch(t, filepath.Join(td, "dir/foo1"))
   725  	touch(t, filepath.Join(td, "dir/foo2"))
   726  
   727  	sawFoo2 := false
   728  	walker := func(path string) error {
   729  		if strings.HasSuffix(path, "foo2") {
   730  			sawFoo2 = true
   731  		}
   732  		if strings.HasSuffix(path, "foo1") {
   733  			return filepath.SkipDir
   734  		}
   735  		return nil
   736  	}
   737  	walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
   738  	walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
   739  
   740  	check := func(t *testing.T, walk func(root string) error, root string) {
   741  		t.Helper()
   742  		sawFoo2 = false
   743  		err := walk(root)
   744  		if err != nil {
   745  			t.Fatal(err)
   746  		}
   747  		if sawFoo2 {
   748  			t.Errorf("SkipDir on file foo1 did not block processing of foo2")
   749  		}
   750  	}
   751  
   752  	t.Run("Walk", func(t *testing.T) {
   753  		Walk := func(root string) error { return filepath.Walk(td, walkFn) }
   754  		check(t, Walk, td)
   755  		check(t, Walk, filepath.Join(td, "dir"))
   756  	})
   757  	t.Run("WalkDir", func(t *testing.T) {
   758  		WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
   759  		check(t, WalkDir, td)
   760  		check(t, WalkDir, filepath.Join(td, "dir"))
   761  	})
   762  }
   763  
   764  func TestWalkSkipAllOnFile(t *testing.T) {
   765  	td := t.TempDir()
   766  
   767  	if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
   768  		t.Fatal(err)
   769  	}
   770  	if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
   771  		t.Fatal(err)
   772  	}
   773  
   774  	touch(t, filepath.Join(td, "dir", "foo1"))
   775  	touch(t, filepath.Join(td, "dir", "foo2"))
   776  	touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
   777  	touch(t, filepath.Join(td, "dir", "foo4"))
   778  	touch(t, filepath.Join(td, "dir2", "bar"))
   779  	touch(t, filepath.Join(td, "last"))
   780  
   781  	remainingWereSkipped := true
   782  	walker := func(path string) error {
   783  		if strings.HasSuffix(path, "foo2") {
   784  			return filepath.SkipAll
   785  		}
   786  
   787  		if strings.HasSuffix(path, "foo3") ||
   788  			strings.HasSuffix(path, "foo4") ||
   789  			strings.HasSuffix(path, "bar") ||
   790  			strings.HasSuffix(path, "last") {
   791  			remainingWereSkipped = false
   792  		}
   793  		return nil
   794  	}
   795  
   796  	walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
   797  	walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
   798  
   799  	check := func(t *testing.T, walk func(root string) error, root string) {
   800  		t.Helper()
   801  		remainingWereSkipped = true
   802  		if err := walk(root); err != nil {
   803  			t.Fatal(err)
   804  		}
   805  		if !remainingWereSkipped {
   806  			t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
   807  		}
   808  	}
   809  
   810  	t.Run("Walk", func(t *testing.T) {
   811  		Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
   812  		check(t, Walk, td)
   813  		check(t, Walk, filepath.Join(td, "dir"))
   814  	})
   815  	t.Run("WalkDir", func(t *testing.T) {
   816  		WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
   817  		check(t, WalkDir, td)
   818  		check(t, WalkDir, filepath.Join(td, "dir"))
   819  	})
   820  }
   821  
   822  func TestWalkFileError(t *testing.T) {
   823  	td := t.TempDir()
   824  
   825  	touch(t, filepath.Join(td, "foo"))
   826  	touch(t, filepath.Join(td, "bar"))
   827  	dir := filepath.Join(td, "dir")
   828  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   829  		t.Fatal(err)
   830  	}
   831  	touch(t, filepath.Join(dir, "baz"))
   832  	touch(t, filepath.Join(dir, "stat-error"))
   833  	defer func() {
   834  		*filepath.LstatP = os.Lstat
   835  	}()
   836  	statErr := errors.New("some stat error")
   837  	*filepath.LstatP = func(path string) (fs.FileInfo, error) {
   838  		if strings.HasSuffix(path, "stat-error") {
   839  			return nil, statErr
   840  		}
   841  		return os.Lstat(path)
   842  	}
   843  	got := map[string]error{}
   844  	err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
   845  		rel, _ := filepath.Rel(td, path)
   846  		got[filepath.ToSlash(rel)] = err
   847  		return nil
   848  	})
   849  	if err != nil {
   850  		t.Errorf("Walk error: %v", err)
   851  	}
   852  	want := map[string]error{
   853  		".":              nil,
   854  		"foo":            nil,
   855  		"bar":            nil,
   856  		"dir":            nil,
   857  		"dir/baz":        nil,
   858  		"dir/stat-error": statErr,
   859  	}
   860  	if !reflect.DeepEqual(got, want) {
   861  		t.Errorf("Walked %#v; want %#v", got, want)
   862  	}
   863  }
   864  
   865  func TestWalkSymlinkRoot(t *testing.T) {
   866  	testenv.MustHaveSymlink(t)
   867  
   868  	td := t.TempDir()
   869  	dir := filepath.Join(td, "dir")
   870  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   871  		t.Fatal(err)
   872  	}
   873  	touch(t, filepath.Join(dir, "foo"))
   874  
   875  	link := filepath.Join(td, "link")
   876  	if err := os.Symlink("dir", link); err != nil {
   877  		t.Fatal(err)
   878  	}
   879  
   880  	abslink := filepath.Join(td, "abslink")
   881  	if err := os.Symlink(dir, abslink); err != nil {
   882  		t.Fatal(err)
   883  	}
   884  
   885  	linklink := filepath.Join(td, "linklink")
   886  	if err := os.Symlink("link", linklink); err != nil {
   887  		t.Fatal(err)
   888  	}
   889  
   890  	// Per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12:
   891  	// “A pathname that contains at least one non- <slash> character and that ends
   892  	// with one or more trailing <slash> characters shall not be resolved
   893  	// successfully unless the last pathname component before the trailing <slash>
   894  	// characters names an existing directory [...].”
   895  	//
   896  	// Since Walk does not traverse symlinks itself, its behavior should depend on
   897  	// whether the path passed to Walk ends in a slash: if it does not end in a slash,
   898  	// Walk should report the symlink itself (since it is the last pathname component);
   899  	// but if it does end in a slash, Walk should walk the directory to which the symlink
   900  	// refers (since it must be fully resolved before walking).
   901  	for _, tt := range []struct {
   902  		desc      string
   903  		root      string
   904  		want      []string
   905  		buggyGOOS []string
   906  	}{
   907  		{
   908  			desc: "no slash",
   909  			root: link,
   910  			want: []string{link},
   911  		},
   912  		{
   913  			desc: "slash",
   914  			root: link + string(filepath.Separator),
   915  			want: []string{link, filepath.Join(link, "foo")},
   916  		},
   917  		{
   918  			desc: "abs no slash",
   919  			root: abslink,
   920  			want: []string{abslink},
   921  		},
   922  		{
   923  			desc: "abs with slash",
   924  			root: abslink + string(filepath.Separator),
   925  			want: []string{abslink, filepath.Join(abslink, "foo")},
   926  		},
   927  		{
   928  			desc: "double link no slash",
   929  			root: linklink,
   930  			want: []string{linklink},
   931  		},
   932  		{
   933  			desc:      "double link with slash",
   934  			root:      linklink + string(filepath.Separator),
   935  			want:      []string{linklink, filepath.Join(linklink, "foo")},
   936  			buggyGOOS: []string{"darwin", "ios"}, // https://go.dev/issue/59586
   937  		},
   938  	} {
   939  		tt := tt
   940  		t.Run(tt.desc, func(t *testing.T) {
   941  			var walked []string
   942  			err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
   943  				if err != nil {
   944  					return err
   945  				}
   946  				t.Logf("%#q: %v", path, info.Mode())
   947  				walked = append(walked, filepath.Clean(path))
   948  				return nil
   949  			})
   950  			if err != nil {
   951  				t.Fatal(err)
   952  			}
   953  
   954  			if !slices.Equal(walked, tt.want) {
   955  				t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
   956  				if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
   957  					t.Logf("(ignoring known bug on %v)", runtime.GOOS)
   958  				} else {
   959  					t.Fail()
   960  				}
   961  			}
   962  		})
   963  	}
   964  }
   965  
   966  var basetests = []PathTest{
   967  	{"", "."},
   968  	{".", "."},
   969  	{"/.", "."},
   970  	{"/", "/"},
   971  	{"////", "/"},
   972  	{"x/", "x"},
   973  	{"abc", "abc"},
   974  	{"abc/def", "def"},
   975  	{"a/b/.x", ".x"},
   976  	{"a/b/c.", "c."},
   977  	{"a/b/c.x", "c.x"},
   978  }
   979  
   980  var winbasetests = []PathTest{
   981  	{`c:\`, `\`},
   982  	{`c:.`, `.`},
   983  	{`c:\a\b`, `b`},
   984  	{`c:a\b`, `b`},
   985  	{`c:a\b\c`, `c`},
   986  	{`\\host\share\`, `\`},
   987  	{`\\host\share\a`, `a`},
   988  	{`\\host\share\a\b`, `b`},
   989  }
   990  
   991  func TestBase(t *testing.T) {
   992  	tests := basetests
   993  	if runtime.GOOS == "windows" {
   994  		// make unix tests work on windows
   995  		for i := range tests {
   996  			tests[i].result = filepath.Clean(tests[i].result)
   997  		}
   998  		// add windows specific tests
   999  		tests = append(tests, winbasetests...)
  1000  	}
  1001  	for _, test := range tests {
  1002  		if s := filepath.Base(test.path); s != test.result {
  1003  			t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
  1004  		}
  1005  	}
  1006  }
  1007  
  1008  var dirtests = []PathTest{
  1009  	{"", "."},
  1010  	{".", "."},
  1011  	{"/.", "/"},
  1012  	{"/", "/"},
  1013  	{"/foo", "/"},
  1014  	{"x/", "x"},
  1015  	{"abc", "."},
  1016  	{"abc/def", "abc"},
  1017  	{"a/b/.x", "a/b"},
  1018  	{"a/b/c.", "a/b"},
  1019  	{"a/b/c.x", "a/b"},
  1020  }
  1021  
  1022  var nonwindirtests = []PathTest{
  1023  	{"////", "/"},
  1024  }
  1025  
  1026  var windirtests = []PathTest{
  1027  	{`c:\`, `c:\`},
  1028  	{`c:.`, `c:.`},
  1029  	{`c:\a\b`, `c:\a`},
  1030  	{`c:a\b`, `c:a`},
  1031  	{`c:a\b\c`, `c:a\b`},
  1032  	{`\\host\share`, `\\host\share`},
  1033  	{`\\host\share\`, `\\host\share\`},
  1034  	{`\\host\share\a`, `\\host\share\`},
  1035  	{`\\host\share\a\b`, `\\host\share\a`},
  1036  	{`\\\\`, `\\\\`},
  1037  }
  1038  
  1039  func TestDir(t *testing.T) {
  1040  	tests := dirtests
  1041  	if runtime.GOOS == "windows" {
  1042  		// make unix tests work on windows
  1043  		for i := range tests {
  1044  			tests[i].result = filepath.Clean(tests[i].result)
  1045  		}
  1046  		// add windows specific tests
  1047  		tests = append(tests, windirtests...)
  1048  	} else {
  1049  		tests = append(tests, nonwindirtests...)
  1050  	}
  1051  	for _, test := range tests {
  1052  		if s := filepath.Dir(test.path); s != test.result {
  1053  			t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
  1054  		}
  1055  	}
  1056  }
  1057  
  1058  type IsAbsTest struct {
  1059  	path  string
  1060  	isAbs bool
  1061  }
  1062  
  1063  var isabstests = []IsAbsTest{
  1064  	{"", false},
  1065  	{"/", true},
  1066  	{"/usr/bin/gcc", true},
  1067  	{"..", false},
  1068  	{"/a/../bb", true},
  1069  	{".", false},
  1070  	{"./", false},
  1071  	{"lala", false},
  1072  }
  1073  
  1074  var winisabstests = []IsAbsTest{
  1075  	{`C:\`, true},
  1076  	{`c\`, false},
  1077  	{`c::`, false},
  1078  	{`c:`, false},
  1079  	{`/`, false},
  1080  	{`\`, false},
  1081  	{`\Windows`, false},
  1082  	{`c:a\b`, false},
  1083  	{`c:\a\b`, true},
  1084  	{`c:/a/b`, true},
  1085  	{`\\host\share`, true},
  1086  	{`\\host\share\`, true},
  1087  	{`\\host\share\foo`, true},
  1088  	{`//host/share/foo/bar`, true},
  1089  	{`\\?\a\b\c`, true},
  1090  	{`\??\a\b\c`, true},
  1091  }
  1092  
  1093  func TestIsAbs(t *testing.T) {
  1094  	var tests []IsAbsTest
  1095  	if runtime.GOOS == "windows" {
  1096  		tests = append(tests, winisabstests...)
  1097  		// All non-windows tests should fail, because they have no volume letter.
  1098  		for _, test := range isabstests {
  1099  			tests = append(tests, IsAbsTest{test.path, false})
  1100  		}
  1101  		// All non-windows test should work as intended if prefixed with volume letter.
  1102  		for _, test := range isabstests {
  1103  			tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
  1104  		}
  1105  	} else {
  1106  		tests = isabstests
  1107  	}
  1108  
  1109  	for _, test := range tests {
  1110  		if r := filepath.IsAbs(test.path); r != test.isAbs {
  1111  			t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
  1112  		}
  1113  	}
  1114  }
  1115  
  1116  type EvalSymlinksTest struct {
  1117  	// If dest is empty, the path is created; otherwise the dest is symlinked to the path.
  1118  	path, dest string
  1119  }
  1120  
  1121  var EvalSymlinksTestDirs = []EvalSymlinksTest{
  1122  	{"test", ""},
  1123  	{"test/dir", ""},
  1124  	{"test/dir/link3", "../../"},
  1125  	{"test/link1", "../test"},
  1126  	{"test/link2", "dir"},
  1127  	{"test/linkabs", "/"},
  1128  	{"test/link4", "../test2"},
  1129  	{"test2", "test/dir"},
  1130  	// Issue 23444.
  1131  	{"src", ""},
  1132  	{"src/pool", ""},
  1133  	{"src/pool/test", ""},
  1134  	{"src/versions", ""},
  1135  	{"src/versions/current", "../../version"},
  1136  	{"src/versions/v1", ""},
  1137  	{"src/versions/v1/modules", ""},
  1138  	{"src/versions/v1/modules/test", "../../../pool/test"},
  1139  	{"version", "src/versions/v1"},
  1140  }
  1141  
  1142  var EvalSymlinksTests = []EvalSymlinksTest{
  1143  	{"test", "test"},
  1144  	{"test/dir", "test/dir"},
  1145  	{"test/dir/../..", "."},
  1146  	{"test/link1", "test"},
  1147  	{"test/link2", "test/dir"},
  1148  	{"test/link1/dir", "test/dir"},
  1149  	{"test/link2/..", "test"},
  1150  	{"test/dir/link3", "."},
  1151  	{"test/link2/link3/test", "test"},
  1152  	{"test/linkabs", "/"},
  1153  	{"test/link4/..", "test"},
  1154  	{"src/versions/current/modules/test", "src/pool/test"},
  1155  }
  1156  
  1157  // simpleJoin builds a file name from the directory and path.
  1158  // It does not use Join because we don't want ".." to be evaluated.
  1159  func simpleJoin(dir, path string) string {
  1160  	return dir + string(filepath.Separator) + path
  1161  }
  1162  
  1163  func testEvalSymlinks(t *testing.T, path, want string) {
  1164  	have, err := filepath.EvalSymlinks(path)
  1165  	if err != nil {
  1166  		t.Errorf("EvalSymlinks(%q) error: %v", path, err)
  1167  		return
  1168  	}
  1169  	if filepath.Clean(have) != filepath.Clean(want) {
  1170  		t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
  1171  	}
  1172  }
  1173  
  1174  func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
  1175  	t.Chdir(wd)
  1176  	have, err := filepath.EvalSymlinks(path)
  1177  	if err != nil {
  1178  		t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
  1179  		return
  1180  	}
  1181  	if filepath.Clean(have) != filepath.Clean(want) {
  1182  		t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
  1183  	}
  1184  }
  1185  
  1186  func TestEvalSymlinks(t *testing.T) {
  1187  	testenv.MustHaveSymlink(t)
  1188  
  1189  	tmpDir := t.TempDir()
  1190  
  1191  	// /tmp may itself be a symlink! Avoid the confusion, although
  1192  	// it means trusting the thing we're testing.
  1193  	var err error
  1194  	tmpDir, err = filepath.EvalSymlinks(tmpDir)
  1195  	if err != nil {
  1196  		t.Fatal("eval symlink for tmp dir:", err)
  1197  	}
  1198  
  1199  	// Create the symlink farm using relative paths.
  1200  	for _, d := range EvalSymlinksTestDirs {
  1201  		var err error
  1202  		path := simpleJoin(tmpDir, d.path)
  1203  		if d.dest == "" {
  1204  			err = os.Mkdir(path, 0755)
  1205  		} else {
  1206  			err = os.Symlink(d.dest, path)
  1207  		}
  1208  		if err != nil {
  1209  			t.Fatal(err)
  1210  		}
  1211  	}
  1212  
  1213  	// Evaluate the symlink farm.
  1214  	for _, test := range EvalSymlinksTests {
  1215  		path := simpleJoin(tmpDir, test.path)
  1216  
  1217  		dest := simpleJoin(tmpDir, test.dest)
  1218  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1219  			dest = test.dest
  1220  		}
  1221  		testEvalSymlinks(t, path, dest)
  1222  
  1223  		// test EvalSymlinks(".")
  1224  		testEvalSymlinksAfterChdir(t, path, ".", ".")
  1225  
  1226  		// test EvalSymlinks("C:.") on Windows
  1227  		if runtime.GOOS == "windows" {
  1228  			volDot := filepath.VolumeName(tmpDir) + "."
  1229  			testEvalSymlinksAfterChdir(t, path, volDot, volDot)
  1230  		}
  1231  
  1232  		// test EvalSymlinks(".."+path)
  1233  		dotdotPath := simpleJoin("..", test.dest)
  1234  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1235  			dotdotPath = test.dest
  1236  		}
  1237  		testEvalSymlinksAfterChdir(t,
  1238  			simpleJoin(tmpDir, "test"),
  1239  			simpleJoin("..", test.path),
  1240  			dotdotPath)
  1241  
  1242  		// test EvalSymlinks(p) where p is relative path
  1243  		testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
  1244  	}
  1245  }
  1246  
  1247  func TestEvalSymlinksIsNotExist(t *testing.T) {
  1248  	testenv.MustHaveSymlink(t)
  1249  	t.Chdir(t.TempDir())
  1250  
  1251  	_, err := filepath.EvalSymlinks("notexist")
  1252  	if !os.IsNotExist(err) {
  1253  		t.Errorf("expected the file is not found, got %v\n", err)
  1254  	}
  1255  
  1256  	err = os.Symlink("notexist", "link")
  1257  	if err != nil {
  1258  		t.Fatal(err)
  1259  	}
  1260  	defer os.Remove("link")
  1261  
  1262  	_, err = filepath.EvalSymlinks("link")
  1263  	if !os.IsNotExist(err) {
  1264  		t.Errorf("expected the file is not found, got %v\n", err)
  1265  	}
  1266  }
  1267  
  1268  func TestIssue13582(t *testing.T) {
  1269  	testenv.MustHaveSymlink(t)
  1270  
  1271  	tmpDir := t.TempDir()
  1272  
  1273  	dir := filepath.Join(tmpDir, "dir")
  1274  	err := os.Mkdir(dir, 0755)
  1275  	if err != nil {
  1276  		t.Fatal(err)
  1277  	}
  1278  	linkToDir := filepath.Join(tmpDir, "link_to_dir")
  1279  	err = os.Symlink(dir, linkToDir)
  1280  	if err != nil {
  1281  		t.Fatal(err)
  1282  	}
  1283  	file := filepath.Join(linkToDir, "file")
  1284  	err = os.WriteFile(file, nil, 0644)
  1285  	if err != nil {
  1286  		t.Fatal(err)
  1287  	}
  1288  	link1 := filepath.Join(linkToDir, "link1")
  1289  	err = os.Symlink(file, link1)
  1290  	if err != nil {
  1291  		t.Fatal(err)
  1292  	}
  1293  	link2 := filepath.Join(linkToDir, "link2")
  1294  	err = os.Symlink(link1, link2)
  1295  	if err != nil {
  1296  		t.Fatal(err)
  1297  	}
  1298  
  1299  	// /tmp may itself be a symlink!
  1300  	realTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1301  	if err != nil {
  1302  		t.Fatal(err)
  1303  	}
  1304  	realDir := filepath.Join(realTmpDir, "dir")
  1305  	realFile := filepath.Join(realDir, "file")
  1306  
  1307  	tests := []struct {
  1308  		path, want string
  1309  	}{
  1310  		{dir, realDir},
  1311  		{linkToDir, realDir},
  1312  		{file, realFile},
  1313  		{link1, realFile},
  1314  		{link2, realFile},
  1315  	}
  1316  	for i, test := range tests {
  1317  		have, err := filepath.EvalSymlinks(test.path)
  1318  		if err != nil {
  1319  			t.Fatal(err)
  1320  		}
  1321  		if have != test.want {
  1322  			t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
  1323  		}
  1324  	}
  1325  }
  1326  
  1327  // Issue 57905.
  1328  func TestRelativeSymlinkToAbsolute(t *testing.T) {
  1329  	testenv.MustHaveSymlink(t)
  1330  	// Not parallel: uses t.Chdir.
  1331  
  1332  	tmpDir := t.TempDir()
  1333  	t.Chdir(tmpDir)
  1334  
  1335  	// Create "link" in the current working directory as a symlink to an arbitrary
  1336  	// absolute path. On macOS, this path is likely to begin with a symlink
  1337  	// itself: generally either in /var (symlinked to "private/var") or /tmp
  1338  	// (symlinked to "private/tmp").
  1339  	if err := os.Symlink(tmpDir, "link"); err != nil {
  1340  		t.Fatal(err)
  1341  	}
  1342  	t.Logf(`os.Symlink(%q, "link")`, tmpDir)
  1343  
  1344  	p, err := filepath.EvalSymlinks("link")
  1345  	if err != nil {
  1346  		t.Fatalf(`EvalSymlinks("link"): %v`, err)
  1347  	}
  1348  	want, err := filepath.EvalSymlinks(tmpDir)
  1349  	if err != nil {
  1350  		t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
  1351  	}
  1352  	if p != want {
  1353  		t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
  1354  	}
  1355  	t.Logf(`EvalSymlinks("link") = %q`, p)
  1356  }
  1357  
  1358  // Test directories relative to temporary directory.
  1359  // The tests are run in absTestDirs[0].
  1360  var absTestDirs = []string{
  1361  	"a",
  1362  	"a/b",
  1363  	"a/b/c",
  1364  }
  1365  
  1366  // Test paths relative to temporary directory. $ expands to the directory.
  1367  // The tests are run in absTestDirs[0].
  1368  // We create absTestDirs first.
  1369  var absTests = []string{
  1370  	".",
  1371  	"b",
  1372  	"b/",
  1373  	"../a",
  1374  	"../a/b",
  1375  	"../a/b/./c/../../.././a",
  1376  	"../a/b/./c/../../.././a/",
  1377  	"$",
  1378  	"$/.",
  1379  	"$/a/../a/b",
  1380  	"$/a/b/c/../../.././a",
  1381  	"$/a/b/c/../../.././a/",
  1382  }
  1383  
  1384  func TestAbs(t *testing.T) {
  1385  	root := t.TempDir()
  1386  	t.Chdir(root)
  1387  
  1388  	for _, dir := range absTestDirs {
  1389  		err := os.Mkdir(dir, 0777)
  1390  		if err != nil {
  1391  			t.Fatal("Mkdir failed: ", err)
  1392  		}
  1393  	}
  1394  
  1395  	// Make sure the global absTests slice is not
  1396  	// modified by multiple invocations of TestAbs.
  1397  	tests := absTests
  1398  	if runtime.GOOS == "windows" {
  1399  		vol := filepath.VolumeName(root)
  1400  		var extra []string
  1401  		for _, path := range absTests {
  1402  			if strings.Contains(path, "$") {
  1403  				continue
  1404  			}
  1405  			path = vol + path
  1406  			extra = append(extra, path)
  1407  		}
  1408  		tests = append(slices.Clip(tests), extra...)
  1409  	}
  1410  
  1411  	err := os.Chdir(absTestDirs[0])
  1412  	if err != nil {
  1413  		t.Fatal("chdir failed: ", err)
  1414  	}
  1415  
  1416  	for _, path := range tests {
  1417  		path = strings.ReplaceAll(path, "$", root)
  1418  		info, err := os.Stat(path)
  1419  		if err != nil {
  1420  			t.Errorf("%s: %s", path, err)
  1421  			continue
  1422  		}
  1423  
  1424  		abspath, err := filepath.Abs(path)
  1425  		if err != nil {
  1426  			t.Errorf("Abs(%q) error: %v", path, err)
  1427  			continue
  1428  		}
  1429  		absinfo, err := os.Stat(abspath)
  1430  		if err != nil || !os.SameFile(absinfo, info) {
  1431  			t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
  1432  		}
  1433  		if !filepath.IsAbs(abspath) {
  1434  			t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
  1435  		}
  1436  		if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1437  			t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
  1438  		}
  1439  	}
  1440  }
  1441  
  1442  // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
  1443  // We test it separately from all other absTests because the empty string is not
  1444  // a valid path, so it can't be used with os.Stat.
  1445  func TestAbsEmptyString(t *testing.T) {
  1446  	root := t.TempDir()
  1447  	t.Chdir(root)
  1448  
  1449  	info, err := os.Stat(root)
  1450  	if err != nil {
  1451  		t.Fatalf("%s: %s", root, err)
  1452  	}
  1453  
  1454  	abspath, err := filepath.Abs("")
  1455  	if err != nil {
  1456  		t.Fatalf(`Abs("") error: %v`, err)
  1457  	}
  1458  	absinfo, err := os.Stat(abspath)
  1459  	if err != nil || !os.SameFile(absinfo, info) {
  1460  		t.Errorf(`Abs("")=%q, not the same file`, abspath)
  1461  	}
  1462  	if !filepath.IsAbs(abspath) {
  1463  		t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
  1464  	}
  1465  	if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1466  		t.Errorf(`Abs("")=%q, isn't clean`, abspath)
  1467  	}
  1468  }
  1469  
  1470  type RelTests struct {
  1471  	root, path, want string
  1472  }
  1473  
  1474  var reltests = []RelTests{
  1475  	{"a/b", "a/b", "."},
  1476  	{"a/b/.", "a/b", "."},
  1477  	{"a/b", "a/b/.", "."},
  1478  	{"./a/b", "a/b", "."},
  1479  	{"a/b", "./a/b", "."},
  1480  	{"ab/cd", "ab/cde", "../cde"},
  1481  	{"ab/cd", "ab/c", "../c"},
  1482  	{"a/b", "a/b/c/d", "c/d"},
  1483  	{"a/b", "a/b/../c", "../c"},
  1484  	{"a/b/../c", "a/b", "../b"},
  1485  	{"a/b/c", "a/c/d", "../../c/d"},
  1486  	{"a/b", "c/d", "../../c/d"},
  1487  	{"a/b/c/d", "a/b", "../.."},
  1488  	{"a/b/c/d", "a/b/", "../.."},
  1489  	{"a/b/c/d/", "a/b", "../.."},
  1490  	{"a/b/c/d/", "a/b/", "../.."},
  1491  	{"../../a/b", "../../a/b/c/d", "c/d"},
  1492  	{"/a/b", "/a/b", "."},
  1493  	{"/a/b/.", "/a/b", "."},
  1494  	{"/a/b", "/a/b/.", "."},
  1495  	{"/ab/cd", "/ab/cde", "../cde"},
  1496  	{"/ab/cd", "/ab/c", "../c"},
  1497  	{"/a/b", "/a/b/c/d", "c/d"},
  1498  	{"/a/b", "/a/b/../c", "../c"},
  1499  	{"/a/b/../c", "/a/b", "../b"},
  1500  	{"/a/b/c", "/a/c/d", "../../c/d"},
  1501  	{"/a/b", "/c/d", "../../c/d"},
  1502  	{"/a/b/c/d", "/a/b", "../.."},
  1503  	{"/a/b/c/d", "/a/b/", "../.."},
  1504  	{"/a/b/c/d/", "/a/b", "../.."},
  1505  	{"/a/b/c/d/", "/a/b/", "../.."},
  1506  	{"/../../a/b", "/../../a/b/c/d", "c/d"},
  1507  	{".", "a/b", "a/b"},
  1508  	{".", "..", ".."},
  1509  
  1510  	// can't do purely lexically
  1511  	{"..", ".", "err"},
  1512  	{"..", "a", "err"},
  1513  	{"../..", "..", "err"},
  1514  	{"a", "/a", "err"},
  1515  	{"/a", "a", "err"},
  1516  }
  1517  
  1518  var winreltests = []RelTests{
  1519  	{`C:a\b\c`, `C:a/b/d`, `..\d`},
  1520  	{`C:\`, `D:\`, `err`},
  1521  	{`C:`, `D:`, `err`},
  1522  	{`C:\Projects`, `c:\projects\src`, `src`},
  1523  	{`C:\Projects`, `c:\projects`, `.`},
  1524  	{`C:\Projects\a\..`, `c:\projects`, `.`},
  1525  	{`\\host\share`, `\\host\share\file.txt`, `file.txt`},
  1526  }
  1527  
  1528  func TestRel(t *testing.T) {
  1529  	tests := append([]RelTests{}, reltests...)
  1530  	if runtime.GOOS == "windows" {
  1531  		for i := range tests {
  1532  			tests[i].want = filepath.FromSlash(tests[i].want)
  1533  		}
  1534  		tests = append(tests, winreltests...)
  1535  	}
  1536  	for _, test := range tests {
  1537  		got, err := filepath.Rel(test.root, test.path)
  1538  		if test.want == "err" {
  1539  			if err == nil {
  1540  				t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
  1541  			}
  1542  			continue
  1543  		}
  1544  		if err != nil {
  1545  			t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
  1546  		}
  1547  		if got != test.want {
  1548  			t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
  1549  		}
  1550  	}
  1551  }
  1552  
  1553  type VolumeNameTest struct {
  1554  	path string
  1555  	vol  string
  1556  }
  1557  
  1558  var volumenametests = []VolumeNameTest{
  1559  	{`c:/foo/bar`, `c:`},
  1560  	{`c:`, `c:`},
  1561  	{`c:\`, `c:`},
  1562  	{`2:`, `2:`},
  1563  	{``, ``},
  1564  	{`\\\host`, `\\\host`},
  1565  	{`\\\host\`, `\\\host`},
  1566  	{`\\\host\share`, `\\\host`},
  1567  	{`\\\host\\share`, `\\\host`},
  1568  	{`\\host`, `\\host`},
  1569  	{`//host`, `\\host`},
  1570  	{`\\host\`, `\\host\`},
  1571  	{`//host/`, `\\host\`},
  1572  	{`\\host\share`, `\\host\share`},
  1573  	{`//host/share`, `\\host\share`},
  1574  	{`\\host\share\`, `\\host\share`},
  1575  	{`//host/share/`, `\\host\share`},
  1576  	{`\\host\share\foo`, `\\host\share`},
  1577  	{`//host/share/foo`, `\\host\share`},
  1578  	{`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
  1579  	{`//host/share//foo///bar////baz`, `\\host\share`},
  1580  	{`\\host\share\foo\..\bar`, `\\host\share`},
  1581  	{`//host/share/foo/../bar`, `\\host\share`},
  1582  	{`//.`, `\\.`},
  1583  	{`//./`, `\\.\`},
  1584  	{`//./NUL`, `\\.\NUL`},
  1585  	{`//?`, `\\?`},
  1586  	{`//?/`, `\\?\`},
  1587  	{`//?/NUL`, `\\?\NUL`},
  1588  	{`/??`, `\??`},
  1589  	{`/??/`, `\??\`},
  1590  	{`/??/NUL`, `\??\NUL`},
  1591  	{`//./a/b`, `\\.\a`},
  1592  	{`//./C:`, `\\.\C:`},
  1593  	{`//./C:/`, `\\.\C:`},
  1594  	{`//./C:/a/b/c`, `\\.\C:`},
  1595  	{`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
  1596  	{`//./UNC/host`, `\\.\UNC\host`},
  1597  	{`//./UNC/host\`, `\\.\UNC\host\`},
  1598  	{`//./UNC`, `\\.\UNC`},
  1599  	{`//./UNC/`, `\\.\UNC\`},
  1600  	{`\\?\x`, `\\?\x`},
  1601  	{`\??\x`, `\??\x`},
  1602  }
  1603  
  1604  func TestVolumeName(t *testing.T) {
  1605  	if runtime.GOOS != "windows" {
  1606  		return
  1607  	}
  1608  	for _, v := range volumenametests {
  1609  		if vol := filepath.VolumeName(v.path); vol != v.vol {
  1610  			t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
  1611  		}
  1612  	}
  1613  }
  1614  
  1615  func TestDriveLetterInEvalSymlinks(t *testing.T) {
  1616  	if runtime.GOOS != "windows" {
  1617  		return
  1618  	}
  1619  	wd, _ := os.Getwd()
  1620  	if len(wd) < 3 {
  1621  		t.Errorf("Current directory path %q is too short", wd)
  1622  	}
  1623  	lp := strings.ToLower(wd)
  1624  	up := strings.ToUpper(wd)
  1625  	flp, err := filepath.EvalSymlinks(lp)
  1626  	if err != nil {
  1627  		t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
  1628  	}
  1629  	fup, err := filepath.EvalSymlinks(up)
  1630  	if err != nil {
  1631  		t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
  1632  	}
  1633  	if flp != fup {
  1634  		t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
  1635  	}
  1636  }
  1637  
  1638  func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
  1639  	if runtime.GOOS == "ios" {
  1640  		t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
  1641  	}
  1642  	root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
  1643  	utf16 := filepath.Join(root, "utf16")
  1644  	utf8 := filepath.Join(root, "utf8")
  1645  	seenUTF16 := false
  1646  	seenUTF8 := false
  1647  	err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
  1648  		if err != nil {
  1649  			t.Fatal(err)
  1650  		}
  1651  
  1652  		switch pth {
  1653  		case utf16:
  1654  			seenUTF16 = true
  1655  			return filepath.SkipDir
  1656  		case utf8:
  1657  			if !seenUTF16 {
  1658  				t.Fatal("filepath.Walk out of order - utf8 before utf16")
  1659  			}
  1660  			seenUTF8 = true
  1661  		}
  1662  		return nil
  1663  	})
  1664  	if err != nil {
  1665  		t.Fatal(err)
  1666  	}
  1667  	if !seenUTF8 {
  1668  		t.Fatalf("%q not seen", utf8)
  1669  	}
  1670  }
  1671  
  1672  func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
  1673  	tmpdir := t.TempDir()
  1674  	t.Chdir(tmpdir)
  1675  
  1676  	err := mklink(tmpdir, "link")
  1677  	if err != nil {
  1678  		t.Fatal(err)
  1679  	}
  1680  
  1681  	var visited []string
  1682  	err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
  1683  		if err != nil {
  1684  			t.Fatal(err)
  1685  		}
  1686  		rel, err := filepath.Rel(tmpdir, path)
  1687  		if err != nil {
  1688  			t.Fatal(err)
  1689  		}
  1690  		visited = append(visited, rel)
  1691  		return nil
  1692  	})
  1693  	if err != nil {
  1694  		t.Fatal(err)
  1695  	}
  1696  	slices.Sort(visited)
  1697  	want := []string{".", "link"}
  1698  	if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
  1699  		t.Errorf("unexpected paths visited %q, want %q", visited, want)
  1700  	}
  1701  }
  1702  
  1703  func TestWalkSymlink(t *testing.T) {
  1704  	testenv.MustHaveSymlink(t)
  1705  	testWalkSymlink(t, os.Symlink)
  1706  }
  1707  
  1708  func TestIssue29372(t *testing.T) {
  1709  	tmpDir := t.TempDir()
  1710  
  1711  	path := filepath.Join(tmpDir, "file.txt")
  1712  	err := os.WriteFile(path, nil, 0644)
  1713  	if err != nil {
  1714  		t.Fatal(err)
  1715  	}
  1716  
  1717  	pathSeparator := string(filepath.Separator)
  1718  	tests := []string{
  1719  		path + strings.Repeat(pathSeparator, 1),
  1720  		path + strings.Repeat(pathSeparator, 2),
  1721  		path + strings.Repeat(pathSeparator, 1) + ".",
  1722  		path + strings.Repeat(pathSeparator, 2) + ".",
  1723  		path + strings.Repeat(pathSeparator, 1) + "..",
  1724  		path + strings.Repeat(pathSeparator, 2) + "..",
  1725  	}
  1726  
  1727  	for i, test := range tests {
  1728  		_, err = filepath.EvalSymlinks(test)
  1729  		if err != syscall.ENOTDIR {
  1730  			t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
  1731  		}
  1732  	}
  1733  }
  1734  
  1735  // Issue 30520 part 1.
  1736  func TestEvalSymlinksAboveRoot(t *testing.T) {
  1737  	testenv.MustHaveSymlink(t)
  1738  
  1739  	t.Parallel()
  1740  
  1741  	tmpDir := t.TempDir()
  1742  
  1743  	evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1744  	if err != nil {
  1745  		t.Fatal(err)
  1746  	}
  1747  
  1748  	if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
  1749  		t.Fatal(err)
  1750  	}
  1751  	if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
  1752  		t.Fatal(err)
  1753  	}
  1754  	if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
  1755  		t.Fatal(err)
  1756  	}
  1757  
  1758  	// Count the number of ".." elements to get to the root directory.
  1759  	vol := filepath.VolumeName(evalTmpDir)
  1760  	c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
  1761  	var dd []string
  1762  	for i := 0; i < c+2; i++ {
  1763  		dd = append(dd, "..")
  1764  	}
  1765  
  1766  	wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
  1767  
  1768  	// Try different numbers of "..".
  1769  	for _, i := range []int{c, c + 1, c + 2} {
  1770  		check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
  1771  		resolved, err := filepath.EvalSymlinks(check)
  1772  		switch {
  1773  		case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
  1774  			// On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
  1775  			testenv.SkipFlaky(t, 37910)
  1776  		case err != nil:
  1777  			t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1778  		case !strings.HasSuffix(resolved, wantSuffix):
  1779  			t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1780  		default:
  1781  			t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1782  		}
  1783  	}
  1784  }
  1785  
  1786  // Issue 30520 part 2.
  1787  func TestEvalSymlinksAboveRootChdir(t *testing.T) {
  1788  	testenv.MustHaveSymlink(t)
  1789  	t.Chdir(t.TempDir())
  1790  
  1791  	subdir := filepath.Join("a", "b")
  1792  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1793  		t.Fatal(err)
  1794  	}
  1795  	if err := os.Symlink(subdir, "c"); err != nil {
  1796  		t.Fatal(err)
  1797  	}
  1798  	if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
  1799  		t.Fatal(err)
  1800  	}
  1801  
  1802  	subdir = filepath.Join("d", "e", "f")
  1803  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1804  		t.Fatal(err)
  1805  	}
  1806  	if err := os.Chdir(subdir); err != nil {
  1807  		t.Fatal(err)
  1808  	}
  1809  
  1810  	check := filepath.Join("..", "..", "..", "c", "file")
  1811  	wantSuffix := filepath.Join("a", "b", "file")
  1812  	if resolved, err := filepath.EvalSymlinks(check); err != nil {
  1813  		t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1814  	} else if !strings.HasSuffix(resolved, wantSuffix) {
  1815  		t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1816  	} else {
  1817  		t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1818  	}
  1819  }
  1820  
  1821  func TestIssue51617(t *testing.T) {
  1822  	dir := t.TempDir()
  1823  	for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
  1824  		if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
  1825  			t.Fatal(err)
  1826  		}
  1827  	}
  1828  	bad := filepath.Join(dir, "a", "bad")
  1829  	if err := os.Chmod(bad, 0); err != nil {
  1830  		t.Fatal(err)
  1831  	}
  1832  	defer os.Chmod(bad, 0700) // avoid errors on cleanup
  1833  	var saw []string
  1834  	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
  1835  		if err != nil {
  1836  			return filepath.SkipDir
  1837  		}
  1838  		if d.IsDir() {
  1839  			rel, err := filepath.Rel(dir, path)
  1840  			if err != nil {
  1841  				t.Fatal(err)
  1842  			}
  1843  			saw = append(saw, rel)
  1844  		}
  1845  		return nil
  1846  	})
  1847  	if err != nil {
  1848  		t.Fatal(err)
  1849  	}
  1850  	want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
  1851  	if !slices.Equal(saw, want) {
  1852  		t.Errorf("got directories %v, want %v", saw, want)
  1853  	}
  1854  }
  1855  
  1856  func TestEscaping(t *testing.T) {
  1857  	dir := t.TempDir()
  1858  	t.Chdir(t.TempDir())
  1859  
  1860  	for _, p := range []string{
  1861  		filepath.Join(dir, "x"),
  1862  	} {
  1863  		if !filepath.IsLocal(p) {
  1864  			continue
  1865  		}
  1866  		f, err := os.Create(p)
  1867  		if err != nil {
  1868  			f.Close()
  1869  		}
  1870  		ents, err := os.ReadDir(dir)
  1871  		if err != nil {
  1872  			t.Fatal(err)
  1873  		}
  1874  		for _, e := range ents {
  1875  			t.Fatalf("found: %v", e.Name())
  1876  		}
  1877  	}
  1878  }
  1879  
  1880  func TestEvalSymlinksTooManyLinks(t *testing.T) {
  1881  	testenv.MustHaveSymlink(t)
  1882  	dir := filepath.Join(t.TempDir(), "dir")
  1883  	err := os.Symlink(dir, dir)
  1884  	if err != nil {
  1885  		t.Fatal(err)
  1886  	}
  1887  	_, err = filepath.EvalSymlinks(dir)
  1888  	if err == nil {
  1889  		t.Fatal("expected error, got nil")
  1890  	}
  1891  }
  1892  

View as plain text