github.com/dorkamotorka/go/src@v0.0.0-20230614113921-187095f0e316/path/filepath/path_test.go (about)

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