github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 }