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  
  1511  	// can't do purely lexically
  1512  	{"..", ".", "err"},
  1513  	{"..", "a", "err"},
  1514  	{"../..", "..", "err"},
  1515  	{"a", "/a", "err"},
  1516  	{"/a", "a", "err"},
  1517  }
  1518  
  1519  var winreltests = []RelTests{
  1520  	{`C:a\b\c`, `C:a/b/d`, `..\d`},
  1521  	{`C:\`, `D:\`, `err`},
  1522  	{`C:`, `D:`, `err`},
  1523  	{`C:\Projects`, `c:\projects\src`, `src`},
  1524  	{`C:\Projects`, `c:\projects`, `.`},
  1525  	{`C:\Projects\a\..`, `c:\projects`, `.`},
  1526  	{`\\host\share`, `\\host\share\file.txt`, `file.txt`},
  1527  }
  1528  
  1529  func TestRel(t *testing.T) {
  1530  	tests := append([]RelTests{}, reltests...)
  1531  	if runtime.GOOS == "windows" {
  1532  		for i := range tests {
  1533  			tests[i].want = filepath.FromSlash(tests[i].want)
  1534  		}
  1535  		tests = append(tests, winreltests...)
  1536  	}
  1537  	for _, test := range tests {
  1538  		got, err := filepath.Rel(test.root, test.path)
  1539  		if test.want == "err" {
  1540  			if err == nil {
  1541  				t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
  1542  			}
  1543  			continue
  1544  		}
  1545  		if err != nil {
  1546  			t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
  1547  		}
  1548  		if got != test.want {
  1549  			t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
  1550  		}
  1551  	}
  1552  }
  1553  
  1554  type VolumeNameTest struct {
  1555  	path string
  1556  	vol  string
  1557  }
  1558  
  1559  var volumenametests = []VolumeNameTest{
  1560  	{`c:/foo/bar`, `c:`},
  1561  	{`c:`, `c:`},
  1562  	{`c:\`, `c:`},
  1563  	{`2:`, `2:`},
  1564  	{``, ``},
  1565  	{`\\\host`, `\\\host`},
  1566  	{`\\\host\`, `\\\host`},
  1567  	{`\\\host\share`, `\\\host`},
  1568  	{`\\\host\\share`, `\\\host`},
  1569  	{`\\host`, `\\host`},
  1570  	{`//host`, `\\host`},
  1571  	{`\\host\`, `\\host\`},
  1572  	{`//host/`, `\\host\`},
  1573  	{`\\host\share`, `\\host\share`},
  1574  	{`//host/share`, `\\host\share`},
  1575  	{`\\host\share\`, `\\host\share`},
  1576  	{`//host/share/`, `\\host\share`},
  1577  	{`\\host\share\foo`, `\\host\share`},
  1578  	{`//host/share/foo`, `\\host\share`},
  1579  	{`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
  1580  	{`//host/share//foo///bar////baz`, `\\host\share`},
  1581  	{`\\host\share\foo\..\bar`, `\\host\share`},
  1582  	{`//host/share/foo/../bar`, `\\host\share`},
  1583  	{`//.`, `\\.`},
  1584  	{`//./`, `\\.\`},
  1585  	{`//./NUL`, `\\.\NUL`},
  1586  	{`//?`, `\\?`},
  1587  	{`//?/`, `\\?\`},
  1588  	{`//?/NUL`, `\\?\NUL`},
  1589  	{`/??`, `\??`},
  1590  	{`/??/`, `\??\`},
  1591  	{`/??/NUL`, `\??\NUL`},
  1592  	{`//./a/b`, `\\.\a`},
  1593  	{`//./C:`, `\\.\C:`},
  1594  	{`//./C:/`, `\\.\C:`},
  1595  	{`//./C:/a/b/c`, `\\.\C:`},
  1596  	{`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
  1597  	{`//./UNC/host`, `\\.\UNC\host`},
  1598  	{`//./UNC/host\`, `\\.\UNC\host\`},
  1599  	{`//./UNC`, `\\.\UNC`},
  1600  	{`//./UNC/`, `\\.\UNC\`},
  1601  	{`\\?\x`, `\\?\x`},
  1602  	{`\??\x`, `\??\x`},
  1603  }
  1604  
  1605  func TestVolumeName(t *testing.T) {
  1606  	if runtime.GOOS != "windows" {
  1607  		return
  1608  	}
  1609  	for _, v := range volumenametests {
  1610  		if vol := filepath.VolumeName(v.path); vol != v.vol {
  1611  			t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
  1612  		}
  1613  	}
  1614  }
  1615  
  1616  func TestDriveLetterInEvalSymlinks(t *testing.T) {
  1617  	if runtime.GOOS != "windows" {
  1618  		return
  1619  	}
  1620  	wd, _ := os.Getwd()
  1621  	if len(wd) < 3 {
  1622  		t.Errorf("Current directory path %q is too short", wd)
  1623  	}
  1624  	lp := strings.ToLower(wd)
  1625  	up := strings.ToUpper(wd)
  1626  	flp, err := filepath.EvalSymlinks(lp)
  1627  	if err != nil {
  1628  		t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
  1629  	}
  1630  	fup, err := filepath.EvalSymlinks(up)
  1631  	if err != nil {
  1632  		t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
  1633  	}
  1634  	if flp != fup {
  1635  		t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
  1636  	}
  1637  }
  1638  
  1639  func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
  1640  	if runtime.GOOS == "ios" {
  1641  		t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
  1642  	}
  1643  	root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
  1644  	utf16 := filepath.Join(root, "utf16")
  1645  	utf8 := filepath.Join(root, "utf8")
  1646  	seenUTF16 := false
  1647  	seenUTF8 := false
  1648  	err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
  1649  		if err != nil {
  1650  			t.Fatal(err)
  1651  		}
  1652  
  1653  		switch pth {
  1654  		case utf16:
  1655  			seenUTF16 = true
  1656  			return filepath.SkipDir
  1657  		case utf8:
  1658  			if !seenUTF16 {
  1659  				t.Fatal("filepath.Walk out of order - utf8 before utf16")
  1660  			}
  1661  			seenUTF8 = true
  1662  		}
  1663  		return nil
  1664  	})
  1665  	if err != nil {
  1666  		t.Fatal(err)
  1667  	}
  1668  	if !seenUTF8 {
  1669  		t.Fatalf("%q not seen", utf8)
  1670  	}
  1671  }
  1672  
  1673  func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
  1674  	tmpdir := t.TempDir()
  1675  	t.Chdir(tmpdir)
  1676  
  1677  	err := mklink(tmpdir, "link")
  1678  	if err != nil {
  1679  		t.Fatal(err)
  1680  	}
  1681  
  1682  	var visited []string
  1683  	err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
  1684  		if err != nil {
  1685  			t.Fatal(err)
  1686  		}
  1687  		rel, err := filepath.Rel(tmpdir, path)
  1688  		if err != nil {
  1689  			t.Fatal(err)
  1690  		}
  1691  		visited = append(visited, rel)
  1692  		return nil
  1693  	})
  1694  	if err != nil {
  1695  		t.Fatal(err)
  1696  	}
  1697  	slices.Sort(visited)
  1698  	want := []string{".", "link"}
  1699  	if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
  1700  		t.Errorf("unexpected paths visited %q, want %q", visited, want)
  1701  	}
  1702  }
  1703  
  1704  func TestWalkSymlink(t *testing.T) {
  1705  	testenv.MustHaveSymlink(t)
  1706  	testWalkSymlink(t, os.Symlink)
  1707  }
  1708  
  1709  func TestIssue29372(t *testing.T) {
  1710  	tmpDir := t.TempDir()
  1711  
  1712  	path := filepath.Join(tmpDir, "file.txt")
  1713  	err := os.WriteFile(path, nil, 0644)
  1714  	if err != nil {
  1715  		t.Fatal(err)
  1716  	}
  1717  
  1718  	pathSeparator := string(filepath.Separator)
  1719  	tests := []string{
  1720  		path + strings.Repeat(pathSeparator, 1),
  1721  		path + strings.Repeat(pathSeparator, 2),
  1722  		path + strings.Repeat(pathSeparator, 1) + ".",
  1723  		path + strings.Repeat(pathSeparator, 2) + ".",
  1724  		path + strings.Repeat(pathSeparator, 1) + "..",
  1725  		path + strings.Repeat(pathSeparator, 2) + "..",
  1726  	}
  1727  
  1728  	for i, test := range tests {
  1729  		_, err = filepath.EvalSymlinks(test)
  1730  		if err != syscall.ENOTDIR {
  1731  			t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
  1732  		}
  1733  	}
  1734  }
  1735  
  1736  // Issue 30520 part 1.
  1737  func TestEvalSymlinksAboveRoot(t *testing.T) {
  1738  	testenv.MustHaveSymlink(t)
  1739  
  1740  	t.Parallel()
  1741  
  1742  	tmpDir := t.TempDir()
  1743  
  1744  	evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1745  	if err != nil {
  1746  		t.Fatal(err)
  1747  	}
  1748  
  1749  	if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
  1750  		t.Fatal(err)
  1751  	}
  1752  	if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
  1753  		t.Fatal(err)
  1754  	}
  1755  	if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
  1756  		t.Fatal(err)
  1757  	}
  1758  
  1759  	// Count the number of ".." elements to get to the root directory.
  1760  	vol := filepath.VolumeName(evalTmpDir)
  1761  	c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
  1762  	var dd []string
  1763  	for i := 0; i < c+2; i++ {
  1764  		dd = append(dd, "..")
  1765  	}
  1766  
  1767  	wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
  1768  
  1769  	// Try different numbers of "..".
  1770  	for _, i := range []int{c, c + 1, c + 2} {
  1771  		check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
  1772  		resolved, err := filepath.EvalSymlinks(check)
  1773  		switch {
  1774  		case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
  1775  			// On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
  1776  			testenv.SkipFlaky(t, 37910)
  1777  		case err != nil:
  1778  			t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1779  		case !strings.HasSuffix(resolved, wantSuffix):
  1780  			t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1781  		default:
  1782  			t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1783  		}
  1784  	}
  1785  }
  1786  
  1787  // Issue 30520 part 2.
  1788  func TestEvalSymlinksAboveRootChdir(t *testing.T) {
  1789  	testenv.MustHaveSymlink(t)
  1790  	t.Chdir(t.TempDir())
  1791  
  1792  	subdir := filepath.Join("a", "b")
  1793  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1794  		t.Fatal(err)
  1795  	}
  1796  	if err := os.Symlink(subdir, "c"); err != nil {
  1797  		t.Fatal(err)
  1798  	}
  1799  	if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
  1800  		t.Fatal(err)
  1801  	}
  1802  
  1803  	subdir = filepath.Join("d", "e", "f")
  1804  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1805  		t.Fatal(err)
  1806  	}
  1807  	if err := os.Chdir(subdir); err != nil {
  1808  		t.Fatal(err)
  1809  	}
  1810  
  1811  	check := filepath.Join("..", "..", "..", "c", "file")
  1812  	wantSuffix := filepath.Join("a", "b", "file")
  1813  	if resolved, err := filepath.EvalSymlinks(check); err != nil {
  1814  		t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1815  	} else if !strings.HasSuffix(resolved, wantSuffix) {
  1816  		t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1817  	} else {
  1818  		t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1819  	}
  1820  }
  1821  
  1822  func TestIssue51617(t *testing.T) {
  1823  	dir := t.TempDir()
  1824  	for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
  1825  		if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
  1826  			t.Fatal(err)
  1827  		}
  1828  	}
  1829  	bad := filepath.Join(dir, "a", "bad")
  1830  	if err := os.Chmod(bad, 0); err != nil {
  1831  		t.Fatal(err)
  1832  	}
  1833  	defer os.Chmod(bad, 0700) // avoid errors on cleanup
  1834  	var saw []string
  1835  	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
  1836  		if err != nil {
  1837  			return filepath.SkipDir
  1838  		}
  1839  		if d.IsDir() {
  1840  			rel, err := filepath.Rel(dir, path)
  1841  			if err != nil {
  1842  				t.Fatal(err)
  1843  			}
  1844  			saw = append(saw, rel)
  1845  		}
  1846  		return nil
  1847  	})
  1848  	if err != nil {
  1849  		t.Fatal(err)
  1850  	}
  1851  	want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
  1852  	if !slices.Equal(saw, want) {
  1853  		t.Errorf("got directories %v, want %v", saw, want)
  1854  	}
  1855  }
  1856  
  1857  func TestEscaping(t *testing.T) {
  1858  	dir := t.TempDir()
  1859  	t.Chdir(t.TempDir())
  1860  
  1861  	for _, p := range []string{
  1862  		filepath.Join(dir, "x"),
  1863  	} {
  1864  		if !filepath.IsLocal(p) {
  1865  			continue
  1866  		}
  1867  		f, err := os.Create(p)
  1868  		if err != nil {
  1869  			f.Close()
  1870  		}
  1871  		ents, err := os.ReadDir(dir)
  1872  		if err != nil {
  1873  			t.Fatal(err)
  1874  		}
  1875  		for _, e := range ents {
  1876  			t.Fatalf("found: %v", e.Name())
  1877  		}
  1878  	}
  1879  }
  1880  
  1881  func TestEvalSymlinksTooManyLinks(t *testing.T) {
  1882  	testenv.MustHaveSymlink(t)
  1883  	dir := filepath.Join(t.TempDir(), "dir")
  1884  	err := os.Symlink(dir, dir)
  1885  	if err != nil {
  1886  		t.Fatal(err)
  1887  	}
  1888  	_, err = filepath.EvalSymlinks(dir)
  1889  	if err == nil {
  1890  		t.Fatal("expected error, got nil")
  1891  	}
  1892  }
  1893  
  1894  func BenchmarkIsLocal(b *testing.B) {
  1895  	tests := islocaltests
  1896  	if runtime.GOOS == "windows" {
  1897  		tests = append(tests, winislocaltests...)
  1898  	}
  1899  	if runtime.GOOS == "plan9" {
  1900  		tests = append(tests, plan9islocaltests...)
  1901  	}
  1902  	for b.Loop() {
  1903  		for _, test := range tests {
  1904  			filepath.IsLocal(test.path)
  1905  		}
  1906  	}
  1907  }
  1908  

View as plain text