...

Source file src/path/filepath/path_test.go

Documentation: path/filepath

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package filepath_test
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"io/fs"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"slices"
    17  	"strings"
    18  	"syscall"
    19  	"testing"
    20  )
    21  
    22  type PathTest struct {
    23  	path, result string
    24  }
    25  
    26  var cleantests = []PathTest{
    27  	// Already clean
    28  	{"abc", "abc"},
    29  	{"abc/def", "abc/def"},
    30  	{"a/b/c", "a/b/c"},
    31  	{".", "."},
    32  	{"..", ".."},
    33  	{"../..", "../.."},
    34  	{"../../abc", "../../abc"},
    35  	{"/abc", "/abc"},
    36  	{"/", "/"},
    37  
    38  	// Empty is current dir
    39  	{"", "."},
    40  
    41  	// Remove trailing slash
    42  	{"abc/", "abc"},
    43  	{"abc/def/", "abc/def"},
    44  	{"a/b/c/", "a/b/c"},
    45  	{"./", "."},
    46  	{"../", ".."},
    47  	{"../../", "../.."},
    48  	{"/abc/", "/abc"},
    49  
    50  	// Remove doubled slash
    51  	{"abc//def//ghi", "abc/def/ghi"},
    52  	{"abc//", "abc"},
    53  
    54  	// Remove . elements
    55  	{"abc/./def", "abc/def"},
    56  	{"/./abc/def", "/abc/def"},
    57  	{"abc/.", "abc"},
    58  
    59  	// Remove .. elements
    60  	{"abc/def/ghi/../jkl", "abc/def/jkl"},
    61  	{"abc/def/../ghi/../jkl", "abc/jkl"},
    62  	{"abc/def/..", "abc"},
    63  	{"abc/def/../..", "."},
    64  	{"/abc/def/../..", "/"},
    65  	{"abc/def/../../..", ".."},
    66  	{"/abc/def/../../..", "/"},
    67  	{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
    68  	{"/../abc", "/abc"},
    69  	{"a/../b:/../../c", `../c`},
    70  
    71  	// Combinations
    72  	{"abc/./../def", "def"},
    73  	{"abc//./../def", "def"},
    74  	{"abc/../../././../def", "../../def"},
    75  }
    76  
    77  var nonwincleantests = []PathTest{
    78  	// Remove leading doubled slash
    79  	{"//abc", "/abc"},
    80  	{"///abc", "/abc"},
    81  	{"//abc//", "/abc"},
    82  }
    83  
    84  var wincleantests = []PathTest{
    85  	{`c:`, `c:.`},
    86  	{`c:\`, `c:\`},
    87  	{`c:\abc`, `c:\abc`},
    88  	{`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
    89  	{`c:\abc\def\..\..`, `c:\`},
    90  	{`c:\..\abc`, `c:\abc`},
    91  	{`c:..\abc`, `c:..\abc`},
    92  	{`c:\b:\..\..\..\d`, `c:\d`},
    93  	{`\`, `\`},
    94  	{`/`, `\`},
    95  	{`\\i\..\c$`, `\\i\..\c$`},
    96  	{`\\i\..\i\c$`, `\\i\..\i\c$`},
    97  	{`\\i\..\I\c$`, `\\i\..\I\c$`},
    98  	{`\\host\share\foo\..\bar`, `\\host\share\bar`},
    99  	{`//host/share/foo/../baz`, `\\host\share\baz`},
   100  	{`\\host\share\foo\..\..\..\..\bar`, `\\host\share\bar`},
   101  	{`\\.\C:\a\..\..\..\..\bar`, `\\.\C:\bar`},
   102  	{`\\.\C:\\\\a`, `\\.\C:\a`},
   103  	{`\\a\b\..\c`, `\\a\b\c`},
   104  	{`\\a\b`, `\\a\b`},
   105  	{`.\c:`, `.\c:`},
   106  	{`.\c:\foo`, `.\c:\foo`},
   107  	{`.\c:foo`, `.\c:foo`},
   108  	{`//abc`, `\\abc`},
   109  	{`///abc`, `\\\abc`},
   110  	{`//abc//`, `\\abc\\`},
   111  	{`\\?\C:\`, `\\?\C:\`},
   112  	{`\\?\C:\a`, `\\?\C:\a`},
   113  
   114  	// Don't allow cleaning to move an element with a colon to the start of the path.
   115  	{`a/../c:`, `.\c:`},
   116  	{`a\..\c:`, `.\c:`},
   117  	{`a/../c:/a`, `.\c:\a`},
   118  	{`a/../../c:`, `..\c:`},
   119  	{`foo:bar`, `foo:bar`},
   120  
   121  	// Don't allow cleaning to create a Root Local Device path like \??\a.
   122  	{`/a/../??/a`, `\.\??\a`},
   123  }
   124  
   125  func TestClean(t *testing.T) {
   126  	tests := cleantests
   127  	if runtime.GOOS == "windows" {
   128  		for i := range tests {
   129  			tests[i].result = filepath.FromSlash(tests[i].result)
   130  		}
   131  		tests = append(tests, wincleantests...)
   132  	} else {
   133  		tests = append(tests, nonwincleantests...)
   134  	}
   135  	for _, test := range tests {
   136  		if s := filepath.Clean(test.path); s != test.result {
   137  			t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
   138  		}
   139  		if s := filepath.Clean(test.result); s != test.result {
   140  			t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
   141  		}
   142  	}
   143  
   144  	if testing.Short() {
   145  		t.Skip("skipping malloc count in short mode")
   146  	}
   147  	if runtime.GOMAXPROCS(0) > 1 {
   148  		t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
   149  		return
   150  	}
   151  
   152  	for _, test := range tests {
   153  		allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
   154  		if allocs > 0 {
   155  			t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
   156  		}
   157  	}
   158  }
   159  
   160  type IsLocalTest struct {
   161  	path    string
   162  	isLocal bool
   163  }
   164  
   165  var islocaltests = []IsLocalTest{
   166  	{"", false},
   167  	{".", true},
   168  	{"..", false},
   169  	{"../a", false},
   170  	{"/", false},
   171  	{"/a", false},
   172  	{"/a/../..", false},
   173  	{"a", true},
   174  	{"a/../a", true},
   175  	{"a/", true},
   176  	{"a/.", true},
   177  	{"a/./b/./c", true},
   178  	{`a/../b:/../../c`, false},
   179  }
   180  
   181  var winislocaltests = []IsLocalTest{
   182  	{"NUL", false},
   183  	{"nul", false},
   184  	{"nul ", false},
   185  	{"nul.", false},
   186  	{"a/nul:", false},
   187  	{"a/nul : a", false},
   188  	{"com0", true},
   189  	{"com1", false},
   190  	{"com2", false},
   191  	{"com3", false},
   192  	{"com4", false},
   193  	{"com5", false},
   194  	{"com6", false},
   195  	{"com7", false},
   196  	{"com8", false},
   197  	{"com9", false},
   198  	{"com¹", false},
   199  	{"com²", false},
   200  	{"com³", false},
   201  	{"com¹ : a", false},
   202  	{"cOm1", false},
   203  	{"lpt1", false},
   204  	{"LPT1", false},
   205  	{"lpt³", false},
   206  	{"./nul", false},
   207  	{`\`, false},
   208  	{`\a`, false},
   209  	{`C:`, false},
   210  	{`C:\a`, false},
   211  	{`..\a`, false},
   212  	{`a/../c:`, false},
   213  	{`CONIN$`, false},
   214  	{`conin$`, false},
   215  	{`CONOUT$`, false},
   216  	{`conout$`, false},
   217  	{`dollar$`, true}, // not a special file name
   218  }
   219  
   220  var plan9islocaltests = []IsLocalTest{
   221  	{"#a", false},
   222  }
   223  
   224  func TestIsLocal(t *testing.T) {
   225  	tests := islocaltests
   226  	if runtime.GOOS == "windows" {
   227  		tests = append(tests, winislocaltests...)
   228  	}
   229  	if runtime.GOOS == "plan9" {
   230  		tests = append(tests, plan9islocaltests...)
   231  	}
   232  	for _, test := range tests {
   233  		if got := filepath.IsLocal(test.path); got != test.isLocal {
   234  			t.Errorf("IsLocal(%q) = %v, want %v", test.path, got, test.isLocal)
   235  		}
   236  	}
   237  }
   238  
   239  type LocalizeTest struct {
   240  	path string
   241  	want string
   242  }
   243  
   244  var localizetests = []LocalizeTest{
   245  	{"", ""},
   246  	{".", "."},
   247  	{"..", ""},
   248  	{"a/..", ""},
   249  	{"/", ""},
   250  	{"/a", ""},
   251  	{"a\xffb", ""},
   252  	{"a/", ""},
   253  	{"a/./b", ""},
   254  	{"\x00", ""},
   255  	{"a", "a"},
   256  	{"a/b/c", "a/b/c"},
   257  }
   258  
   259  var plan9localizetests = []LocalizeTest{
   260  	{"#a", ""},
   261  	{`a\b:c`, `a\b:c`},
   262  }
   263  
   264  var unixlocalizetests = []LocalizeTest{
   265  	{"#a", "#a"},
   266  	{`a\b:c`, `a\b:c`},
   267  }
   268  
   269  var winlocalizetests = []LocalizeTest{
   270  	{"#a", "#a"},
   271  	{"c:", ""},
   272  	{`a\b`, ""},
   273  	{`a:b`, ""},
   274  	{`a/b:c`, ""},
   275  	{`NUL`, ""},
   276  	{`a/NUL`, ""},
   277  	{`./com1`, ""},
   278  	{`a/nul/b`, ""},
   279  }
   280  
   281  func TestLocalize(t *testing.T) {
   282  	tests := localizetests
   283  	switch runtime.GOOS {
   284  	case "plan9":
   285  		tests = append(tests, plan9localizetests...)
   286  	case "windows":
   287  		tests = append(tests, winlocalizetests...)
   288  		for i := range tests {
   289  			tests[i].want = filepath.FromSlash(tests[i].want)
   290  		}
   291  	default:
   292  		tests = append(tests, unixlocalizetests...)
   293  	}
   294  	for _, test := range tests {
   295  		got, err := filepath.Localize(test.path)
   296  		wantErr := "<nil>"
   297  		if test.want == "" {
   298  			wantErr = "error"
   299  		}
   300  		if got != test.want || ((err == nil) != (test.want != "")) {
   301  			t.Errorf("IsLocal(%q) = %q, %v want %q, %v", test.path, got, err, test.want, wantErr)
   302  		}
   303  	}
   304  }
   305  
   306  const sep = filepath.Separator
   307  
   308  var slashtests = []PathTest{
   309  	{"", ""},
   310  	{"/", string(sep)},
   311  	{"/a/b", string([]byte{sep, 'a', sep, 'b'})},
   312  	{"a//b", string([]byte{'a', sep, sep, 'b'})},
   313  }
   314  
   315  func TestFromAndToSlash(t *testing.T) {
   316  	for _, test := range slashtests {
   317  		if s := filepath.FromSlash(test.path); s != test.result {
   318  			t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
   319  		}
   320  		if s := filepath.ToSlash(test.result); s != test.path {
   321  			t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
   322  		}
   323  	}
   324  }
   325  
   326  type SplitListTest struct {
   327  	list   string
   328  	result []string
   329  }
   330  
   331  const lsep = filepath.ListSeparator
   332  
   333  var splitlisttests = []SplitListTest{
   334  	{"", []string{}},
   335  	{string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
   336  	{string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
   337  }
   338  
   339  var winsplitlisttests = []SplitListTest{
   340  	// quoted
   341  	{`"a"`, []string{`a`}},
   342  
   343  	// semicolon
   344  	{`";"`, []string{`;`}},
   345  	{`"a;b"`, []string{`a;b`}},
   346  	{`";";`, []string{`;`, ``}},
   347  	{`;";"`, []string{``, `;`}},
   348  
   349  	// partially quoted
   350  	{`a";"b`, []string{`a;b`}},
   351  	{`a; ""b`, []string{`a`, ` b`}},
   352  	{`"a;b`, []string{`a;b`}},
   353  	{`""a;b`, []string{`a`, `b`}},
   354  	{`"""a;b`, []string{`a;b`}},
   355  	{`""""a;b`, []string{`a`, `b`}},
   356  	{`a";b`, []string{`a;b`}},
   357  	{`a;b";c`, []string{`a`, `b;c`}},
   358  	{`"a";b";c`, []string{`a`, `b;c`}},
   359  }
   360  
   361  func TestSplitList(t *testing.T) {
   362  	tests := splitlisttests
   363  	if runtime.GOOS == "windows" {
   364  		tests = append(tests, winsplitlisttests...)
   365  	}
   366  	for _, test := range tests {
   367  		if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
   368  			t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
   369  		}
   370  	}
   371  }
   372  
   373  type SplitTest struct {
   374  	path, dir, file string
   375  }
   376  
   377  var unixsplittests = []SplitTest{
   378  	{"a/b", "a/", "b"},
   379  	{"a/b/", "a/b/", ""},
   380  	{"a/", "a/", ""},
   381  	{"a", "", "a"},
   382  	{"/", "/", ""},
   383  }
   384  
   385  var winsplittests = []SplitTest{
   386  	{`c:`, `c:`, ``},
   387  	{`c:/`, `c:/`, ``},
   388  	{`c:/foo`, `c:/`, `foo`},
   389  	{`c:/foo/bar`, `c:/foo/`, `bar`},
   390  	{`//host/share`, `//host/share`, ``},
   391  	{`//host/share/`, `//host/share/`, ``},
   392  	{`//host/share/foo`, `//host/share/`, `foo`},
   393  	{`\\host\share`, `\\host\share`, ``},
   394  	{`\\host\share\`, `\\host\share\`, ``},
   395  	{`\\host\share\foo`, `\\host\share\`, `foo`},
   396  }
   397  
   398  func TestSplit(t *testing.T) {
   399  	var splittests []SplitTest
   400  	splittests = unixsplittests
   401  	if runtime.GOOS == "windows" {
   402  		splittests = append(splittests, winsplittests...)
   403  	}
   404  	for _, test := range splittests {
   405  		if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
   406  			t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
   407  		}
   408  	}
   409  }
   410  
   411  type JoinTest struct {
   412  	elem []string
   413  	path string
   414  }
   415  
   416  var jointests = []JoinTest{
   417  	// zero parameters
   418  	{[]string{}, ""},
   419  
   420  	// one parameter
   421  	{[]string{""}, ""},
   422  	{[]string{"/"}, "/"},
   423  	{[]string{"a"}, "a"},
   424  
   425  	// two parameters
   426  	{[]string{"a", "b"}, "a/b"},
   427  	{[]string{"a", ""}, "a"},
   428  	{[]string{"", "b"}, "b"},
   429  	{[]string{"/", "a"}, "/a"},
   430  	{[]string{"/", "a/b"}, "/a/b"},
   431  	{[]string{"/", ""}, "/"},
   432  	{[]string{"/a", "b"}, "/a/b"},
   433  	{[]string{"a", "/b"}, "a/b"},
   434  	{[]string{"/a", "/b"}, "/a/b"},
   435  	{[]string{"a/", "b"}, "a/b"},
   436  	{[]string{"a/", ""}, "a"},
   437  	{[]string{"", ""}, ""},
   438  
   439  	// three parameters
   440  	{[]string{"/", "a", "b"}, "/a/b"},
   441  }
   442  
   443  var nonwinjointests = []JoinTest{
   444  	{[]string{"//", "a"}, "/a"},
   445  }
   446  
   447  var winjointests = []JoinTest{
   448  	{[]string{`directory`, `file`}, `directory\file`},
   449  	{[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
   450  	{[]string{`C:\Windows\`, ``}, `C:\Windows`},
   451  	{[]string{`C:\`, `Windows`}, `C:\Windows`},
   452  	{[]string{`C:`, `a`}, `C:a`},
   453  	{[]string{`C:`, `a\b`}, `C:a\b`},
   454  	{[]string{`C:`, `a`, `b`}, `C:a\b`},
   455  	{[]string{`C:`, ``, `b`}, `C:b`},
   456  	{[]string{`C:`, ``, ``, `b`}, `C:b`},
   457  	{[]string{`C:`, ``}, `C:.`},
   458  	{[]string{`C:`, ``, ``}, `C:.`},
   459  	{[]string{`C:`, `\a`}, `C:\a`},
   460  	{[]string{`C:`, ``, `\a`}, `C:\a`},
   461  	{[]string{`C:.`, `a`}, `C:a`},
   462  	{[]string{`C:a`, `b`}, `C:a\b`},
   463  	{[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
   464  	{[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
   465  	{[]string{`\\host\share\foo`}, `\\host\share\foo`},
   466  	{[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
   467  	{[]string{`\`}, `\`},
   468  	{[]string{`\`, ``}, `\`},
   469  	{[]string{`\`, `a`}, `\a`},
   470  	{[]string{`\\`, `a`}, `\\a`},
   471  	{[]string{`\`, `a`, `b`}, `\a\b`},
   472  	{[]string{`\\`, `a`, `b`}, `\\a\b`},
   473  	{[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
   474  	{[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
   475  	{[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
   476  	{[]string{`//`, `a`}, `\\a`},
   477  	{[]string{`a:\b\c`, `x\..\y:\..\..\z`}, `a:\b\z`},
   478  	{[]string{`\`, `??\a`}, `\.\??\a`},
   479  }
   480  
   481  func TestJoin(t *testing.T) {
   482  	if runtime.GOOS == "windows" {
   483  		jointests = append(jointests, winjointests...)
   484  	} else {
   485  		jointests = append(jointests, nonwinjointests...)
   486  	}
   487  	for _, test := range jointests {
   488  		expected := filepath.FromSlash(test.path)
   489  		if p := filepath.Join(test.elem...); p != expected {
   490  			t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
   491  		}
   492  	}
   493  }
   494  
   495  type ExtTest struct {
   496  	path, ext string
   497  }
   498  
   499  var exttests = []ExtTest{
   500  	{"path.go", ".go"},
   501  	{"path.pb.go", ".go"},
   502  	{"a.dir/b", ""},
   503  	{"a.dir/b.go", ".go"},
   504  	{"a.dir/", ""},
   505  }
   506  
   507  func TestExt(t *testing.T) {
   508  	for _, test := range exttests {
   509  		if x := filepath.Ext(test.path); x != test.ext {
   510  			t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
   511  		}
   512  	}
   513  }
   514  
   515  type Node struct {
   516  	name    string
   517  	entries []*Node // nil if the entry is a file
   518  	mark    int
   519  }
   520  
   521  var tree = &Node{
   522  	"testdata",
   523  	[]*Node{
   524  		{"a", nil, 0},
   525  		{"b", []*Node{}, 0},
   526  		{"c", nil, 0},
   527  		{
   528  			"d",
   529  			[]*Node{
   530  				{"x", nil, 0},
   531  				{"y", []*Node{}, 0},
   532  				{
   533  					"z",
   534  					[]*Node{
   535  						{"u", nil, 0},
   536  						{"v", nil, 0},
   537  					},
   538  					0,
   539  				},
   540  			},
   541  			0,
   542  		},
   543  	},
   544  	0,
   545  }
   546  
   547  func walkTree(n *Node, path string, f func(path string, n *Node)) {
   548  	f(path, n)
   549  	for _, e := range n.entries {
   550  		walkTree(e, filepath.Join(path, e.name), f)
   551  	}
   552  }
   553  
   554  func makeTree(t *testing.T) {
   555  	walkTree(tree, tree.name, func(path string, n *Node) {
   556  		if n.entries == nil {
   557  			fd, err := os.Create(path)
   558  			if err != nil {
   559  				t.Errorf("makeTree: %v", err)
   560  				return
   561  			}
   562  			fd.Close()
   563  		} else {
   564  			os.Mkdir(path, 0770)
   565  		}
   566  	})
   567  }
   568  
   569  func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
   570  
   571  func checkMarks(t *testing.T, report bool) {
   572  	walkTree(tree, tree.name, func(path string, n *Node) {
   573  		if n.mark != 1 && report {
   574  			t.Errorf("node %s mark = %d; expected 1", path, n.mark)
   575  		}
   576  		n.mark = 0
   577  	})
   578  }
   579  
   580  // Assumes that each node name is unique. Good enough for a test.
   581  // If clear is true, any incoming error is cleared before return. The errors
   582  // are always accumulated, though.
   583  func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
   584  	name := d.Name()
   585  	walkTree(tree, tree.name, func(path string, n *Node) {
   586  		if n.name == name {
   587  			n.mark++
   588  		}
   589  	})
   590  	if err != nil {
   591  		*errors = append(*errors, err)
   592  		if clear {
   593  			return nil
   594  		}
   595  		return err
   596  	}
   597  	return nil
   598  }
   599  
   600  // chdir changes the current working directory to the named directory,
   601  // and then restore the original working directory at the end of the test.
   602  func chdir(t *testing.T, dir string) {
   603  	olddir, err := os.Getwd()
   604  	if err != nil {
   605  		t.Fatalf("getwd %s: %v", dir, err)
   606  	}
   607  	if err := os.Chdir(dir); err != nil {
   608  		t.Fatalf("chdir %s: %v", dir, err)
   609  	}
   610  
   611  	t.Cleanup(func() {
   612  		if err := os.Chdir(olddir); err != nil {
   613  			t.Errorf("restore original working directory %s: %v", olddir, err)
   614  			os.Exit(1)
   615  		}
   616  	})
   617  }
   618  
   619  func chtmpdir(t *testing.T) (restore func()) {
   620  	oldwd, err := os.Getwd()
   621  	if err != nil {
   622  		t.Fatalf("chtmpdir: %v", err)
   623  	}
   624  	d, err := os.MkdirTemp("", "test")
   625  	if err != nil {
   626  		t.Fatalf("chtmpdir: %v", err)
   627  	}
   628  	if err := os.Chdir(d); err != nil {
   629  		t.Fatalf("chtmpdir: %v", err)
   630  	}
   631  	return func() {
   632  		if err := os.Chdir(oldwd); err != nil {
   633  			t.Fatalf("chtmpdir: %v", err)
   634  		}
   635  		os.RemoveAll(d)
   636  	}
   637  }
   638  
   639  // tempDirCanonical returns a temporary directory for the test to use, ensuring
   640  // that the returned path does not contain symlinks.
   641  func tempDirCanonical(t *testing.T) string {
   642  	dir := t.TempDir()
   643  
   644  	cdir, err := filepath.EvalSymlinks(dir)
   645  	if err != nil {
   646  		t.Errorf("tempDirCanonical: %v", err)
   647  	}
   648  
   649  	return cdir
   650  }
   651  
   652  func TestWalk(t *testing.T) {
   653  	walk := func(root string, fn fs.WalkDirFunc) error {
   654  		return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
   655  			return fn(path, fs.FileInfoToDirEntry(info), err)
   656  		})
   657  	}
   658  	testWalk(t, walk, 1)
   659  }
   660  
   661  func TestWalkDir(t *testing.T) {
   662  	testWalk(t, filepath.WalkDir, 2)
   663  }
   664  
   665  func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
   666  	if runtime.GOOS == "ios" {
   667  		restore := chtmpdir(t)
   668  		defer restore()
   669  	}
   670  
   671  	tmpDir := t.TempDir()
   672  
   673  	origDir, err := os.Getwd()
   674  	if err != nil {
   675  		t.Fatal("finding working dir:", err)
   676  	}
   677  	if err = os.Chdir(tmpDir); err != nil {
   678  		t.Fatal("entering temp dir:", err)
   679  	}
   680  	defer os.Chdir(origDir)
   681  
   682  	makeTree(t)
   683  	errors := make([]error, 0, 10)
   684  	clear := true
   685  	markFn := func(path string, d fs.DirEntry, err error) error {
   686  		return mark(d, err, &errors, clear)
   687  	}
   688  	// Expect no errors.
   689  	err = walk(tree.name, markFn)
   690  	if err != nil {
   691  		t.Fatalf("no error expected, found: %s", err)
   692  	}
   693  	if len(errors) != 0 {
   694  		t.Fatalf("unexpected errors: %s", errors)
   695  	}
   696  	checkMarks(t, true)
   697  	errors = errors[0:0]
   698  
   699  	t.Run("PermErr", func(t *testing.T) {
   700  		// Test permission errors. Only possible if we're not root
   701  		// and only on some file systems (AFS, FAT).  To avoid errors during
   702  		// all.bash on those file systems, skip during go test -short.
   703  		// Chmod is not supported on wasip1.
   704  		if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
   705  			t.Skip("skipping on " + runtime.GOOS)
   706  		}
   707  		if os.Getuid() == 0 {
   708  			t.Skip("skipping as root")
   709  		}
   710  		if testing.Short() {
   711  			t.Skip("skipping in short mode")
   712  		}
   713  
   714  		// introduce 2 errors: chmod top-level directories to 0
   715  		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
   716  		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
   717  
   718  		// 3) capture errors, expect two.
   719  		// mark respective subtrees manually
   720  		markTree(tree.entries[1])
   721  		markTree(tree.entries[3])
   722  		// correct double-marking of directory itself
   723  		tree.entries[1].mark -= errVisit
   724  		tree.entries[3].mark -= errVisit
   725  		err := walk(tree.name, markFn)
   726  		if err != nil {
   727  			t.Fatalf("expected no error return from Walk, got %s", err)
   728  		}
   729  		if len(errors) != 2 {
   730  			t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
   731  		}
   732  		// the inaccessible subtrees were marked manually
   733  		checkMarks(t, true)
   734  		errors = errors[0:0]
   735  
   736  		// 4) capture errors, stop after first error.
   737  		// mark respective subtrees manually
   738  		markTree(tree.entries[1])
   739  		markTree(tree.entries[3])
   740  		// correct double-marking of directory itself
   741  		tree.entries[1].mark -= errVisit
   742  		tree.entries[3].mark -= errVisit
   743  		clear = false // error will stop processing
   744  		err = walk(tree.name, markFn)
   745  		if err == nil {
   746  			t.Fatalf("expected error return from Walk")
   747  		}
   748  		if len(errors) != 1 {
   749  			t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
   750  		}
   751  		// the inaccessible subtrees were marked manually
   752  		checkMarks(t, false)
   753  		errors = errors[0:0]
   754  
   755  		// restore permissions
   756  		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
   757  		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
   758  	})
   759  }
   760  
   761  func touch(t *testing.T, name string) {
   762  	f, err := os.Create(name)
   763  	if err != nil {
   764  		t.Fatal(err)
   765  	}
   766  	if err := f.Close(); err != nil {
   767  		t.Fatal(err)
   768  	}
   769  }
   770  
   771  func TestWalkSkipDirOnFile(t *testing.T) {
   772  	td := t.TempDir()
   773  
   774  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	touch(t, filepath.Join(td, "dir/foo1"))
   778  	touch(t, filepath.Join(td, "dir/foo2"))
   779  
   780  	sawFoo2 := false
   781  	walker := func(path string) error {
   782  		if strings.HasSuffix(path, "foo2") {
   783  			sawFoo2 = true
   784  		}
   785  		if strings.HasSuffix(path, "foo1") {
   786  			return filepath.SkipDir
   787  		}
   788  		return nil
   789  	}
   790  	walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
   791  	walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
   792  
   793  	check := func(t *testing.T, walk func(root string) error, root string) {
   794  		t.Helper()
   795  		sawFoo2 = false
   796  		err := walk(root)
   797  		if err != nil {
   798  			t.Fatal(err)
   799  		}
   800  		if sawFoo2 {
   801  			t.Errorf("SkipDir on file foo1 did not block processing of foo2")
   802  		}
   803  	}
   804  
   805  	t.Run("Walk", func(t *testing.T) {
   806  		Walk := func(root string) error { return filepath.Walk(td, walkFn) }
   807  		check(t, Walk, td)
   808  		check(t, Walk, filepath.Join(td, "dir"))
   809  	})
   810  	t.Run("WalkDir", func(t *testing.T) {
   811  		WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
   812  		check(t, WalkDir, td)
   813  		check(t, WalkDir, filepath.Join(td, "dir"))
   814  	})
   815  }
   816  
   817  func TestWalkSkipAllOnFile(t *testing.T) {
   818  	td := t.TempDir()
   819  
   820  	if err := os.MkdirAll(filepath.Join(td, "dir", "subdir"), 0755); err != nil {
   821  		t.Fatal(err)
   822  	}
   823  	if err := os.MkdirAll(filepath.Join(td, "dir2"), 0755); err != nil {
   824  		t.Fatal(err)
   825  	}
   826  
   827  	touch(t, filepath.Join(td, "dir", "foo1"))
   828  	touch(t, filepath.Join(td, "dir", "foo2"))
   829  	touch(t, filepath.Join(td, "dir", "subdir", "foo3"))
   830  	touch(t, filepath.Join(td, "dir", "foo4"))
   831  	touch(t, filepath.Join(td, "dir2", "bar"))
   832  	touch(t, filepath.Join(td, "last"))
   833  
   834  	remainingWereSkipped := true
   835  	walker := func(path string) error {
   836  		if strings.HasSuffix(path, "foo2") {
   837  			return filepath.SkipAll
   838  		}
   839  
   840  		if strings.HasSuffix(path, "foo3") ||
   841  			strings.HasSuffix(path, "foo4") ||
   842  			strings.HasSuffix(path, "bar") ||
   843  			strings.HasSuffix(path, "last") {
   844  			remainingWereSkipped = false
   845  		}
   846  		return nil
   847  	}
   848  
   849  	walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
   850  	walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
   851  
   852  	check := func(t *testing.T, walk func(root string) error, root string) {
   853  		t.Helper()
   854  		remainingWereSkipped = true
   855  		if err := walk(root); err != nil {
   856  			t.Fatal(err)
   857  		}
   858  		if !remainingWereSkipped {
   859  			t.Errorf("SkipAll on file foo2 did not block processing of remaining files and directories")
   860  		}
   861  	}
   862  
   863  	t.Run("Walk", func(t *testing.T) {
   864  		Walk := func(_ string) error { return filepath.Walk(td, walkFn) }
   865  		check(t, Walk, td)
   866  		check(t, Walk, filepath.Join(td, "dir"))
   867  	})
   868  	t.Run("WalkDir", func(t *testing.T) {
   869  		WalkDir := func(_ string) error { return filepath.WalkDir(td, walkDirFn) }
   870  		check(t, WalkDir, td)
   871  		check(t, WalkDir, filepath.Join(td, "dir"))
   872  	})
   873  }
   874  
   875  func TestWalkFileError(t *testing.T) {
   876  	td := t.TempDir()
   877  
   878  	touch(t, filepath.Join(td, "foo"))
   879  	touch(t, filepath.Join(td, "bar"))
   880  	dir := filepath.Join(td, "dir")
   881  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   882  		t.Fatal(err)
   883  	}
   884  	touch(t, filepath.Join(dir, "baz"))
   885  	touch(t, filepath.Join(dir, "stat-error"))
   886  	defer func() {
   887  		*filepath.LstatP = os.Lstat
   888  	}()
   889  	statErr := errors.New("some stat error")
   890  	*filepath.LstatP = func(path string) (fs.FileInfo, error) {
   891  		if strings.HasSuffix(path, "stat-error") {
   892  			return nil, statErr
   893  		}
   894  		return os.Lstat(path)
   895  	}
   896  	got := map[string]error{}
   897  	err := filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
   898  		rel, _ := filepath.Rel(td, path)
   899  		got[filepath.ToSlash(rel)] = err
   900  		return nil
   901  	})
   902  	if err != nil {
   903  		t.Errorf("Walk error: %v", err)
   904  	}
   905  	want := map[string]error{
   906  		".":              nil,
   907  		"foo":            nil,
   908  		"bar":            nil,
   909  		"dir":            nil,
   910  		"dir/baz":        nil,
   911  		"dir/stat-error": statErr,
   912  	}
   913  	if !reflect.DeepEqual(got, want) {
   914  		t.Errorf("Walked %#v; want %#v", got, want)
   915  	}
   916  }
   917  
   918  func TestWalkSymlinkRoot(t *testing.T) {
   919  	testenv.MustHaveSymlink(t)
   920  
   921  	td := t.TempDir()
   922  	dir := filepath.Join(td, "dir")
   923  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   924  		t.Fatal(err)
   925  	}
   926  	touch(t, filepath.Join(dir, "foo"))
   927  
   928  	link := filepath.Join(td, "link")
   929  	if err := os.Symlink("dir", link); err != nil {
   930  		t.Fatal(err)
   931  	}
   932  
   933  	abslink := filepath.Join(td, "abslink")
   934  	if err := os.Symlink(dir, abslink); err != nil {
   935  		t.Fatal(err)
   936  	}
   937  
   938  	linklink := filepath.Join(td, "linklink")
   939  	if err := os.Symlink("link", linklink); err != nil {
   940  		t.Fatal(err)
   941  	}
   942  
   943  	// Per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12:
   944  	// “A pathname that contains at least one non- <slash> character and that ends
   945  	// with one or more trailing <slash> characters shall not be resolved
   946  	// successfully unless the last pathname component before the trailing <slash>
   947  	// characters names an existing directory [...].”
   948  	//
   949  	// Since Walk does not traverse symlinks itself, its behavior should depend on
   950  	// whether the path passed to Walk ends in a slash: if it does not end in a slash,
   951  	// Walk should report the symlink itself (since it is the last pathname component);
   952  	// but if it does end in a slash, Walk should walk the directory to which the symlink
   953  	// refers (since it must be fully resolved before walking).
   954  	for _, tt := range []struct {
   955  		desc      string
   956  		root      string
   957  		want      []string
   958  		buggyGOOS []string
   959  	}{
   960  		{
   961  			desc: "no slash",
   962  			root: link,
   963  			want: []string{link},
   964  		},
   965  		{
   966  			desc: "slash",
   967  			root: link + string(filepath.Separator),
   968  			want: []string{link, filepath.Join(link, "foo")},
   969  		},
   970  		{
   971  			desc: "abs no slash",
   972  			root: abslink,
   973  			want: []string{abslink},
   974  		},
   975  		{
   976  			desc: "abs with slash",
   977  			root: abslink + string(filepath.Separator),
   978  			want: []string{abslink, filepath.Join(abslink, "foo")},
   979  		},
   980  		{
   981  			desc: "double link no slash",
   982  			root: linklink,
   983  			want: []string{linklink},
   984  		},
   985  		{
   986  			desc:      "double link with slash",
   987  			root:      linklink + string(filepath.Separator),
   988  			want:      []string{linklink, filepath.Join(linklink, "foo")},
   989  			buggyGOOS: []string{"darwin", "ios"}, // https://go.dev/issue/59586
   990  		},
   991  	} {
   992  		tt := tt
   993  		t.Run(tt.desc, func(t *testing.T) {
   994  			var walked []string
   995  			err := filepath.Walk(tt.root, func(path string, info fs.FileInfo, err error) error {
   996  				if err != nil {
   997  					return err
   998  				}
   999  				t.Logf("%#q: %v", path, info.Mode())
  1000  				walked = append(walked, filepath.Clean(path))
  1001  				return nil
  1002  			})
  1003  			if err != nil {
  1004  				t.Fatal(err)
  1005  			}
  1006  
  1007  			if !reflect.DeepEqual(walked, tt.want) {
  1008  				t.Logf("Walk(%#q) visited %#q; want %#q", tt.root, walked, tt.want)
  1009  				if slices.Contains(tt.buggyGOOS, runtime.GOOS) {
  1010  					t.Logf("(ignoring known bug on %v)", runtime.GOOS)
  1011  				} else {
  1012  					t.Fail()
  1013  				}
  1014  			}
  1015  		})
  1016  	}
  1017  }
  1018  
  1019  var basetests = []PathTest{
  1020  	{"", "."},
  1021  	{".", "."},
  1022  	{"/.", "."},
  1023  	{"/", "/"},
  1024  	{"////", "/"},
  1025  	{"x/", "x"},
  1026  	{"abc", "abc"},
  1027  	{"abc/def", "def"},
  1028  	{"a/b/.x", ".x"},
  1029  	{"a/b/c.", "c."},
  1030  	{"a/b/c.x", "c.x"},
  1031  }
  1032  
  1033  var winbasetests = []PathTest{
  1034  	{`c:\`, `\`},
  1035  	{`c:.`, `.`},
  1036  	{`c:\a\b`, `b`},
  1037  	{`c:a\b`, `b`},
  1038  	{`c:a\b\c`, `c`},
  1039  	{`\\host\share\`, `\`},
  1040  	{`\\host\share\a`, `a`},
  1041  	{`\\host\share\a\b`, `b`},
  1042  }
  1043  
  1044  func TestBase(t *testing.T) {
  1045  	tests := basetests
  1046  	if runtime.GOOS == "windows" {
  1047  		// make unix tests work on windows
  1048  		for i := range tests {
  1049  			tests[i].result = filepath.Clean(tests[i].result)
  1050  		}
  1051  		// add windows specific tests
  1052  		tests = append(tests, winbasetests...)
  1053  	}
  1054  	for _, test := range tests {
  1055  		if s := filepath.Base(test.path); s != test.result {
  1056  			t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
  1057  		}
  1058  	}
  1059  }
  1060  
  1061  var dirtests = []PathTest{
  1062  	{"", "."},
  1063  	{".", "."},
  1064  	{"/.", "/"},
  1065  	{"/", "/"},
  1066  	{"/foo", "/"},
  1067  	{"x/", "x"},
  1068  	{"abc", "."},
  1069  	{"abc/def", "abc"},
  1070  	{"a/b/.x", "a/b"},
  1071  	{"a/b/c.", "a/b"},
  1072  	{"a/b/c.x", "a/b"},
  1073  }
  1074  
  1075  var nonwindirtests = []PathTest{
  1076  	{"////", "/"},
  1077  }
  1078  
  1079  var windirtests = []PathTest{
  1080  	{`c:\`, `c:\`},
  1081  	{`c:.`, `c:.`},
  1082  	{`c:\a\b`, `c:\a`},
  1083  	{`c:a\b`, `c:a`},
  1084  	{`c:a\b\c`, `c:a\b`},
  1085  	{`\\host\share`, `\\host\share`},
  1086  	{`\\host\share\`, `\\host\share\`},
  1087  	{`\\host\share\a`, `\\host\share\`},
  1088  	{`\\host\share\a\b`, `\\host\share\a`},
  1089  	{`\\\\`, `\\\\`},
  1090  }
  1091  
  1092  func TestDir(t *testing.T) {
  1093  	tests := dirtests
  1094  	if runtime.GOOS == "windows" {
  1095  		// make unix tests work on windows
  1096  		for i := range tests {
  1097  			tests[i].result = filepath.Clean(tests[i].result)
  1098  		}
  1099  		// add windows specific tests
  1100  		tests = append(tests, windirtests...)
  1101  	} else {
  1102  		tests = append(tests, nonwindirtests...)
  1103  	}
  1104  	for _, test := range tests {
  1105  		if s := filepath.Dir(test.path); s != test.result {
  1106  			t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
  1107  		}
  1108  	}
  1109  }
  1110  
  1111  type IsAbsTest struct {
  1112  	path  string
  1113  	isAbs bool
  1114  }
  1115  
  1116  var isabstests = []IsAbsTest{
  1117  	{"", false},
  1118  	{"/", true},
  1119  	{"/usr/bin/gcc", true},
  1120  	{"..", false},
  1121  	{"/a/../bb", true},
  1122  	{".", false},
  1123  	{"./", false},
  1124  	{"lala", false},
  1125  }
  1126  
  1127  var winisabstests = []IsAbsTest{
  1128  	{`C:\`, true},
  1129  	{`c\`, false},
  1130  	{`c::`, false},
  1131  	{`c:`, false},
  1132  	{`/`, false},
  1133  	{`\`, false},
  1134  	{`\Windows`, false},
  1135  	{`c:a\b`, false},
  1136  	{`c:\a\b`, true},
  1137  	{`c:/a/b`, true},
  1138  	{`\\host\share`, true},
  1139  	{`\\host\share\`, true},
  1140  	{`\\host\share\foo`, true},
  1141  	{`//host/share/foo/bar`, true},
  1142  	{`\\?\a\b\c`, true},
  1143  	{`\??\a\b\c`, true},
  1144  }
  1145  
  1146  func TestIsAbs(t *testing.T) {
  1147  	var tests []IsAbsTest
  1148  	if runtime.GOOS == "windows" {
  1149  		tests = append(tests, winisabstests...)
  1150  		// All non-windows tests should fail, because they have no volume letter.
  1151  		for _, test := range isabstests {
  1152  			tests = append(tests, IsAbsTest{test.path, false})
  1153  		}
  1154  		// All non-windows test should work as intended if prefixed with volume letter.
  1155  		for _, test := range isabstests {
  1156  			tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
  1157  		}
  1158  	} else {
  1159  		tests = isabstests
  1160  	}
  1161  
  1162  	for _, test := range tests {
  1163  		if r := filepath.IsAbs(test.path); r != test.isAbs {
  1164  			t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
  1165  		}
  1166  	}
  1167  }
  1168  
  1169  type EvalSymlinksTest struct {
  1170  	// If dest is empty, the path is created; otherwise the dest is symlinked to the path.
  1171  	path, dest string
  1172  }
  1173  
  1174  var EvalSymlinksTestDirs = []EvalSymlinksTest{
  1175  	{"test", ""},
  1176  	{"test/dir", ""},
  1177  	{"test/dir/link3", "../../"},
  1178  	{"test/link1", "../test"},
  1179  	{"test/link2", "dir"},
  1180  	{"test/linkabs", "/"},
  1181  	{"test/link4", "../test2"},
  1182  	{"test2", "test/dir"},
  1183  	// Issue 23444.
  1184  	{"src", ""},
  1185  	{"src/pool", ""},
  1186  	{"src/pool/test", ""},
  1187  	{"src/versions", ""},
  1188  	{"src/versions/current", "../../version"},
  1189  	{"src/versions/v1", ""},
  1190  	{"src/versions/v1/modules", ""},
  1191  	{"src/versions/v1/modules/test", "../../../pool/test"},
  1192  	{"version", "src/versions/v1"},
  1193  }
  1194  
  1195  var EvalSymlinksTests = []EvalSymlinksTest{
  1196  	{"test", "test"},
  1197  	{"test/dir", "test/dir"},
  1198  	{"test/dir/../..", "."},
  1199  	{"test/link1", "test"},
  1200  	{"test/link2", "test/dir"},
  1201  	{"test/link1/dir", "test/dir"},
  1202  	{"test/link2/..", "test"},
  1203  	{"test/dir/link3", "."},
  1204  	{"test/link2/link3/test", "test"},
  1205  	{"test/linkabs", "/"},
  1206  	{"test/link4/..", "test"},
  1207  	{"src/versions/current/modules/test", "src/pool/test"},
  1208  }
  1209  
  1210  // simpleJoin builds a file name from the directory and path.
  1211  // It does not use Join because we don't want ".." to be evaluated.
  1212  func simpleJoin(dir, path string) string {
  1213  	return dir + string(filepath.Separator) + path
  1214  }
  1215  
  1216  func testEvalSymlinks(t *testing.T, path, want string) {
  1217  	have, err := filepath.EvalSymlinks(path)
  1218  	if err != nil {
  1219  		t.Errorf("EvalSymlinks(%q) error: %v", path, err)
  1220  		return
  1221  	}
  1222  	if filepath.Clean(have) != filepath.Clean(want) {
  1223  		t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
  1224  	}
  1225  }
  1226  
  1227  func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
  1228  	cwd, err := os.Getwd()
  1229  	if err != nil {
  1230  		t.Fatal(err)
  1231  	}
  1232  	defer func() {
  1233  		err := os.Chdir(cwd)
  1234  		if err != nil {
  1235  			t.Fatal(err)
  1236  		}
  1237  	}()
  1238  
  1239  	err = os.Chdir(wd)
  1240  	if err != nil {
  1241  		t.Fatal(err)
  1242  	}
  1243  
  1244  	have, err := filepath.EvalSymlinks(path)
  1245  	if err != nil {
  1246  		t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
  1247  		return
  1248  	}
  1249  	if filepath.Clean(have) != filepath.Clean(want) {
  1250  		t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
  1251  	}
  1252  }
  1253  
  1254  func TestEvalSymlinks(t *testing.T) {
  1255  	testenv.MustHaveSymlink(t)
  1256  
  1257  	tmpDir := t.TempDir()
  1258  
  1259  	// /tmp may itself be a symlink! Avoid the confusion, although
  1260  	// it means trusting the thing we're testing.
  1261  	var err error
  1262  	tmpDir, err = filepath.EvalSymlinks(tmpDir)
  1263  	if err != nil {
  1264  		t.Fatal("eval symlink for tmp dir:", err)
  1265  	}
  1266  
  1267  	// Create the symlink farm using relative paths.
  1268  	for _, d := range EvalSymlinksTestDirs {
  1269  		var err error
  1270  		path := simpleJoin(tmpDir, d.path)
  1271  		if d.dest == "" {
  1272  			err = os.Mkdir(path, 0755)
  1273  		} else {
  1274  			err = os.Symlink(d.dest, path)
  1275  		}
  1276  		if err != nil {
  1277  			t.Fatal(err)
  1278  		}
  1279  	}
  1280  
  1281  	// Evaluate the symlink farm.
  1282  	for _, test := range EvalSymlinksTests {
  1283  		path := simpleJoin(tmpDir, test.path)
  1284  
  1285  		dest := simpleJoin(tmpDir, test.dest)
  1286  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1287  			dest = test.dest
  1288  		}
  1289  		testEvalSymlinks(t, path, dest)
  1290  
  1291  		// test EvalSymlinks(".")
  1292  		testEvalSymlinksAfterChdir(t, path, ".", ".")
  1293  
  1294  		// test EvalSymlinks("C:.") on Windows
  1295  		if runtime.GOOS == "windows" {
  1296  			volDot := filepath.VolumeName(tmpDir) + "."
  1297  			testEvalSymlinksAfterChdir(t, path, volDot, volDot)
  1298  		}
  1299  
  1300  		// test EvalSymlinks(".."+path)
  1301  		dotdotPath := simpleJoin("..", test.dest)
  1302  		if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
  1303  			dotdotPath = test.dest
  1304  		}
  1305  		testEvalSymlinksAfterChdir(t,
  1306  			simpleJoin(tmpDir, "test"),
  1307  			simpleJoin("..", test.path),
  1308  			dotdotPath)
  1309  
  1310  		// test EvalSymlinks(p) where p is relative path
  1311  		testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
  1312  	}
  1313  }
  1314  
  1315  func TestEvalSymlinksIsNotExist(t *testing.T) {
  1316  	testenv.MustHaveSymlink(t)
  1317  
  1318  	defer chtmpdir(t)()
  1319  
  1320  	_, err := filepath.EvalSymlinks("notexist")
  1321  	if !os.IsNotExist(err) {
  1322  		t.Errorf("expected the file is not found, got %v\n", err)
  1323  	}
  1324  
  1325  	err = os.Symlink("notexist", "link")
  1326  	if err != nil {
  1327  		t.Fatal(err)
  1328  	}
  1329  	defer os.Remove("link")
  1330  
  1331  	_, err = filepath.EvalSymlinks("link")
  1332  	if !os.IsNotExist(err) {
  1333  		t.Errorf("expected the file is not found, got %v\n", err)
  1334  	}
  1335  }
  1336  
  1337  func TestIssue13582(t *testing.T) {
  1338  	testenv.MustHaveSymlink(t)
  1339  
  1340  	tmpDir := t.TempDir()
  1341  
  1342  	dir := filepath.Join(tmpDir, "dir")
  1343  	err := os.Mkdir(dir, 0755)
  1344  	if err != nil {
  1345  		t.Fatal(err)
  1346  	}
  1347  	linkToDir := filepath.Join(tmpDir, "link_to_dir")
  1348  	err = os.Symlink(dir, linkToDir)
  1349  	if err != nil {
  1350  		t.Fatal(err)
  1351  	}
  1352  	file := filepath.Join(linkToDir, "file")
  1353  	err = os.WriteFile(file, nil, 0644)
  1354  	if err != nil {
  1355  		t.Fatal(err)
  1356  	}
  1357  	link1 := filepath.Join(linkToDir, "link1")
  1358  	err = os.Symlink(file, link1)
  1359  	if err != nil {
  1360  		t.Fatal(err)
  1361  	}
  1362  	link2 := filepath.Join(linkToDir, "link2")
  1363  	err = os.Symlink(link1, link2)
  1364  	if err != nil {
  1365  		t.Fatal(err)
  1366  	}
  1367  
  1368  	// /tmp may itself be a symlink!
  1369  	realTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1370  	if err != nil {
  1371  		t.Fatal(err)
  1372  	}
  1373  	realDir := filepath.Join(realTmpDir, "dir")
  1374  	realFile := filepath.Join(realDir, "file")
  1375  
  1376  	tests := []struct {
  1377  		path, want string
  1378  	}{
  1379  		{dir, realDir},
  1380  		{linkToDir, realDir},
  1381  		{file, realFile},
  1382  		{link1, realFile},
  1383  		{link2, realFile},
  1384  	}
  1385  	for i, test := range tests {
  1386  		have, err := filepath.EvalSymlinks(test.path)
  1387  		if err != nil {
  1388  			t.Fatal(err)
  1389  		}
  1390  		if have != test.want {
  1391  			t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
  1392  		}
  1393  	}
  1394  }
  1395  
  1396  // Issue 57905.
  1397  func TestRelativeSymlinkToAbsolute(t *testing.T) {
  1398  	testenv.MustHaveSymlink(t)
  1399  	// Not parallel: uses os.Chdir.
  1400  
  1401  	tmpDir := t.TempDir()
  1402  	chdir(t, tmpDir)
  1403  
  1404  	// Create "link" in the current working directory as a symlink to an arbitrary
  1405  	// absolute path. On macOS, this path is likely to begin with a symlink
  1406  	// itself: generally either in /var (symlinked to "private/var") or /tmp
  1407  	// (symlinked to "private/tmp").
  1408  	if err := os.Symlink(tmpDir, "link"); err != nil {
  1409  		t.Fatal(err)
  1410  	}
  1411  	t.Logf(`os.Symlink(%q, "link")`, tmpDir)
  1412  
  1413  	p, err := filepath.EvalSymlinks("link")
  1414  	if err != nil {
  1415  		t.Fatalf(`EvalSymlinks("link"): %v`, err)
  1416  	}
  1417  	want, err := filepath.EvalSymlinks(tmpDir)
  1418  	if err != nil {
  1419  		t.Fatalf(`EvalSymlinks(%q): %v`, tmpDir, err)
  1420  	}
  1421  	if p != want {
  1422  		t.Errorf(`EvalSymlinks("link") = %q; want %q`, p, want)
  1423  	}
  1424  	t.Logf(`EvalSymlinks("link") = %q`, p)
  1425  }
  1426  
  1427  // Test directories relative to temporary directory.
  1428  // The tests are run in absTestDirs[0].
  1429  var absTestDirs = []string{
  1430  	"a",
  1431  	"a/b",
  1432  	"a/b/c",
  1433  }
  1434  
  1435  // Test paths relative to temporary directory. $ expands to the directory.
  1436  // The tests are run in absTestDirs[0].
  1437  // We create absTestDirs first.
  1438  var absTests = []string{
  1439  	".",
  1440  	"b",
  1441  	"b/",
  1442  	"../a",
  1443  	"../a/b",
  1444  	"../a/b/./c/../../.././a",
  1445  	"../a/b/./c/../../.././a/",
  1446  	"$",
  1447  	"$/.",
  1448  	"$/a/../a/b",
  1449  	"$/a/b/c/../../.././a",
  1450  	"$/a/b/c/../../.././a/",
  1451  }
  1452  
  1453  func TestAbs(t *testing.T) {
  1454  	root := t.TempDir()
  1455  	wd, err := os.Getwd()
  1456  	if err != nil {
  1457  		t.Fatal("getwd failed: ", err)
  1458  	}
  1459  	err = os.Chdir(root)
  1460  	if err != nil {
  1461  		t.Fatal("chdir failed: ", err)
  1462  	}
  1463  	defer os.Chdir(wd)
  1464  
  1465  	for _, dir := range absTestDirs {
  1466  		err = os.Mkdir(dir, 0777)
  1467  		if err != nil {
  1468  			t.Fatal("Mkdir failed: ", err)
  1469  		}
  1470  	}
  1471  
  1472  	// Make sure the global absTests slice is not
  1473  	// modified by multiple invocations of TestAbs.
  1474  	tests := absTests
  1475  	if runtime.GOOS == "windows" {
  1476  		vol := filepath.VolumeName(root)
  1477  		var extra []string
  1478  		for _, path := range absTests {
  1479  			if strings.Contains(path, "$") {
  1480  				continue
  1481  			}
  1482  			path = vol + path
  1483  			extra = append(extra, path)
  1484  		}
  1485  		tests = append(slices.Clip(tests), extra...)
  1486  	}
  1487  
  1488  	err = os.Chdir(absTestDirs[0])
  1489  	if err != nil {
  1490  		t.Fatal("chdir failed: ", err)
  1491  	}
  1492  
  1493  	for _, path := range tests {
  1494  		path = strings.ReplaceAll(path, "$", root)
  1495  		info, err := os.Stat(path)
  1496  		if err != nil {
  1497  			t.Errorf("%s: %s", path, err)
  1498  			continue
  1499  		}
  1500  
  1501  		abspath, err := filepath.Abs(path)
  1502  		if err != nil {
  1503  			t.Errorf("Abs(%q) error: %v", path, err)
  1504  			continue
  1505  		}
  1506  		absinfo, err := os.Stat(abspath)
  1507  		if err != nil || !os.SameFile(absinfo, info) {
  1508  			t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
  1509  		}
  1510  		if !filepath.IsAbs(abspath) {
  1511  			t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
  1512  		}
  1513  		if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1514  			t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
  1515  		}
  1516  	}
  1517  }
  1518  
  1519  // Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
  1520  // We test it separately from all other absTests because the empty string is not
  1521  // a valid path, so it can't be used with os.Stat.
  1522  func TestAbsEmptyString(t *testing.T) {
  1523  	root := t.TempDir()
  1524  
  1525  	wd, err := os.Getwd()
  1526  	if err != nil {
  1527  		t.Fatal("getwd failed: ", err)
  1528  	}
  1529  	err = os.Chdir(root)
  1530  	if err != nil {
  1531  		t.Fatal("chdir failed: ", err)
  1532  	}
  1533  	defer os.Chdir(wd)
  1534  
  1535  	info, err := os.Stat(root)
  1536  	if err != nil {
  1537  		t.Fatalf("%s: %s", root, err)
  1538  	}
  1539  
  1540  	abspath, err := filepath.Abs("")
  1541  	if err != nil {
  1542  		t.Fatalf(`Abs("") error: %v`, err)
  1543  	}
  1544  	absinfo, err := os.Stat(abspath)
  1545  	if err != nil || !os.SameFile(absinfo, info) {
  1546  		t.Errorf(`Abs("")=%q, not the same file`, abspath)
  1547  	}
  1548  	if !filepath.IsAbs(abspath) {
  1549  		t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
  1550  	}
  1551  	if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
  1552  		t.Errorf(`Abs("")=%q, isn't clean`, abspath)
  1553  	}
  1554  }
  1555  
  1556  type RelTests struct {
  1557  	root, path, want string
  1558  }
  1559  
  1560  var reltests = []RelTests{
  1561  	{"a/b", "a/b", "."},
  1562  	{"a/b/.", "a/b", "."},
  1563  	{"a/b", "a/b/.", "."},
  1564  	{"./a/b", "a/b", "."},
  1565  	{"a/b", "./a/b", "."},
  1566  	{"ab/cd", "ab/cde", "../cde"},
  1567  	{"ab/cd", "ab/c", "../c"},
  1568  	{"a/b", "a/b/c/d", "c/d"},
  1569  	{"a/b", "a/b/../c", "../c"},
  1570  	{"a/b/../c", "a/b", "../b"},
  1571  	{"a/b/c", "a/c/d", "../../c/d"},
  1572  	{"a/b", "c/d", "../../c/d"},
  1573  	{"a/b/c/d", "a/b", "../.."},
  1574  	{"a/b/c/d", "a/b/", "../.."},
  1575  	{"a/b/c/d/", "a/b", "../.."},
  1576  	{"a/b/c/d/", "a/b/", "../.."},
  1577  	{"../../a/b", "../../a/b/c/d", "c/d"},
  1578  	{"/a/b", "/a/b", "."},
  1579  	{"/a/b/.", "/a/b", "."},
  1580  	{"/a/b", "/a/b/.", "."},
  1581  	{"/ab/cd", "/ab/cde", "../cde"},
  1582  	{"/ab/cd", "/ab/c", "../c"},
  1583  	{"/a/b", "/a/b/c/d", "c/d"},
  1584  	{"/a/b", "/a/b/../c", "../c"},
  1585  	{"/a/b/../c", "/a/b", "../b"},
  1586  	{"/a/b/c", "/a/c/d", "../../c/d"},
  1587  	{"/a/b", "/c/d", "../../c/d"},
  1588  	{"/a/b/c/d", "/a/b", "../.."},
  1589  	{"/a/b/c/d", "/a/b/", "../.."},
  1590  	{"/a/b/c/d/", "/a/b", "../.."},
  1591  	{"/a/b/c/d/", "/a/b/", "../.."},
  1592  	{"/../../a/b", "/../../a/b/c/d", "c/d"},
  1593  	{".", "a/b", "a/b"},
  1594  	{".", "..", ".."},
  1595  
  1596  	// can't do purely lexically
  1597  	{"..", ".", "err"},
  1598  	{"..", "a", "err"},
  1599  	{"../..", "..", "err"},
  1600  	{"a", "/a", "err"},
  1601  	{"/a", "a", "err"},
  1602  }
  1603  
  1604  var winreltests = []RelTests{
  1605  	{`C:a\b\c`, `C:a/b/d`, `..\d`},
  1606  	{`C:\`, `D:\`, `err`},
  1607  	{`C:`, `D:`, `err`},
  1608  	{`C:\Projects`, `c:\projects\src`, `src`},
  1609  	{`C:\Projects`, `c:\projects`, `.`},
  1610  	{`C:\Projects\a\..`, `c:\projects`, `.`},
  1611  	{`\\host\share`, `\\host\share\file.txt`, `file.txt`},
  1612  }
  1613  
  1614  func TestRel(t *testing.T) {
  1615  	tests := append([]RelTests{}, reltests...)
  1616  	if runtime.GOOS == "windows" {
  1617  		for i := range tests {
  1618  			tests[i].want = filepath.FromSlash(tests[i].want)
  1619  		}
  1620  		tests = append(tests, winreltests...)
  1621  	}
  1622  	for _, test := range tests {
  1623  		got, err := filepath.Rel(test.root, test.path)
  1624  		if test.want == "err" {
  1625  			if err == nil {
  1626  				t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
  1627  			}
  1628  			continue
  1629  		}
  1630  		if err != nil {
  1631  			t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
  1632  		}
  1633  		if got != test.want {
  1634  			t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
  1635  		}
  1636  	}
  1637  }
  1638  
  1639  type VolumeNameTest struct {
  1640  	path string
  1641  	vol  string
  1642  }
  1643  
  1644  var volumenametests = []VolumeNameTest{
  1645  	{`c:/foo/bar`, `c:`},
  1646  	{`c:`, `c:`},
  1647  	{`c:\`, `c:`},
  1648  	{`2:`, `2:`},
  1649  	{``, ``},
  1650  	{`\\\host`, `\\\host`},
  1651  	{`\\\host\`, `\\\host`},
  1652  	{`\\\host\share`, `\\\host`},
  1653  	{`\\\host\\share`, `\\\host`},
  1654  	{`\\host`, `\\host`},
  1655  	{`//host`, `\\host`},
  1656  	{`\\host\`, `\\host\`},
  1657  	{`//host/`, `\\host\`},
  1658  	{`\\host\share`, `\\host\share`},
  1659  	{`//host/share`, `\\host\share`},
  1660  	{`\\host\share\`, `\\host\share`},
  1661  	{`//host/share/`, `\\host\share`},
  1662  	{`\\host\share\foo`, `\\host\share`},
  1663  	{`//host/share/foo`, `\\host\share`},
  1664  	{`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
  1665  	{`//host/share//foo///bar////baz`, `\\host\share`},
  1666  	{`\\host\share\foo\..\bar`, `\\host\share`},
  1667  	{`//host/share/foo/../bar`, `\\host\share`},
  1668  	{`//.`, `\\.`},
  1669  	{`//./`, `\\.\`},
  1670  	{`//./NUL`, `\\.\NUL`},
  1671  	{`//?`, `\\?`},
  1672  	{`//?/`, `\\?\`},
  1673  	{`//?/NUL`, `\\?\NUL`},
  1674  	{`/??`, `\??`},
  1675  	{`/??/`, `\??\`},
  1676  	{`/??/NUL`, `\??\NUL`},
  1677  	{`//./a/b`, `\\.\a`},
  1678  	{`//./C:`, `\\.\C:`},
  1679  	{`//./C:/`, `\\.\C:`},
  1680  	{`//./C:/a/b/c`, `\\.\C:`},
  1681  	{`//./UNC/host/share/a/b/c`, `\\.\UNC\host\share`},
  1682  	{`//./UNC/host`, `\\.\UNC\host`},
  1683  	{`//./UNC/host\`, `\\.\UNC\host\`},
  1684  	{`//./UNC`, `\\.\UNC`},
  1685  	{`//./UNC/`, `\\.\UNC\`},
  1686  	{`\\?\x`, `\\?\x`},
  1687  	{`\??\x`, `\??\x`},
  1688  }
  1689  
  1690  func TestVolumeName(t *testing.T) {
  1691  	if runtime.GOOS != "windows" {
  1692  		return
  1693  	}
  1694  	for _, v := range volumenametests {
  1695  		if vol := filepath.VolumeName(v.path); vol != v.vol {
  1696  			t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
  1697  		}
  1698  	}
  1699  }
  1700  
  1701  func TestDriveLetterInEvalSymlinks(t *testing.T) {
  1702  	if runtime.GOOS != "windows" {
  1703  		return
  1704  	}
  1705  	wd, _ := os.Getwd()
  1706  	if len(wd) < 3 {
  1707  		t.Errorf("Current directory path %q is too short", wd)
  1708  	}
  1709  	lp := strings.ToLower(wd)
  1710  	up := strings.ToUpper(wd)
  1711  	flp, err := filepath.EvalSymlinks(lp)
  1712  	if err != nil {
  1713  		t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
  1714  	}
  1715  	fup, err := filepath.EvalSymlinks(up)
  1716  	if err != nil {
  1717  		t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
  1718  	}
  1719  	if flp != fup {
  1720  		t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
  1721  	}
  1722  }
  1723  
  1724  func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
  1725  	if runtime.GOOS == "ios" {
  1726  		t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
  1727  	}
  1728  	root := filepath.Join(testenv.GOROOT(t), "src", "unicode")
  1729  	utf16 := filepath.Join(root, "utf16")
  1730  	utf8 := filepath.Join(root, "utf8")
  1731  	seenUTF16 := false
  1732  	seenUTF8 := false
  1733  	err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
  1734  		if err != nil {
  1735  			t.Fatal(err)
  1736  		}
  1737  
  1738  		switch pth {
  1739  		case utf16:
  1740  			seenUTF16 = true
  1741  			return filepath.SkipDir
  1742  		case utf8:
  1743  			if !seenUTF16 {
  1744  				t.Fatal("filepath.Walk out of order - utf8 before utf16")
  1745  			}
  1746  			seenUTF8 = true
  1747  		}
  1748  		return nil
  1749  	})
  1750  	if err != nil {
  1751  		t.Fatal(err)
  1752  	}
  1753  	if !seenUTF8 {
  1754  		t.Fatalf("%q not seen", utf8)
  1755  	}
  1756  }
  1757  
  1758  func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
  1759  	tmpdir := t.TempDir()
  1760  
  1761  	wd, err := os.Getwd()
  1762  	if err != nil {
  1763  		t.Fatal(err)
  1764  	}
  1765  	defer os.Chdir(wd)
  1766  
  1767  	err = os.Chdir(tmpdir)
  1768  	if err != nil {
  1769  		t.Fatal(err)
  1770  	}
  1771  
  1772  	err = mklink(tmpdir, "link")
  1773  	if err != nil {
  1774  		t.Fatal(err)
  1775  	}
  1776  
  1777  	var visited []string
  1778  	err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
  1779  		if err != nil {
  1780  			t.Fatal(err)
  1781  		}
  1782  		rel, err := filepath.Rel(tmpdir, path)
  1783  		if err != nil {
  1784  			t.Fatal(err)
  1785  		}
  1786  		visited = append(visited, rel)
  1787  		return nil
  1788  	})
  1789  	if err != nil {
  1790  		t.Fatal(err)
  1791  	}
  1792  	slices.Sort(visited)
  1793  	want := []string{".", "link"}
  1794  	if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
  1795  		t.Errorf("unexpected paths visited %q, want %q", visited, want)
  1796  	}
  1797  }
  1798  
  1799  func TestWalkSymlink(t *testing.T) {
  1800  	testenv.MustHaveSymlink(t)
  1801  	testWalkSymlink(t, os.Symlink)
  1802  }
  1803  
  1804  func TestIssue29372(t *testing.T) {
  1805  	tmpDir := t.TempDir()
  1806  
  1807  	path := filepath.Join(tmpDir, "file.txt")
  1808  	err := os.WriteFile(path, nil, 0644)
  1809  	if err != nil {
  1810  		t.Fatal(err)
  1811  	}
  1812  
  1813  	pathSeparator := string(filepath.Separator)
  1814  	tests := []string{
  1815  		path + strings.Repeat(pathSeparator, 1),
  1816  		path + strings.Repeat(pathSeparator, 2),
  1817  		path + strings.Repeat(pathSeparator, 1) + ".",
  1818  		path + strings.Repeat(pathSeparator, 2) + ".",
  1819  		path + strings.Repeat(pathSeparator, 1) + "..",
  1820  		path + strings.Repeat(pathSeparator, 2) + "..",
  1821  	}
  1822  
  1823  	for i, test := range tests {
  1824  		_, err = filepath.EvalSymlinks(test)
  1825  		if err != syscall.ENOTDIR {
  1826  			t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
  1827  		}
  1828  	}
  1829  }
  1830  
  1831  // Issue 30520 part 1.
  1832  func TestEvalSymlinksAboveRoot(t *testing.T) {
  1833  	testenv.MustHaveSymlink(t)
  1834  
  1835  	t.Parallel()
  1836  
  1837  	tmpDir := t.TempDir()
  1838  
  1839  	evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
  1840  	if err != nil {
  1841  		t.Fatal(err)
  1842  	}
  1843  
  1844  	if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
  1845  		t.Fatal(err)
  1846  	}
  1847  	if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
  1848  		t.Fatal(err)
  1849  	}
  1850  	if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
  1851  		t.Fatal(err)
  1852  	}
  1853  
  1854  	// Count the number of ".." elements to get to the root directory.
  1855  	vol := filepath.VolumeName(evalTmpDir)
  1856  	c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
  1857  	var dd []string
  1858  	for i := 0; i < c+2; i++ {
  1859  		dd = append(dd, "..")
  1860  	}
  1861  
  1862  	wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
  1863  
  1864  	// Try different numbers of "..".
  1865  	for _, i := range []int{c, c + 1, c + 2} {
  1866  		check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
  1867  		resolved, err := filepath.EvalSymlinks(check)
  1868  		switch {
  1869  		case runtime.GOOS == "darwin" && errors.Is(err, fs.ErrNotExist):
  1870  			// On darwin, the temp dir is sometimes cleaned up mid-test (issue 37910).
  1871  			testenv.SkipFlaky(t, 37910)
  1872  		case err != nil:
  1873  			t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1874  		case !strings.HasSuffix(resolved, wantSuffix):
  1875  			t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1876  		default:
  1877  			t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1878  		}
  1879  	}
  1880  }
  1881  
  1882  // Issue 30520 part 2.
  1883  func TestEvalSymlinksAboveRootChdir(t *testing.T) {
  1884  	testenv.MustHaveSymlink(t)
  1885  
  1886  	tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
  1887  	if err != nil {
  1888  		t.Fatal(err)
  1889  	}
  1890  	defer os.RemoveAll(tmpDir)
  1891  	chdir(t, tmpDir)
  1892  
  1893  	subdir := filepath.Join("a", "b")
  1894  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1895  		t.Fatal(err)
  1896  	}
  1897  	if err := os.Symlink(subdir, "c"); err != nil {
  1898  		t.Fatal(err)
  1899  	}
  1900  	if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
  1901  		t.Fatal(err)
  1902  	}
  1903  
  1904  	subdir = filepath.Join("d", "e", "f")
  1905  	if err := os.MkdirAll(subdir, 0777); err != nil {
  1906  		t.Fatal(err)
  1907  	}
  1908  	if err := os.Chdir(subdir); err != nil {
  1909  		t.Fatal(err)
  1910  	}
  1911  
  1912  	check := filepath.Join("..", "..", "..", "c", "file")
  1913  	wantSuffix := filepath.Join("a", "b", "file")
  1914  	if resolved, err := filepath.EvalSymlinks(check); err != nil {
  1915  		t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
  1916  	} else if !strings.HasSuffix(resolved, wantSuffix) {
  1917  		t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
  1918  	} else {
  1919  		t.Logf("EvalSymlinks(%q) = %q", check, resolved)
  1920  	}
  1921  }
  1922  
  1923  func TestIssue51617(t *testing.T) {
  1924  	dir := t.TempDir()
  1925  	for _, sub := range []string{"a", filepath.Join("a", "bad"), filepath.Join("a", "next")} {
  1926  		if err := os.Mkdir(filepath.Join(dir, sub), 0755); err != nil {
  1927  			t.Fatal(err)
  1928  		}
  1929  	}
  1930  	bad := filepath.Join(dir, "a", "bad")
  1931  	if err := os.Chmod(bad, 0); err != nil {
  1932  		t.Fatal(err)
  1933  	}
  1934  	defer os.Chmod(bad, 0700) // avoid errors on cleanup
  1935  	var saw []string
  1936  	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
  1937  		if err != nil {
  1938  			return filepath.SkipDir
  1939  		}
  1940  		if d.IsDir() {
  1941  			rel, err := filepath.Rel(dir, path)
  1942  			if err != nil {
  1943  				t.Fatal(err)
  1944  			}
  1945  			saw = append(saw, rel)
  1946  		}
  1947  		return nil
  1948  	})
  1949  	if err != nil {
  1950  		t.Fatal(err)
  1951  	}
  1952  	want := []string{".", "a", filepath.Join("a", "bad"), filepath.Join("a", "next")}
  1953  	if !reflect.DeepEqual(saw, want) {
  1954  		t.Errorf("got directories %v, want %v", saw, want)
  1955  	}
  1956  }
  1957  
  1958  func TestEscaping(t *testing.T) {
  1959  	dir1 := t.TempDir()
  1960  	dir2 := t.TempDir()
  1961  	chdir(t, dir1)
  1962  
  1963  	for _, p := range []string{
  1964  		filepath.Join(dir2, "x"),
  1965  	} {
  1966  		if !filepath.IsLocal(p) {
  1967  			continue
  1968  		}
  1969  		f, err := os.Create(p)
  1970  		if err != nil {
  1971  			f.Close()
  1972  		}
  1973  		ents, err := os.ReadDir(dir2)
  1974  		if err != nil {
  1975  			t.Fatal(err)
  1976  		}
  1977  		for _, e := range ents {
  1978  			t.Fatalf("found: %v", e.Name())
  1979  		}
  1980  	}
  1981  }
  1982  
  1983  func TestEvalSymlinksTooManyLinks(t *testing.T) {
  1984  	testenv.MustHaveSymlink(t)
  1985  	dir := filepath.Join(t.TempDir(), "dir")
  1986  	err := os.Symlink(dir, dir)
  1987  	if err != nil {
  1988  		t.Fatal(err)
  1989  	}
  1990  	_, err = filepath.EvalSymlinks(dir)
  1991  	if err == nil {
  1992  		t.Fatal("expected error, got nil")
  1993  	}
  1994  }
  1995  

View as plain text