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

View as plain text