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