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  	"sort"
    18  	"strings"
    19  	"syscall"
    20  	"testing"
    21  )
    22  
    23  type PathTest struct {
    24  	path, result string
    25  }
    26  
    27  var cleantests = []PathTest{
    28  	// Already clean
    29  	{"abc", "abc"},
    30  	{"abc/def", "abc/def"},
    31  	{"a/b/c", "a/b/c"},
    32  	{".", "."},
    33  	{"..", ".."},
    34  	{"../..", "../.."},
    35  	{"../../abc", "../../abc"},
    36  	{"/abc", "/abc"},
    37  	{"/", "/"},
    38  
    39  	// Empty is current dir
    40  	{"", "."},
    41  
    42  	// Remove trailing slash
    43  	{"abc/", "abc"},
    44  	{"abc/def/", "abc/def"},
    45  	{"a/b/c/", "a/b/c"},
    46  	{"./", "."},
    47  	{"../", ".."},
    48  	{"../../", "../.."},
    49  	{"/abc/", "/abc"},
    50  
    51  	// Remove doubled slash
    52  	{"abc//def//ghi", "abc/def/ghi"},
    53  	{"abc//", "abc"},
    54  
    55  	// Remove . elements
    56  	{"abc/./def", "abc/def"},
    57  	{"/./abc/def", "/abc/def"},
    58  	{"abc/.", "abc"},
    59  
    60  	// Remove .. elements
    61  	{"abc/def/ghi/../jkl", "abc/def/jkl"},
    62  	{"abc/def/../ghi/../jkl", "abc/jkl"},
    63  	{"abc/def/..", "abc"},
    64  	{"abc/def/../..", "."},
    65  	{"/abc/def/../..", "/"},
    66  	{"abc/def/../../..", ".."},
    67  	{"/abc/def/../../..", "/"},
    68  	{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
    69  	{"/../abc", "/abc"},
    70  	{"a/../b:/../../c", `../c`},
    71  
    72  	// Combinations
    73  	{"abc/./../def", "def"},
    74  	{"abc//./../def", "def"},
    75  	{"abc/../../././../def", "../../def"},
    76  }
    77  
    78  var nonwincleantests = []PathTest{
    79  	// Remove leading doubled slash
    80  	{"//abc", "/abc"},
    81  	{"///abc", "/abc"},
    82  	{"//abc//", "/abc"},
    83  }
    84  
    85  var wincleantests = []PathTest{
    86  	{`c:`, `c:.`},
    87  	{`c:\`, `c:\`},
    88  	{`c:\abc`, `c:\abc`},
    89  	{`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
    90  	{`c:\abc\def\..\..`, `c:\`},
    91  	{`c:\..\abc`, `c:\abc`},
    92  	{`c:..\abc`, `c:..\abc`},
    93  	{`c:\b:\..\..\..\d`, `c:\d`},
    94  	{`\`, `\`},
    95  	{`/`, `\`},
    96  	{`\\i\..\c$`, `\\i\..\c$`},
    97  	{`\\i\..\i\c$`, `\\i\..\i\c$`},
    98  	{`\\i\..\I\c$`, `\\i\..\I\c$`},
    99  	{`\\host\share\foo\..\bar`, `\\host\share\bar`},
   100  	{`//host/share/foo/../baz`, `\\host\share\baz`},
   101  	{`\\host\share\foo\..\..\..\..\bar`, `\\host\share\bar`},
   102  	{`\\.\C:\a\..\..\..\..\bar`, `\\.\C:\bar`},
   103  	{`\\.\C:\\\\a`, `\\.\C:\a`},
   104  	{`\\a\b\..\c`, `\\a\b\c`},
   105  	{`\\a\b`, `\\a\b`},
   106  	{`.\c:`, `.\c:`},
   107  	{`.\c:\foo`, `.\c:\foo`},
   108  	{`.\c:foo`, `.\c:foo`},
   109  	{`//abc`, `\\abc`},
   110  	{`///abc`, `\\\abc`},
   111  	{`//abc//`, `\\abc\\`},
   112  	{`\\?\C:\`, `\\?\C:\`},
   113  	{`\\?\C:\a`, `\\?\C:\a`},
   114  
   115  	// Don't allow cleaning to move an element with a colon to the start of the path.
   116  	{`a/../c:`, `.\c:`},
   117  	{`a\..\c:`, `.\c:`},
   118  	{`a/../c:/a`, `.\c:\a`},
   119  	{`a/../../c:`, `..\c:`},
   120  	{`foo:bar`, `foo:bar`},
   121  
   122  	// Don't allow cleaning to create a Root Local Device path like \??\a.
   123  	{`/a/../??/a`, `\.\??\a`},
   124  }
   125  
   126  func TestClean(t *testing.T) {
   127  	tests := cleantests
   128  	if runtime.GOOS == "windows" {
   129  		for i := range tests {
   130  			tests[i].result = filepath.FromSlash(tests[i].result)
   131  		}
   132  		tests = append(tests, wincleantests...)
   133  	} else {
   134  		tests = append(tests, nonwincleantests...)
   135  	}
   136  	for _, test := range tests {
   137  		if s := filepath.Clean(test.path); s != test.result {
   138  			t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
   139  		}
   140  		if s := filepath.Clean(test.result); s != test.result {
   141  			t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
   142  		}
   143  	}
   144  
   145  	if testing.Short() {
   146  		t.Skip("skipping malloc count in short mode")
   147  	}
   148  	if runtime.GOMAXPROCS(0) > 1 {
   149  		t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
   150  		return
   151  	}
   152  
   153  	for _, test := range tests {
   154  		allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
   155  		if allocs > 0 {
   156  			t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
   157  		}
   158  	}
   159  }
   160  
   161  type IsLocalTest struct {
   162  	path    string
   163  	isLocal bool
   164  }
   165  
   166  var islocaltests = []IsLocalTest{
   167  	{"", false},
   168  	{".", true},
   169  	{"..", false},
   170  	{"../a", false},
   171  	{"/", false},
   172  	{"/a", false},
   173  	{"/a/../..", false},
   174  	{"a", true},
   175  	{"a/../a", true},
   176  	{"a/", true},
   177  	{"a/.", true},
   178  	{"a/./b/./c", true},
   179  	{`a/../b:/../../c`, false},
   180  }
   181  
   182  var winislocaltests = []IsLocalTest{
   183  	{"NUL", false},
   184  	{"nul", false},
   185  	{"nul ", false},
   186  	{"nul.", false},
   187  	{"a/nul:", false},
   188  	{"a/nul : a", false},
   189  	{"com0", true},
   190  	{"com1", false},
   191  	{"com2", false},
   192  	{"com3", false},
   193  	{"com4", false},
   194  	{"com5", false},
   195  	{"com6", false},
   196  	{"com7", false},
   197  	{"com8", false},
   198  	{"com9", false},
   199  	{"com¹", false},
   200  	{"com²", false},
   201  	{"com³", false},
   202  	{"com¹ : a", false},
   203  	{"cOm1", false},
   204  	{"lpt1", false},
   205  	{"LPT1", false},
   206  	{"lpt³", false},
   207  	{"./nul", false},
   208  	{`\`, false},
   209  	{`\a`, false},
   210  	{`C:`, false},
   211  	{`C:\a`, false},
   212  	{`..\a`, false},
   213  	{`a/../c:`, false},
   214  	{`CONIN$`, false},
   215  	{`conin$`, false},
   216  	{`CONOUT$`, false},
   217  	{`conout$`, false},
   218  	{`dollar$`, true}, // not a special file name
   219  }
   220  
   221  var plan9islocaltests = []IsLocalTest{
   222  	{"#a", false},
   223  }
   224  
   225  func TestIsLocal(t *testing.T) {
   226  	tests := islocaltests
   227  	if runtime.GOOS == "windows" {
   228  		tests = append(tests, winislocaltests...)
   229  	}
   230  	if runtime.GOOS == "plan9" {
   231  		tests = append(tests, plan9islocaltests...)
   232  	}
   233  	for _, test := range tests {
   234  		if got := filepath.IsLocal(test.path); got != test.isLocal {
   235  			t.Errorf("IsLocal(%q) = %v, want %v", test.path, got, test.isLocal)
   236  		}
   237  	}
   238  }
   239  
   240  const sep = filepath.Separator
   241  
   242  var slashtests = []PathTest{
   243  	{"", ""},
   244  	{"/", string(sep)},
   245  	{"/a/b", string([]byte{sep, 'a', sep, 'b'})},
   246  	{"a//b", string([]byte{'a', sep, sep, 'b'})},
   247  }
   248  
   249  func TestFromAndToSlash(t *testing.T) {
   250  	for _, test := range slashtests {
   251  		if s := filepath.FromSlash(test.path); s != test.result {
   252  			t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
   253  		}
   254  		if s := filepath.ToSlash(test.result); s != test.path {
   255  			t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
   256  		}
   257  	}
   258  }
   259  
   260  type SplitListTest struct {
   261  	list   string
   262  	result []string
   263  }
   264  
   265  const lsep = filepath.ListSeparator
   266  
   267  var splitlisttests = []SplitListTest{
   268  	{"", []string{}},
   269  	{string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
   270  	{string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
   271  }
   272  
   273  var winsplitlisttests = []SplitListTest{
   274  	// quoted
   275  	{`"a"`, []string{`a`}},
   276  
   277  	// semicolon
   278  	{`";"`, []string{`;`}},
   279  	{`"a;b"`, []string{`a;b`}},
   280  	{`";";`, []string{`;`, ``}},
   281  	{`;";"`, []string{``, `;`}},
   282  
   283  	// partially quoted
   284  	{`a";"b`, []string{`a;b`}},
   285  	{`a; ""b`, []string{`a`, ` b`}},
   286  	{`"a;b`, []string{`a;b`}},
   287  	{`""a;b`, []string{`a`, `b`}},
   288  	{`"""a;b`, []string{`a;b`}},
   289  	{`""""a;b`, []string{`a`, `b`}},
   290  	{`a";b`, []string{`a;b`}},
   291  	{`a;b";c`, []string{`a`, `b;c`}},
   292  	{`"a";b";c`, []string{`a`, `b;c`}},
   293  }
   294  
   295  func TestSplitList(t *testing.T) {
   296  	tests := splitlisttests
   297  	if runtime.GOOS == "windows" {
   298  		tests = append(tests, winsplitlisttests...)
   299  	}
   300  	for _, test := range tests {
   301  		if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
   302  			t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
   303  		}
   304  	}
   305  }
   306  
   307  type SplitTest struct {
   308  	path, dir, file string
   309  }
   310  
   311  var unixsplittests = []SplitTest{
   312  	{"a/b", "a/", "b"},
   313  	{"a/b/", "a/b/", ""},
   314  	{"a/", "a/", ""},
   315  	{"a", "", "a"},
   316  	{"/", "/", ""},
   317  }
   318  
   319  var winsplittests = []SplitTest{
   320  	{`c:`, `c:`, ``},
   321  	{`c:/`, `c:/`, ``},
   322  	{`c:/foo`, `c:/`, `foo`},
   323  	{`c:/foo/bar`, `c:/foo/`, `bar`},
   324  	{`//host/share`, `//host/share`, ``},
   325  	{`//host/share/`, `//host/share/`, ``},
   326  	{`//host/share/foo`, `//host/share/`, `foo`},
   327  	{`\\host\share`, `\\host\share`, ``},
   328  	{`\\host\share\`, `\\host\share\`, ``},
   329  	{`\\host\share\foo`, `\\host\share\`, `foo`},
   330  }
   331  
   332  func TestSplit(t *testing.T) {
   333  	var splittests []SplitTest
   334  	splittests = unixsplittests
   335  	if runtime.GOOS == "windows" {
   336  		splittests = append(splittests, winsplittests...)
   337  	}
   338  	for _, test := range splittests {
   339  		if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
   340  			t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
   341  		}
   342  	}
   343  }
   344  
   345  type JoinTest struct {
   346  	elem []string
   347  	path string
   348  }
   349  
   350  var jointests = []JoinTest{
   351  	// zero parameters
   352  	{[]string{}, ""},
   353  
   354  	// one parameter
   355  	{[]string{""}, ""},
   356  	{[]string{"/"}, "/"},
   357  	{[]string{"a"}, "a"},
   358  
   359  	// two parameters
   360  	{[]string{"a", "b"}, "a/b"},
   361  	{[]string{"a", ""}, "a"},
   362  	{[]string{"", "b"}, "b"},
   363  	{[]string{"/", "a"}, "/a"},
   364  	{[]string{"/", "a/b"}, "/a/b"},
   365  	{[]string{"/", ""}, "/"},
   366  	{[]string{"/a", "b"}, "/a/b"},
   367  	{[]string{"a", "/b"}, "a/b"},
   368  	{[]string{"/a", "/b"}, "/a/b"},
   369  	{[]string{"a/", "b"}, "a/b"},
   370  	{[]string{"a/", ""}, "a"},
   371  	{[]string{"", ""}, ""},
   372  
   373  	// three parameters
   374  	{[]string{"/", "a", "b"}, "/a/b"},
   375  }
   376  
   377  var nonwinjointests = []JoinTest{
   378  	{[]string{"//", "a"}, "/a"},
   379  }
   380  
   381  var winjointests = []JoinTest{
   382  	{[]string{`directory`, `file`}, `directory\file`},
   383  	{[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
   384  	{[]string{`C:\Windows\`, ``}, `C:\Windows`},
   385  	{[]string{`C:\`, `Windows`}, `C:\Windows`},
   386  	{[]string{`C:`, `a`}, `C:a`},
   387  	{[]string{`C:`, `a\b`}, `C:a\b`},
   388  	{[]string{`C:`, `a`, `b`}, `C:a\b`},
   389  	{[]string{`C:`, ``, `b`}, `C:b`},
   390  	{[]string{`C:`, ``, ``, `b`}, `C:b`},
   391  	{[]string{`C:`, ``}, `C:.`},
   392  	{[]string{`C:`, ``, ``}, `C:.`},
   393  	{[]string{`C:`, `\a`}, `C:\a`},
   394  	{[]string{`C:`, ``, `\a`}, `C:\a`},
   395  	{[]string{`C:.`, `a`}, `C:a`},
   396  	{[]string{`C:a`, `b`}, `C:a\b`},
   397  	{[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
   398  	{[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
   399  	{[]string{`\\host\share\foo`}, `\\host\share\foo`},
   400  	{[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
   401  	{[]string{`\`}, `\`},
   402  	{[]string{`\`, ``}, `\`},
   403  	{[]string{`\`, `a`}, `\a`},
   404  	{[]string{`\\`, `a`}, `\\a`},
   405  	{[]string{`\`, `a`, `b`}, `\a\b`},
   406  	{[]string{`\\`, `a`, `b`}, `\\a\b`},
   407  	{[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
   408  	{[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
   409  	{[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
   410  	{[]string{`//`, `a`}, `\\a`},
   411  	{[]string{`a:\b\c`, `x\..\y:\..\..\z`}, `a:\b\z`},
   412  	{[]string{`\`, `??\a`}, `\.\??\a`},
   413  }
   414  
   415  func TestJoin(t *testing.T) {
   416  	if runtime.GOOS == "windows" {
   417  		jointests = append(jointests, winjointests...)
   418  	} else {
   419  		jointests = append(jointests, nonwinjointests...)
   420  	}
   421  	for _, test := range jointests {
   422  		expected := filepath.FromSlash(test.path)
   423  		if p := filepath.Join(test.elem...); p != expected {
   424  			t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
   425  		}
   426  	}
   427  }
   428  
   429  type ExtTest struct {
   430  	path, ext string
   431  }
   432  
   433  var exttests = []ExtTest{
   434  	{"path.go", ".go"},
   435  	{"path.pb.go", ".go"},
   436  	{"a.dir/b", ""},
   437  	{"a.dir/b.go", ".go"},
   438  	{"a.dir/", ""},
   439  }
   440  
   441  func TestExt(t *testing.T) {
   442  	for _, test := range exttests {
   443  		if x := filepath.Ext(test.path); x != test.ext {
   444  			t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
   445  		}
   446  	}
   447  }
   448  
   449  type Node struct {
   450  	name    string
   451  	entries []*Node // nil if the entry is a file
   452  	mark    int
   453  }
   454  
   455  var tree = &Node{
   456  	"testdata",
   457  	[]*Node{
   458  		{"a", nil, 0},
   459  		{"b", []*Node{}, 0},
   460  		{"c", nil, 0},
   461  		{
   462  			"d",
   463  			[]*Node{
   464  				{"x", nil, 0},
   465  				{"y", []*Node{}, 0},
   466  				{
   467  					"z",
   468  					[]*Node{
   469  						{"u", nil, 0},
   470  						{"v", nil, 0},
   471  					},
   472  					0,
   473  				},
   474  			},
   475  			0,
   476  		},
   477  	},
   478  	0,
   479  }
   480  
   481  func walkTree(n *Node, path string, f func(path string, n *Node)) {
   482  	f(path, n)
   483  	for _, e := range n.entries {
   484  		walkTree(e, filepath.Join(path, e.name), f)
   485  	}
   486  }
   487  
   488  func makeTree(t *testing.T) {
   489  	walkTree(tree, tree.name, func(path string, n *Node) {
   490  		if n.entries == nil {
   491  			fd, err := os.Create(path)
   492  			if err != nil {
   493  				t.Errorf("makeTree: %v", err)
   494  				return
   495  			}
   496  			fd.Close()
   497  		} else {
   498  			os.Mkdir(path, 0770)
   499  		}
   500  	})
   501  }
   502  
   503  func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
   504  
   505  func checkMarks(t *testing.T, report bool) {
   506  	walkTree(tree, tree.name, func(path string, n *Node) {
   507  		if n.mark != 1 && report {
   508  			t.Errorf("node %s mark = %d; expected 1", path, n.mark)
   509  		}
   510  		n.mark = 0
   511  	})
   512  }
   513  
   514  // Assumes that each node name is unique. Good enough for a test.
   515  // If clear is true, any incoming error is cleared before return. The errors
   516  // are always accumulated, though.
   517  func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
   518  	name := d.Name()
   519  	walkTree(tree, tree.name, func(path string, n *Node) {
   520  		if n.name == name {
   521  			n.mark++
   522  		}
   523  	})
   524  	if err != nil {
   525  		*errors = append(*errors, err)
   526  		if clear {
   527  			return nil
   528  		}
   529  		return err
   530  	}
   531  	return nil
   532  }
   533  
   534  // chdir changes the current working directory to the named directory,
   535  // and then restore the original working directory at the end of the test.
   536  func chdir(t *testing.T, dir string) {
   537  	olddir, err := os.Getwd()
   538  	if err != nil {
   539  		t.Fatalf("getwd %s: %v", dir, err)
   540  	}
   541  	if err := os.Chdir(dir); err != nil {
   542  		t.Fatalf("chdir %s: %v", dir, err)
   543  	}
   544  
   545  	t.Cleanup(func() {
   546  		if err := os.Chdir(olddir); err != nil {
   547  			t.Errorf("restore original working directory %s: %v", olddir, err)
   548  			os.Exit(1)
   549  		}
   550  	})
   551  }
   552  
   553  func chtmpdir(t *testing.T) (restore func()) {
   554  	oldwd, err := os.Getwd()
   555  	if err != nil {
   556  		t.Fatalf("chtmpdir: %v", err)
   557  	}
   558  	d, err := os.MkdirTemp("", "test")
   559  	if err != nil {
   560  		t.Fatalf("chtmpdir: %v", err)
   561  	}
   562  	if err := os.Chdir(d); err != nil {
   563  		t.Fatalf("chtmpdir: %v", err)
   564  	}
   565  	return func() {
   566  		if err := os.Chdir(oldwd); err != nil {
   567  			t.Fatalf("chtmpdir: %v", err)
   568  		}
   569  		os.RemoveAll(d)
   570  	}
   571  }
   572  
   573  // tempDirCanonical returns a temporary directory for the test to use, ensuring
   574  // that the returned path does not contain symlinks.
   575  func tempDirCanonical(t *testing.T) string {
   576  	dir := t.TempDir()
   577  
   578  	cdir, err := filepath.EvalSymlinks(dir)
   579  	if err != nil {
   580  		t.Errorf("tempDirCanonical: %v", err)
   581  	}
   582  
   583  	return cdir
   584  }
   585  
   586  func TestWalk(t *testing.T) {
   587  	walk := func(root string, fn fs.WalkDirFunc) error {
   588  		return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
   589  			return fn(path, &statDirEntry{info}, err)
   590  		})
   591  	}
   592  	testWalk(t, walk, 1)
   593  }
   594  
   595  type statDirEntry struct {
   596  	info fs.FileInfo
   597  }
   598  
   599  func (d *statDirEntry) Name() string               { return d.info.Name() }
   600  func (d *statDirEntry) IsDir() bool                { return d.info.IsDir() }
   601  func (d *statDirEntry) Type() fs.FileMode          { return d.info.Mode().Type() }
   602  func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
   603  
   604  func (d *statDirEntry) String() string {
   605  	return fs.FormatDirEntry(d)
   606  }
   607  
   608  func TestWalkDir(t *testing.T) {
   609  	testWalk(t, filepath.WalkDir, 2)
   610  }
   611  
   612  func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
   613  	if runtime.GOOS == "ios" {
   614  		restore := chtmpdir(t)
   615  		defer restore()
   616  	}
   617  
   618  	tmpDir := t.TempDir()
   619  
   620  	origDir, err := os.Getwd()
   621  	if err != nil {
   622  		t.Fatal("finding working dir:", err)
   623  	}
   624  	if err = os.Chdir(tmpDir); err != nil {
   625  		t.Fatal("entering temp dir:", err)
   626  	}
   627  	defer os.Chdir(origDir)
   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 !reflect.DeepEqual(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  	cwd, err := os.Getwd()
  1176  	if err != nil {
  1177  		t.Fatal(err)
  1178  	}
  1179  	defer func() {
  1180  		err := os.Chdir(cwd)
  1181  		if err != nil {
  1182  			t.Fatal(err)
  1183  		}
  1184  	}()
  1185  
  1186  	err = os.Chdir(wd)
  1187  	if err != nil {
  1188  		t.Fatal(err)
  1189  	}
  1190  
  1191  	have, err := filepath.EvalSymlinks(path)
  1192  	if err != nil {
  1193  		t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
  1194  		return
  1195  	}
  1196  	if filepath.Clean(have) != filepath.Clean(want) {
  1197  		t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
  1198  	}
  1199  }
  1200  
  1201  func TestEvalSymlinks(t *testing.T) {
  1202  	testenv.MustHaveSymlink(t)
  1203  
  1204  	tmpDir := t.TempDir()
  1205  
  1206  	// /tmp may itself be a symlink! Avoid the confusion, although
  1207  	// it means trusting the thing we're testing.
  1208  	var err error
  1209  	tmpDir, err = filepath.EvalSymlinks(tmpDir)
  1210  	if err != nil {
  1211  		t.Fatal("eval symlink for tmp dir:", err)
  1212  	}
  1213  
  1214  	// Create the symlink farm using relative paths.
  1215  	for _, d := range EvalSymlinksTestDirs {
  1216  		var err error
  1217  		path := simpleJoin(tmpDir, d.path)
  1218  		if d.dest == "" {
  1219  			err = os.Mkdir(path, 0755)
  1220  		} else {
  1221  			err = os.Symlink(d.dest, path)
  1222  		}
  1223  		if err != nil {
  1224  			t.Fatal(err)
  1225  		}
  1226  	}
  1227  
  1228  	// Evaluate the symlink farm.
  1229  	for _, test := range EvalSymlinksTests {
  1230  		path := simpleJoin(tmpDir, test.path)
  1231  
  1232  		dest := simpleJoin(tmpDir, test.dest)
  1233  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1234  			dest = test.dest
  1235  		}
  1236  		testEvalSymlinks(t, path, dest)
  1237  
  1238  		// test EvalSymlinks(".")
  1239  		testEvalSymlinksAfterChdir(t, path, ".", ".")
  1240  
  1241  		// test EvalSymlinks("C:.") on Windows
  1242  		if runtime.GOOS == "windows" {
  1243  			volDot := filepath.VolumeName(tmpDir) + "."
  1244  			testEvalSymlinksAfterChdir(t, path, volDot, volDot)
  1245  		}
  1246  
  1247  		// test EvalSymlinks(".."+path)
  1248  		dotdotPath := simpleJoin("..", test.dest)
  1249  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1250  			dotdotPath = test.dest
  1251  		}
  1252  		testEvalSymlinksAfterChdir(t,
  1253  			simpleJoin(tmpDir, "test"),
  1254  			simpleJoin("..", test.path),
  1255  			dotdotPath)
  1256  
  1257  		// test EvalSymlinks(p) where p is relative path
  1258  		testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
  1259  	}
  1260  }
  1261  
  1262  func TestEvalSymlinksIsNotExist(t *testing.T) {
  1263  	testenv.MustHaveSymlink(t)
  1264  
  1265  	defer chtmpdir(t)()
  1266  
  1267  	_, err := filepath.EvalSymlinks("notexist")
  1268  	if !os.IsNotExist(err) {
  1269  		t.Errorf("expected the file is not found, got %v\n", err)
  1270  	}
  1271  
  1272  	err = os.Symlink("notexist", "link")
  1273  	if err != nil {
  1274  		t.Fatal(err)
  1275  	}
  1276  	defer os.Remove("link")
  1277  
  1278  	_, err = filepath.EvalSymlinks("link")
  1279  	if !os.IsNotExist(err) {
  1280  		t.Errorf("expected the file is not found, got %v\n", err)
  1281  	}
  1282  }
  1283  
  1284  func TestIssue13582(t *testing.T) {
  1285  	testenv.MustHaveSymlink(t)
  1286  
  1287  	tmpDir := t.TempDir()
  1288  
  1289  	dir := filepath.Join(tmpDir, "dir")
  1290  	err := os.Mkdir(dir, 0755)
  1291  	if err != nil {
  1292  		t.Fatal(err)
  1293  	}
  1294  	linkToDir := filepath.Join(tmpDir, "link_to_dir")
  1295  	err = os.Symlink(dir, linkToDir)
  1296  	if err != nil {
  1297  		t.Fatal(err)
  1298  	}
  1299  	file := filepath.Join(linkToDir, "file")
  1300  	err = os.WriteFile(file, nil, 0644)
  1301  	if err != nil {
  1302  		t.Fatal(err)
  1303  	}
  1304  	link1 := filepath.Join(linkToDir, "link1")
  1305  	err = os.Symlink(file, link1)
  1306  	if err != nil {
  1307  		t.Fatal(err)
  1308  	}
  1309  	link2 := filepath.Join(linkToDir, "link2")
  1310  	err = os.Symlink(link1, link2)
  1311  	if err != nil {
  1312  		t.Fatal(err)
  1313  	}
  1314  
  1315  	// /tmp may itself be a symlink!
  1316  	realTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1317  	if err != nil {
  1318  		t.Fatal(err)
  1319  	}
  1320  	realDir := filepath.Join(realTmpDir, "dir")
  1321  	realFile := filepath.Join(realDir, "file")
  1322  
  1323  	tests := []struct {
  1324  		path, want string
  1325  	}{
  1326  		{dir, realDir},
  1327  		{linkToDir, realDir},
  1328  		{file, realFile},
  1329  		{link1, realFile},
  1330  		{link2, realFile},
  1331  	}
  1332  	for i, test := range tests {
  1333  		have, err := filepath.EvalSymlinks(test.path)
  1334  		if err != nil {
  1335  			t.Fatal(err)
  1336  		}
  1337  		if have != test.want {
  1338  			t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
  1339  		}
  1340  	}
  1341  }
  1342  
  1343  // Issue 57905.
  1344  func TestRelativeSymlinkToAbsolute(t *testing.T) {
  1345  	testenv.MustHaveSymlink(t)
  1346  	// Not parallel: uses os.Chdir.
  1347  
  1348  	tmpDir := t.TempDir()
  1349  	chdir(t, tmpDir)
  1350  
  1351  	// Create "link" in the current working directory as a symlink to an arbitrary
  1352  	// absolute path. On macOS, this path is likely to begin with a symlink
  1353  	// itself: generally either in /var (symlinked to "private/var") or /tmp
  1354  	// (symlinked to "private/tmp").
  1355  	if err := os.Symlink(tmpDir, "link"); err != nil {
  1356  		t.Fatal(err)
  1357  	}
  1358  	t.Logf(`os.Symlink(%q, "link")`, tmpDir)
  1359  
  1360  	p, err := filepath.EvalSymlinks("link")
  1361  	if err != nil {
  1362  		t.Fatalf(`EvalSymlinks("link"): %v`, err)
  1363  	}
  1364  	want, err := filepath.EvalSymlinks(tmpDir)
  1365  	if err != nil {
  1366  		t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
  1367  	}
  1368  	if p != want {
  1369  		t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
  1370  	}
  1371  	t.Logf(`EvalSymlinks("link") = %q`, p)
  1372  }
  1373  
  1374  // Test directories relative to temporary directory.
  1375  // The tests are run in absTestDirs[0].
  1376  var absTestDirs = []string{
  1377  	"a",
  1378  	"a/b",
  1379  	"a/b/c",
  1380  }
  1381  
  1382  // Test paths relative to temporary directory. $ expands to the directory.
  1383  // The tests are run in absTestDirs[0].
  1384  // We create absTestDirs first.
  1385  var absTests = []string{
  1386  	".",
  1387  	"b",
  1388  	"b/",
  1389  	"../a",
  1390  	"../a/b",
  1391  	"../a/b/./c/../../.././a",
  1392  	"../a/b/./c/../../.././a/",
  1393  	"$",
  1394  	"$/.",
  1395  	"$/a/../a/b",
  1396  	"$/a/b/c/../../.././a",
  1397  	"$/a/b/c/../../.././a/",
  1398  }
  1399  
  1400  func TestAbs(t *testing.T) {
  1401  	root := t.TempDir()
  1402  	wd, err := os.Getwd()
  1403  	if err != nil {
  1404  		t.Fatal("getwd failed: ", err)
  1405  	}
  1406  	err = os.Chdir(root)
  1407  	if err != nil {
  1408  		t.Fatal("chdir failed: ", err)
  1409  	}
  1410  	defer os.Chdir(wd)
  1411  
  1412  	for _, dir := range absTestDirs {
  1413  		err = os.Mkdir(dir, 0777)
  1414  		if err != nil {
  1415  			t.Fatal("Mkdir failed: ", err)
  1416  		}
  1417  	}
  1418  
  1419  	if runtime.GOOS == "windows" {
  1420  		vol := filepath.VolumeName(root)
  1421  		var extra []string
  1422  		for _, path := range absTests {
  1423  			if strings.Contains(path, "$") {
  1424  				continue
  1425  			}
  1426  			path = vol + path
  1427  			extra = append(extra, path)
  1428  		}
  1429  		absTests = append(absTests, extra...)
  1430  	}
  1431  
  1432  	err = os.Chdir(absTestDirs[0])
  1433  	if err != nil {
  1434  		t.Fatal("chdir failed: ", err)
  1435  	}
  1436  
  1437  	for _, path := range absTests {
  1438  		path = strings.ReplaceAll(path, "$", root)
  1439  		info, err := os.Stat(path)
  1440  		if err != nil {
  1441  			t.Errorf("%s: %s", path, err)
  1442  			continue
  1443  		}
  1444  
  1445  		abspath, err := filepath.Abs(path)
  1446  		if err != nil {
  1447  			t.Errorf("Abs(%q) error: %v", path, err)
  1448  			continue
  1449  		}
  1450  		absinfo, err := os.Stat(abspath)
  1451  		if err != nil || !os.SameFile(absinfo, info) {
  1452  			t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
  1453  		}
  1454  		if !filepath.IsAbs(abspath) {
  1455  			t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
  1456  		}
  1457  		if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1458  			t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
  1459  		}
  1460  	}
  1461  }
  1462  
  1463  // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
  1464  // We test it separately from all other absTests because the empty string is not
  1465  // a valid path, so it can't be used with os.Stat.
  1466  func TestAbsEmptyString(t *testing.T) {
  1467  	root := t.TempDir()
  1468  
  1469  	wd, err := os.Getwd()
  1470  	if err != nil {
  1471  		t.Fatal("getwd failed: ", err)
  1472  	}
  1473  	err = os.Chdir(root)
  1474  	if err != nil {
  1475  		t.Fatal("chdir failed: ", err)
  1476  	}
  1477  	defer os.Chdir(wd)
  1478  
  1479  	info, err := os.Stat(root)
  1480  	if err != nil {
  1481  		t.Fatalf("%s: %s", root, err)
  1482  	}
  1483  
  1484  	abspath, err := filepath.Abs("")
  1485  	if err != nil {
  1486  		t.Fatalf(`Abs("") error: %v`, err)
  1487  	}
  1488  	absinfo, err := os.Stat(abspath)
  1489  	if err != nil || !os.SameFile(absinfo, info) {
  1490  		t.Errorf(`Abs("")=%q, not the same file`, abspath)
  1491  	}
  1492  	if !filepath.IsAbs(abspath) {
  1493  		t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
  1494  	}
  1495  	if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1496  		t.Errorf(`Abs("")=%q, isn't clean`, abspath)
  1497  	}
  1498  }
  1499  
  1500  type RelTests struct {
  1501  	root, path, want string
  1502  }
  1503  
  1504  var reltests = []RelTests{
  1505  	{"a/b", "a/b", "."},
  1506  	{"a/b/.", "a/b", "."},
  1507  	{"a/b", "a/b/.", "."},
  1508  	{"./a/b", "a/b", "."},
  1509  	{"a/b", "./a/b", "."},
  1510  	{"ab/cd", "ab/cde", "../cde"},
  1511  	{"ab/cd", "ab/c", "../c"},
  1512  	{"a/b", "a/b/c/d", "c/d"},
  1513  	{"a/b", "a/b/../c", "../c"},
  1514  	{"a/b/../c", "a/b", "../b"},
  1515  	{"a/b/c", "a/c/d", "../../c/d"},
  1516  	{"a/b", "c/d", "../../c/d"},
  1517  	{"a/b/c/d", "a/b", "../.."},
  1518  	{"a/b/c/d", "a/b/", "../.."},
  1519  	{"a/b/c/d/", "a/b", "../.."},
  1520  	{"a/b/c/d/", "a/b/", "../.."},
  1521  	{"../../a/b", "../../a/b/c/d", "c/d"},
  1522  	{"/a/b", "/a/b", "."},
  1523  	{"/a/b/.", "/a/b", "."},
  1524  	{"/a/b", "/a/b/.", "."},
  1525  	{"/ab/cd", "/ab/cde", "../cde"},
  1526  	{"/ab/cd", "/ab/c", "../c"},
  1527  	{"/a/b", "/a/b/c/d", "c/d"},
  1528  	{"/a/b", "/a/b/../c", "../c"},
  1529  	{"/a/b/../c", "/a/b", "../b"},
  1530  	{"/a/b/c", "/a/c/d", "../../c/d"},
  1531  	{"/a/b", "/c/d", "../../c/d"},
  1532  	{"/a/b/c/d", "/a/b", "../.."},
  1533  	{"/a/b/c/d", "/a/b/", "../.."},
  1534  	{"/a/b/c/d/", "/a/b", "../.."},
  1535  	{"/a/b/c/d/", "/a/b/", "../.."},
  1536  	{"/../../a/b", "/../../a/b/c/d", "c/d"},
  1537  	{".", "a/b", "a/b"},
  1538  	{".", "..", ".."},
  1539  
  1540  	// can't do purely lexically
  1541  	{"..", ".", "err"},
  1542  	{"..", "a", "err"},
  1543  	{"../..", "..", "err"},
  1544  	{"a", "/a", "err"},
  1545  	{"/a", "a", "err"},
  1546  }
  1547  
  1548  var winreltests = []RelTests{
  1549  	{`C:a\b\c`, `C:a/b/d`, `..\d`},
  1550  	{`C:\`, `D:\`, `err`},
  1551  	{`C:`, `D:`, `err`},
  1552  	{`C:\Projects`, `c:\projects\src`, `src`},
  1553  	{`C:\Projects`, `c:\projects`, `.`},
  1554  	{`C:\Projects\a\..`, `c:\projects`, `.`},
  1555  	{`\\host\share`, `\\host\share\file.txt`, `file.txt`},
  1556  }
  1557  
  1558  func TestRel(t *testing.T) {
  1559  	tests := append([]RelTests{}, reltests...)
  1560  	if runtime.GOOS == "windows" {
  1561  		for i := range tests {
  1562  			tests[i].want = filepath.FromSlash(tests[i].want)
  1563  		}
  1564  		tests = append(tests, winreltests...)
  1565  	}
  1566  	for _, test := range tests {
  1567  		got, err := filepath.Rel(test.root, test.path)
  1568  		if test.want == "err" {
  1569  			if err == nil {
  1570  				t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
  1571  			}
  1572  			continue
  1573  		}
  1574  		if err != nil {
  1575  			t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
  1576  		}
  1577  		if got != test.want {
  1578  			t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
  1579  		}
  1580  	}
  1581  }
  1582  
  1583  type VolumeNameTest struct {
  1584  	path string
  1585  	vol  string
  1586  }
  1587  
  1588  var volumenametests = []VolumeNameTest{
  1589  	{`c:/foo/bar`, `c:`},
  1590  	{`c:`, `c:`},
  1591  	{`c:\`, `c:`},
  1592  	{`2:`, `2:`},
  1593  	{``, ``},
  1594  	{`\\\host`, `\\\host`},
  1595  	{`\\\host\`, `\\\host`},
  1596  	{`\\\host\share`, `\\\host`},
  1597  	{`\\\host\\share`, `\\\host`},
  1598  	{`\\host`, `\\host`},
  1599  	{`//host`, `\\host`},
  1600  	{`\\host\`, `\\host\`},
  1601  	{`//host/`, `\\host\`},
  1602  	{`\\host\share`, `\\host\share`},
  1603  	{`//host/share`, `\\host\share`},
  1604  	{`\\host\share\`, `\\host\share`},
  1605  	{`//host/share/`, `\\host\share`},
  1606  	{`\\host\share\foo`, `\\host\share`},
  1607  	{`//host/share/foo`, `\\host\share`},
  1608  	{`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
  1609  	{`//host/share//foo///bar////baz`, `\\host\share`},
  1610  	{`\\host\share\foo\..\bar`, `\\host\share`},
  1611  	{`//host/share/foo/../bar`, `\\host\share`},
  1612  	{`//.`, `\\.`},
  1613  	{`//./`, `\\.\`},
  1614  	{`//./NUL`, `\\.\NUL`},
  1615  	{`//?`, `\\?`},
  1616  	{`//?/`, `\\?\`},
  1617  	{`//?/NUL`, `\\?\NUL`},
  1618  	{`/??`, `\??`},
  1619  	{`/??/`, `\??\`},
  1620  	{`/??/NUL`, `\??\NUL`},
  1621  	{`//./a/b`, `\\.\a`},
  1622  	{`//./C:`, `\\.\C:`},
  1623  	{`//./C:/`, `\\.\C:`},
  1624  	{`//./C:/a/b/c`, `\\.\C:`},
  1625  	{`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
  1626  	{`//./UNC/host`, `\\.\UNC\host`},
  1627  	{`//./UNC/host\`, `\\.\UNC\host\`},
  1628  	{`//./UNC`, `\\.\UNC`},
  1629  	{`//./UNC/`, `\\.\UNC\`},
  1630  	{`\\?\x`, `\\?\x`},
  1631  	{`\??\x`, `\??\x`},
  1632  }
  1633  
  1634  func TestVolumeName(t *testing.T) {
  1635  	if runtime.GOOS != "windows" {
  1636  		return
  1637  	}
  1638  	for _, v := range volumenametests {
  1639  		if vol := filepath.VolumeName(v.path); vol != v.vol {
  1640  			t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
  1641  		}
  1642  	}
  1643  }
  1644  
  1645  func TestDriveLetterInEvalSymlinks(t *testing.T) {
  1646  	if runtime.GOOS != "windows" {
  1647  		return
  1648  	}
  1649  	wd, _ := os.Getwd()
  1650  	if len(wd) < 3 {
  1651  		t.Errorf("Current directory path %q is too short", wd)
  1652  	}
  1653  	lp := strings.ToLower(wd)
  1654  	up := strings.ToUpper(wd)
  1655  	flp, err := filepath.EvalSymlinks(lp)
  1656  	if err != nil {
  1657  		t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
  1658  	}
  1659  	fup, err := filepath.EvalSymlinks(up)
  1660  	if err != nil {
  1661  		t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
  1662  	}
  1663  	if flp != fup {
  1664  		t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
  1665  	}
  1666  }
  1667  
  1668  func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
  1669  	if runtime.GOOS == "ios" {
  1670  		t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
  1671  	}
  1672  	root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
  1673  	utf16 := filepath.Join(root, "utf16")
  1674  	utf8 := filepath.Join(root, "utf8")
  1675  	seenUTF16 := false
  1676  	seenUTF8 := false
  1677  	err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
  1678  		if err != nil {
  1679  			t.Fatal(err)
  1680  		}
  1681  
  1682  		switch pth {
  1683  		case utf16:
  1684  			seenUTF16 = true
  1685  			return filepath.SkipDir
  1686  		case utf8:
  1687  			if !seenUTF16 {
  1688  				t.Fatal("filepath.Walk out of order - utf8 before utf16")
  1689  			}
  1690  			seenUTF8 = true
  1691  		}
  1692  		return nil
  1693  	})
  1694  	if err != nil {
  1695  		t.Fatal(err)
  1696  	}
  1697  	if !seenUTF8 {
  1698  		t.Fatalf("%q not seen", utf8)
  1699  	}
  1700  }
  1701  
  1702  func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
  1703  	tmpdir := t.TempDir()
  1704  
  1705  	wd, err := os.Getwd()
  1706  	if err != nil {
  1707  		t.Fatal(err)
  1708  	}
  1709  	defer os.Chdir(wd)
  1710  
  1711  	err = os.Chdir(tmpdir)
  1712  	if err != nil {
  1713  		t.Fatal(err)
  1714  	}
  1715  
  1716  	err = mklink(tmpdir, "link")
  1717  	if err != nil {
  1718  		t.Fatal(err)
  1719  	}
  1720  
  1721  	var visited []string
  1722  	err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
  1723  		if err != nil {
  1724  			t.Fatal(err)
  1725  		}
  1726  		rel, err := filepath.Rel(tmpdir, path)
  1727  		if err != nil {
  1728  			t.Fatal(err)
  1729  		}
  1730  		visited = append(visited, rel)
  1731  		return nil
  1732  	})
  1733  	if err != nil {
  1734  		t.Fatal(err)
  1735  	}
  1736  	sort.Strings(visited)
  1737  	want := []string{".", "link"}
  1738  	if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
  1739  		t.Errorf("unexpected paths visited %q, want %q", visited, want)
  1740  	}
  1741  }
  1742  
  1743  func TestWalkSymlink(t *testing.T) {
  1744  	testenv.MustHaveSymlink(t)
  1745  	testWalkSymlink(t, os.Symlink)
  1746  }
  1747  
  1748  func TestIssue29372(t *testing.T) {
  1749  	tmpDir := t.TempDir()
  1750  
  1751  	path := filepath.Join(tmpDir, "file.txt")
  1752  	err := os.WriteFile(path, nil, 0644)
  1753  	if err != nil {
  1754  		t.Fatal(err)
  1755  	}
  1756  
  1757  	pathSeparator := string(filepath.Separator)
  1758  	tests := []string{
  1759  		path + strings.Repeat(pathSeparator, 1),
  1760  		path + strings.Repeat(pathSeparator, 2),
  1761  		path + strings.Repeat(pathSeparator, 1) + ".",
  1762  		path + strings.Repeat(pathSeparator, 2) + ".",
  1763  		path + strings.Repeat(pathSeparator, 1) + "..",
  1764  		path + strings.Repeat(pathSeparator, 2) + "..",
  1765  	}
  1766  
  1767  	for i, test := range tests {
  1768  		_, err = filepath.EvalSymlinks(test)
  1769  		if err != syscall.ENOTDIR {
  1770  			t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
  1771  		}
  1772  	}
  1773  }
  1774  
  1775  // Issue 30520 part 1.
  1776  func TestEvalSymlinksAboveRoot(t *testing.T) {
  1777  	testenv.MustHaveSymlink(t)
  1778  
  1779  	t.Parallel()
  1780  
  1781  	tmpDir := t.TempDir()
  1782  
  1783  	evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1784  	if err != nil {
  1785  		t.Fatal(err)
  1786  	}
  1787  
  1788  	if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
  1789  		t.Fatal(err)
  1790  	}
  1791  	if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
  1792  		t.Fatal(err)
  1793  	}
  1794  	if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
  1795  		t.Fatal(err)
  1796  	}
  1797  
  1798  	// Count the number of ".." elements to get to the root directory.
  1799  	vol := filepath.VolumeName(evalTmpDir)
  1800  	c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
  1801  	var dd []string
  1802  	for i := 0; i < c+2; i++ {
  1803  		dd = append(dd, "..")
  1804  	}
  1805  
  1806  	wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
  1807  
  1808  	// Try different numbers of "..".
  1809  	for _, i := range []int{c, c + 1, c + 2} {
  1810  		check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
  1811  		resolved, err := filepath.EvalSymlinks(check)
  1812  		switch {
  1813  		case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
  1814  			// On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
  1815  			testenv.SkipFlaky(t, 37910)
  1816  		case err != nil:
  1817  			t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1818  		case !strings.HasSuffix(resolved, wantSuffix):
  1819  			t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1820  		default:
  1821  			t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1822  		}
  1823  	}
  1824  }
  1825  
  1826  // Issue 30520 part 2.
  1827  func TestEvalSymlinksAboveRootChdir(t *testing.T) {
  1828  	testenv.MustHaveSymlink(t)
  1829  
  1830  	tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
  1831  	if err != nil {
  1832  		t.Fatal(err)
  1833  	}
  1834  	defer os.RemoveAll(tmpDir)
  1835  	chdir(t, tmpDir)
  1836  
  1837  	subdir := filepath.Join("a", "b")
  1838  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1839  		t.Fatal(err)
  1840  	}
  1841  	if err := os.Symlink(subdir, "c"); err != nil {
  1842  		t.Fatal(err)
  1843  	}
  1844  	if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
  1845  		t.Fatal(err)
  1846  	}
  1847  
  1848  	subdir = filepath.Join("d", "e", "f")
  1849  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1850  		t.Fatal(err)
  1851  	}
  1852  	if err := os.Chdir(subdir); err != nil {
  1853  		t.Fatal(err)
  1854  	}
  1855  
  1856  	check := filepath.Join("..", "..", "..", "c", "file")
  1857  	wantSuffix := filepath.Join("a", "b", "file")
  1858  	if resolved, err := filepath.EvalSymlinks(check); err != nil {
  1859  		t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1860  	} else if !strings.HasSuffix(resolved, wantSuffix) {
  1861  		t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1862  	} else {
  1863  		t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1864  	}
  1865  }
  1866  
  1867  func TestIssue51617(t *testing.T) {
  1868  	dir := t.TempDir()
  1869  	for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
  1870  		if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
  1871  			t.Fatal(err)
  1872  		}
  1873  	}
  1874  	bad := filepath.Join(dir, "a", "bad")
  1875  	if err := os.Chmod(bad, 0); err != nil {
  1876  		t.Fatal(err)
  1877  	}
  1878  	defer os.Chmod(bad, 0700) // avoid errors on cleanup
  1879  	var saw []string
  1880  	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
  1881  		if err != nil {
  1882  			return filepath.SkipDir
  1883  		}
  1884  		if d.IsDir() {
  1885  			rel, err := filepath.Rel(dir, path)
  1886  			if err != nil {
  1887  				t.Fatal(err)
  1888  			}
  1889  			saw = append(saw, rel)
  1890  		}
  1891  		return nil
  1892  	})
  1893  	if err != nil {
  1894  		t.Fatal(err)
  1895  	}
  1896  	want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
  1897  	if !reflect.DeepEqual(saw, want) {
  1898  		t.Errorf("got directories %v, want %v", saw, want)
  1899  	}
  1900  }
  1901  
  1902  func TestEscaping(t *testing.T) {
  1903  	dir1 := t.TempDir()
  1904  	dir2 := t.TempDir()
  1905  	chdir(t, dir1)
  1906  
  1907  	for _, p := range []string{
  1908  		filepath.Join(dir2, "x"),
  1909  	} {
  1910  		if !filepath.IsLocal(p) {
  1911  			continue
  1912  		}
  1913  		f, err := os.Create(p)
  1914  		if err != nil {
  1915  			f.Close()
  1916  		}
  1917  		ents, err := os.ReadDir(dir2)
  1918  		if err != nil {
  1919  			t.Fatal(err)
  1920  		}
  1921  		for _, e := range ents {
  1922  			t.Fatalf("found: %v", e.Name())
  1923  		}
  1924  	}
  1925  }
  1926  

View as plain text