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

View as plain text