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  		t.Run(tt.desc, func(t *testing.T) {
   940  			var walked []string
   941  			err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
   942  				if err != nil {
   943  					return err
   944  				}
   945  				t.Logf("%#q: %v", path, info.Mode())
   946  				walked = append(walked, filepath.Clean(path))
   947  				return nil
   948  			})
   949  			if err != nil {
   950  				t.Fatal(err)
   951  			}
   952  
   953  			if !slices.Equal(walked, tt.want) {
   954  				t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
   955  				if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
   956  					t.Logf("(ignoring known bug on %v)", runtime.GOOS)
   957  				} else {
   958  					t.Fail()
   959  				}
   960  			}
   961  		})
   962  	}
   963  }
   964  
   965  var basetests = []PathTest{
   966  	{"", "."},
   967  	{".", "."},
   968  	{"/.", "."},
   969  	{"/", "/"},
   970  	{"////", "/"},
   971  	{"x/", "x"},
   972  	{"abc", "abc"},
   973  	{"abc/def", "def"},
   974  	{"a/b/.x", ".x"},
   975  	{"a/b/c.", "c."},
   976  	{"a/b/c.x", "c.x"},
   977  }
   978  
   979  var winbasetests = []PathTest{
   980  	{`c:\`, `\`},
   981  	{`c:.`, `.`},
   982  	{`c:\a\b`, `b`},
   983  	{`c:a\b`, `b`},
   984  	{`c:a\b\c`, `c`},
   985  	{`\\host\share\`, `\`},
   986  	{`\\host\share\a`, `a`},
   987  	{`\\host\share\a\b`, `b`},
   988  }
   989  
   990  func TestBase(t *testing.T) {
   991  	tests := basetests
   992  	if runtime.GOOS == "windows" {
   993  		// make unix tests work on windows
   994  		for i := range tests {
   995  			tests[i].result = filepath.Clean(tests[i].result)
   996  		}
   997  		// add windows specific tests
   998  		tests = append(tests, winbasetests...)
   999  	}
  1000  	for _, test := range tests {
  1001  		if s := filepath.Base(test.path); s != test.result {
  1002  			t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
  1003  		}
  1004  	}
  1005  }
  1006  
  1007  var dirtests = []PathTest{
  1008  	{"", "."},
  1009  	{".", "."},
  1010  	{"/.", "/"},
  1011  	{"/", "/"},
  1012  	{"/foo", "/"},
  1013  	{"x/", "x"},
  1014  	{"abc", "."},
  1015  	{"abc/def", "abc"},
  1016  	{"a/b/.x", "a/b"},
  1017  	{"a/b/c.", "a/b"},
  1018  	{"a/b/c.x", "a/b"},
  1019  }
  1020  
  1021  var nonwindirtests = []PathTest{
  1022  	{"////", "/"},
  1023  }
  1024  
  1025  var windirtests = []PathTest{
  1026  	{`c:\`, `c:\`},
  1027  	{`c:.`, `c:.`},
  1028  	{`c:\a\b`, `c:\a`},
  1029  	{`c:a\b`, `c:a`},
  1030  	{`c:a\b\c`, `c:a\b`},
  1031  	{`\\host\share`, `\\host\share`},
  1032  	{`\\host\share\`, `\\host\share\`},
  1033  	{`\\host\share\a`, `\\host\share\`},
  1034  	{`\\host\share\a\b`, `\\host\share\a`},
  1035  	{`\\\\`, `\\\\`},
  1036  }
  1037  
  1038  func TestDir(t *testing.T) {
  1039  	tests := dirtests
  1040  	if runtime.GOOS == "windows" {
  1041  		// make unix tests work on windows
  1042  		for i := range tests {
  1043  			tests[i].result = filepath.Clean(tests[i].result)
  1044  		}
  1045  		// add windows specific tests
  1046  		tests = append(tests, windirtests...)
  1047  	} else {
  1048  		tests = append(tests, nonwindirtests...)
  1049  	}
  1050  	for _, test := range tests {
  1051  		if s := filepath.Dir(test.path); s != test.result {
  1052  			t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
  1053  		}
  1054  	}
  1055  }
  1056  
  1057  type IsAbsTest struct {
  1058  	path  string
  1059  	isAbs bool
  1060  }
  1061  
  1062  var isabstests = []IsAbsTest{
  1063  	{"", false},
  1064  	{"/", true},
  1065  	{"/usr/bin/gcc", true},
  1066  	{"..", false},
  1067  	{"/a/../bb", true},
  1068  	{".", false},
  1069  	{"./", false},
  1070  	{"lala", false},
  1071  }
  1072  
  1073  var winisabstests = []IsAbsTest{
  1074  	{`C:\`, true},
  1075  	{`c\`, false},
  1076  	{`c::`, false},
  1077  	{`c:`, false},
  1078  	{`/`, false},
  1079  	{`\`, false},
  1080  	{`\Windows`, false},
  1081  	{`c:a\b`, false},
  1082  	{`c:\a\b`, true},
  1083  	{`c:/a/b`, true},
  1084  	{`\\host\share`, true},
  1085  	{`\\host\share\`, true},
  1086  	{`\\host\share\foo`, true},
  1087  	{`//host/share/foo/bar`, true},
  1088  	{`\\?\a\b\c`, true},
  1089  	{`\??\a\b\c`, true},
  1090  }
  1091  
  1092  func TestIsAbs(t *testing.T) {
  1093  	var tests []IsAbsTest
  1094  	if runtime.GOOS == "windows" {
  1095  		tests = append(tests, winisabstests...)
  1096  		// All non-windows tests should fail, because they have no volume letter.
  1097  		for _, test := range isabstests {
  1098  			tests = append(tests, IsAbsTest{test.path, false})
  1099  		}
  1100  		// All non-windows test should work as intended if prefixed with volume letter.
  1101  		for _, test := range isabstests {
  1102  			tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
  1103  		}
  1104  	} else {
  1105  		tests = isabstests
  1106  	}
  1107  
  1108  	for _, test := range tests {
  1109  		if r := filepath.IsAbs(test.path); r != test.isAbs {
  1110  			t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
  1111  		}
  1112  	}
  1113  }
  1114  
  1115  type EvalSymlinksTest struct {
  1116  	// If dest is empty, the path is created; otherwise the dest is symlinked to the path.
  1117  	path, dest string
  1118  }
  1119  
  1120  var EvalSymlinksTestDirs = []EvalSymlinksTest{
  1121  	{"test", ""},
  1122  	{"test/dir", ""},
  1123  	{"test/dir/link3", "../../"},
  1124  	{"test/link1", "../test"},
  1125  	{"test/link2", "dir"},
  1126  	{"test/linkabs", "/"},
  1127  	{"test/link4", "../test2"},
  1128  	{"test2", "test/dir"},
  1129  	// Issue 23444.
  1130  	{"src", ""},
  1131  	{"src/pool", ""},
  1132  	{"src/pool/test", ""},
  1133  	{"src/versions", ""},
  1134  	{"src/versions/current", "../../version"},
  1135  	{"src/versions/v1", ""},
  1136  	{"src/versions/v1/modules", ""},
  1137  	{"src/versions/v1/modules/test", "../../../pool/test"},
  1138  	{"version", "src/versions/v1"},
  1139  }
  1140  
  1141  var EvalSymlinksTests = []EvalSymlinksTest{
  1142  	{"test", "test"},
  1143  	{"test/dir", "test/dir"},
  1144  	{"test/dir/../..", "."},
  1145  	{"test/link1", "test"},
  1146  	{"test/link2", "test/dir"},
  1147  	{"test/link1/dir", "test/dir"},
  1148  	{"test/link2/..", "test"},
  1149  	{"test/dir/link3", "."},
  1150  	{"test/link2/link3/test", "test"},
  1151  	{"test/linkabs", "/"},
  1152  	{"test/link4/..", "test"},
  1153  	{"src/versions/current/modules/test", "src/pool/test"},
  1154  }
  1155  
  1156  // simpleJoin builds a file name from the directory and path.
  1157  // It does not use Join because we don't want ".." to be evaluated.
  1158  func simpleJoin(dir, path string) string {
  1159  	return dir + string(filepath.Separator) + path
  1160  }
  1161  
  1162  func testEvalSymlinks(t *testing.T, path, want string) {
  1163  	have, err := filepath.EvalSymlinks(path)
  1164  	if err != nil {
  1165  		t.Errorf("EvalSymlinks(%q) error: %v", path, err)
  1166  		return
  1167  	}
  1168  	if filepath.Clean(have) != filepath.Clean(want) {
  1169  		t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
  1170  	}
  1171  }
  1172  
  1173  func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
  1174  	t.Chdir(wd)
  1175  	have, err := filepath.EvalSymlinks(path)
  1176  	if err != nil {
  1177  		t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
  1178  		return
  1179  	}
  1180  	if filepath.Clean(have) != filepath.Clean(want) {
  1181  		t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
  1182  	}
  1183  }
  1184  
  1185  func TestEvalSymlinks(t *testing.T) {
  1186  	testenv.MustHaveSymlink(t)
  1187  
  1188  	tmpDir := t.TempDir()
  1189  
  1190  	// /tmp may itself be a symlink! Avoid the confusion, although
  1191  	// it means trusting the thing we're testing.
  1192  	var err error
  1193  	tmpDir, err = filepath.EvalSymlinks(tmpDir)
  1194  	if err != nil {
  1195  		t.Fatal("eval symlink for tmp dir:", err)
  1196  	}
  1197  
  1198  	// Create the symlink farm using relative paths.
  1199  	for _, d := range EvalSymlinksTestDirs {
  1200  		var err error
  1201  		path := simpleJoin(tmpDir, d.path)
  1202  		if d.dest == "" {
  1203  			err = os.Mkdir(path, 0755)
  1204  		} else {
  1205  			err = os.Symlink(d.dest, path)
  1206  		}
  1207  		if err != nil {
  1208  			t.Fatal(err)
  1209  		}
  1210  	}
  1211  
  1212  	// Evaluate the symlink farm.
  1213  	for _, test := range EvalSymlinksTests {
  1214  		path := simpleJoin(tmpDir, test.path)
  1215  
  1216  		dest := simpleJoin(tmpDir, test.dest)
  1217  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1218  			dest = test.dest
  1219  		}
  1220  		testEvalSymlinks(t, path, dest)
  1221  
  1222  		// test EvalSymlinks(".")
  1223  		testEvalSymlinksAfterChdir(t, path, ".", ".")
  1224  
  1225  		// test EvalSymlinks("C:.") on Windows
  1226  		if runtime.GOOS == "windows" {
  1227  			volDot := filepath.VolumeName(tmpDir) + "."
  1228  			testEvalSymlinksAfterChdir(t, path, volDot, volDot)
  1229  		}
  1230  
  1231  		// test EvalSymlinks(".."+path)
  1232  		dotdotPath := simpleJoin("..", test.dest)
  1233  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1234  			dotdotPath = test.dest
  1235  		}
  1236  		testEvalSymlinksAfterChdir(t,
  1237  			simpleJoin(tmpDir, "test"),
  1238  			simpleJoin("..", test.path),
  1239  			dotdotPath)
  1240  
  1241  		// test EvalSymlinks(p) where p is relative path
  1242  		testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
  1243  	}
  1244  }
  1245  
  1246  func TestEvalSymlinksIsNotExist(t *testing.T) {
  1247  	testenv.MustHaveSymlink(t)
  1248  	t.Chdir(t.TempDir())
  1249  
  1250  	_, err := filepath.EvalSymlinks("notexist")
  1251  	if !os.IsNotExist(err) {
  1252  		t.Errorf("expected the file is not found, got %v\n", err)
  1253  	}
  1254  
  1255  	err = os.Symlink("notexist", "link")
  1256  	if err != nil {
  1257  		t.Fatal(err)
  1258  	}
  1259  	defer os.Remove("link")
  1260  
  1261  	_, err = filepath.EvalSymlinks("link")
  1262  	if !os.IsNotExist(err) {
  1263  		t.Errorf("expected the file is not found, got %v\n", err)
  1264  	}
  1265  }
  1266  
  1267  func TestIssue13582(t *testing.T) {
  1268  	testenv.MustHaveSymlink(t)
  1269  
  1270  	tmpDir := t.TempDir()
  1271  
  1272  	dir := filepath.Join(tmpDir, "dir")
  1273  	err := os.Mkdir(dir, 0755)
  1274  	if err != nil {
  1275  		t.Fatal(err)
  1276  	}
  1277  	linkToDir := filepath.Join(tmpDir, "link_to_dir")
  1278  	err = os.Symlink(dir, linkToDir)
  1279  	if err != nil {
  1280  		t.Fatal(err)
  1281  	}
  1282  	file := filepath.Join(linkToDir, "file")
  1283  	err = os.WriteFile(file, nil, 0644)
  1284  	if err != nil {
  1285  		t.Fatal(err)
  1286  	}
  1287  	link1 := filepath.Join(linkToDir, "link1")
  1288  	err = os.Symlink(file, link1)
  1289  	if err != nil {
  1290  		t.Fatal(err)
  1291  	}
  1292  	link2 := filepath.Join(linkToDir, "link2")
  1293  	err = os.Symlink(link1, link2)
  1294  	if err != nil {
  1295  		t.Fatal(err)
  1296  	}
  1297  
  1298  	// /tmp may itself be a symlink!
  1299  	realTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1300  	if err != nil {
  1301  		t.Fatal(err)
  1302  	}
  1303  	realDir := filepath.Join(realTmpDir, "dir")
  1304  	realFile := filepath.Join(realDir, "file")
  1305  
  1306  	tests := []struct {
  1307  		path, want string
  1308  	}{
  1309  		{dir, realDir},
  1310  		{linkToDir, realDir},
  1311  		{file, realFile},
  1312  		{link1, realFile},
  1313  		{link2, realFile},
  1314  	}
  1315  	for i, test := range tests {
  1316  		have, err := filepath.EvalSymlinks(test.path)
  1317  		if err != nil {
  1318  			t.Fatal(err)
  1319  		}
  1320  		if have != test.want {
  1321  			t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
  1322  		}
  1323  	}
  1324  }
  1325  
  1326  // Issue 57905.
  1327  func TestRelativeSymlinkToAbsolute(t *testing.T) {
  1328  	testenv.MustHaveSymlink(t)
  1329  	// Not parallel: uses t.Chdir.
  1330  
  1331  	tmpDir := t.TempDir()
  1332  	t.Chdir(tmpDir)
  1333  
  1334  	// Create "link" in the current working directory as a symlink to an arbitrary
  1335  	// absolute path. On macOS, this path is likely to begin with a symlink
  1336  	// itself: generally either in /var (symlinked to "private/var") or /tmp
  1337  	// (symlinked to "private/tmp").
  1338  	if err := os.Symlink(tmpDir, "link"); err != nil {
  1339  		t.Fatal(err)
  1340  	}
  1341  	t.Logf(`os.Symlink(%q, "link")`, tmpDir)
  1342  
  1343  	p, err := filepath.EvalSymlinks("link")
  1344  	if err != nil {
  1345  		t.Fatalf(`EvalSymlinks("link"): %v`, err)
  1346  	}
  1347  	want, err := filepath.EvalSymlinks(tmpDir)
  1348  	if err != nil {
  1349  		t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
  1350  	}
  1351  	if p != want {
  1352  		t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
  1353  	}
  1354  	t.Logf(`EvalSymlinks("link") = %q`, p)
  1355  }
  1356  
  1357  // Test directories relative to temporary directory.
  1358  // The tests are run in absTestDirs[0].
  1359  var absTestDirs = []string{
  1360  	"a",
  1361  	"a/b",
  1362  	"a/b/c",
  1363  }
  1364  
  1365  // Test paths relative to temporary directory. $ expands to the directory.
  1366  // The tests are run in absTestDirs[0].
  1367  // We create absTestDirs first.
  1368  var absTests = []string{
  1369  	".",
  1370  	"b",
  1371  	"b/",
  1372  	"../a",
  1373  	"../a/b",
  1374  	"../a/b/./c/../../.././a",
  1375  	"../a/b/./c/../../.././a/",
  1376  	"$",
  1377  	"$/.",
  1378  	"$/a/../a/b",
  1379  	"$/a/b/c/../../.././a",
  1380  	"$/a/b/c/../../.././a/",
  1381  }
  1382  
  1383  func TestAbs(t *testing.T) {
  1384  	root := t.TempDir()
  1385  	t.Chdir(root)
  1386  
  1387  	for _, dir := range absTestDirs {
  1388  		err := os.Mkdir(dir, 0777)
  1389  		if err != nil {
  1390  			t.Fatal("Mkdir failed: ", err)
  1391  		}
  1392  	}
  1393  
  1394  	// Make sure the global absTests slice is not
  1395  	// modified by multiple invocations of TestAbs.
  1396  	tests := absTests
  1397  	if runtime.GOOS == "windows" {
  1398  		vol := filepath.VolumeName(root)
  1399  		var extra []string
  1400  		for _, path := range absTests {
  1401  			if strings.Contains(path, "$") {
  1402  				continue
  1403  			}
  1404  			path = vol + path
  1405  			extra = append(extra, path)
  1406  		}
  1407  		tests = append(slices.Clip(tests), extra...)
  1408  	}
  1409  
  1410  	err := os.Chdir(absTestDirs[0])
  1411  	if err != nil {
  1412  		t.Fatal("chdir failed: ", err)
  1413  	}
  1414  
  1415  	for _, path := range tests {
  1416  		path = strings.ReplaceAll(path, "$", root)
  1417  		info, err := os.Stat(path)
  1418  		if err != nil {
  1419  			t.Errorf("%s: %s", path, err)
  1420  			continue
  1421  		}
  1422  
  1423  		abspath, err := filepath.Abs(path)
  1424  		if err != nil {
  1425  			t.Errorf("Abs(%q) error: %v", path, err)
  1426  			continue
  1427  		}
  1428  		absinfo, err := os.Stat(abspath)
  1429  		if err != nil || !os.SameFile(absinfo, info) {
  1430  			t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
  1431  		}
  1432  		if !filepath.IsAbs(abspath) {
  1433  			t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
  1434  		}
  1435  		if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1436  			t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
  1437  		}
  1438  	}
  1439  }
  1440  
  1441  // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
  1442  // We test it separately from all other absTests because the empty string is not
  1443  // a valid path, so it can't be used with os.Stat.
  1444  func TestAbsEmptyString(t *testing.T) {
  1445  	root := t.TempDir()
  1446  	t.Chdir(root)
  1447  
  1448  	info, err := os.Stat(root)
  1449  	if err != nil {
  1450  		t.Fatalf("%s: %s", root, err)
  1451  	}
  1452  
  1453  	abspath, err := filepath.Abs("")
  1454  	if err != nil {
  1455  		t.Fatalf(`Abs("") error: %v`, err)
  1456  	}
  1457  	absinfo, err := os.Stat(abspath)
  1458  	if err != nil || !os.SameFile(absinfo, info) {
  1459  		t.Errorf(`Abs("")=%q, not the same file`, abspath)
  1460  	}
  1461  	if !filepath.IsAbs(abspath) {
  1462  		t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
  1463  	}
  1464  	if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1465  		t.Errorf(`Abs("")=%q, isn't clean`, abspath)
  1466  	}
  1467  }
  1468  
  1469  type RelTests struct {
  1470  	root, path, want string
  1471  }
  1472  
  1473  var reltests = []RelTests{
  1474  	{"a/b", "a/b", "."},
  1475  	{"a/b/.", "a/b", "."},
  1476  	{"a/b", "a/b/.", "."},
  1477  	{"./a/b", "a/b", "."},
  1478  	{"a/b", "./a/b", "."},
  1479  	{"ab/cd", "ab/cde", "../cde"},
  1480  	{"ab/cd", "ab/c", "../c"},
  1481  	{"a/b", "a/b/c/d", "c/d"},
  1482  	{"a/b", "a/b/../c", "../c"},
  1483  	{"a/b/../c", "a/b", "../b"},
  1484  	{"a/b/c", "a/c/d", "../../c/d"},
  1485  	{"a/b", "c/d", "../../c/d"},
  1486  	{"a/b/c/d", "a/b", "../.."},
  1487  	{"a/b/c/d", "a/b/", "../.."},
  1488  	{"a/b/c/d/", "a/b", "../.."},
  1489  	{"a/b/c/d/", "a/b/", "../.."},
  1490  	{"../../a/b", "../../a/b/c/d", "c/d"},
  1491  	{"/a/b", "/a/b", "."},
  1492  	{"/a/b/.", "/a/b", "."},
  1493  	{"/a/b", "/a/b/.", "."},
  1494  	{"/ab/cd", "/ab/cde", "../cde"},
  1495  	{"/ab/cd", "/ab/c", "../c"},
  1496  	{"/a/b", "/a/b/c/d", "c/d"},
  1497  	{"/a/b", "/a/b/../c", "../c"},
  1498  	{"/a/b/../c", "/a/b", "../b"},
  1499  	{"/a/b/c", "/a/c/d", "../../c/d"},
  1500  	{"/a/b", "/c/d", "../../c/d"},
  1501  	{"/a/b/c/d", "/a/b", "../.."},
  1502  	{"/a/b/c/d", "/a/b/", "../.."},
  1503  	{"/a/b/c/d/", "/a/b", "../.."},
  1504  	{"/a/b/c/d/", "/a/b/", "../.."},
  1505  	{"/../../a/b", "/../../a/b/c/d", "c/d"},
  1506  	{".", "a/b", "a/b"},
  1507  	{".", "..", ".."},
  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  
  1893  func BenchmarkIsLocal(b *testing.B) {
  1894  	tests := islocaltests
  1895  	if runtime.GOOS == "windows" {
  1896  		tests = append(tests, winislocaltests...)
  1897  	}
  1898  	if runtime.GOOS == "plan9" {
  1899  		tests = append(tests, plan9islocaltests...)
  1900  	}
  1901  	for b.Loop() {
  1902  		for _, test := range tests {
  1903  			filepath.IsLocal(test.path)
  1904  		}
  1905  	}
  1906  }
  1907  

View as plain text