Source file src/path/filepath/path_test.go

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

View as plain text