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 "internal/testenv" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "reflect" 14 "runtime" 15 "strings" 16 "testing" 17 ) 18 19 type PathTest struct { 20 path, result string 21 } 22 23 var cleantests = []PathTest{ 24 // Already clean 25 {"abc", "abc"}, 26 {"abc/def", "abc/def"}, 27 {"a/b/c", "a/b/c"}, 28 {".", "."}, 29 {"..", ".."}, 30 {"../..", "../.."}, 31 {"../../abc", "../../abc"}, 32 {"/abc", "/abc"}, 33 {"/", "/"}, 34 35 // Empty is current dir 36 {"", "."}, 37 38 // Remove trailing slash 39 {"abc/", "abc"}, 40 {"abc/def/", "abc/def"}, 41 {"a/b/c/", "a/b/c"}, 42 {"./", "."}, 43 {"../", ".."}, 44 {"../../", "../.."}, 45 {"/abc/", "/abc"}, 46 47 // Remove doubled slash 48 {"abc//def//ghi", "abc/def/ghi"}, 49 {"//abc", "/abc"}, 50 {"///abc", "/abc"}, 51 {"//abc//", "/abc"}, 52 {"abc//", "abc"}, 53 54 // Remove . elements 55 {"abc/./def", "abc/def"}, 56 {"/./abc/def", "/abc/def"}, 57 {"abc/.", "abc"}, 58 59 // Remove .. elements 60 {"abc/def/ghi/../jkl", "abc/def/jkl"}, 61 {"abc/def/../ghi/../jkl", "abc/jkl"}, 62 {"abc/def/..", "abc"}, 63 {"abc/def/../..", "."}, 64 {"/abc/def/../..", "/"}, 65 {"abc/def/../../..", ".."}, 66 {"/abc/def/../../..", "/"}, 67 {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"}, 68 {"/../abc", "/abc"}, 69 70 // Combinations 71 {"abc/./../def", "def"}, 72 {"abc//./../def", "def"}, 73 {"abc/../../././../def", "../../def"}, 74 } 75 76 var wincleantests = []PathTest{ 77 {`c:`, `c:.`}, 78 {`c:\`, `c:\`}, 79 {`c:\abc`, `c:\abc`}, 80 {`c:abc\..\..\.\.\..\def`, `c:..\..\def`}, 81 {`c:\abc\def\..\..`, `c:\`}, 82 {`c:\..\abc`, `c:\abc`}, 83 {`c:..\abc`, `c:..\abc`}, 84 {`\`, `\`}, 85 {`/`, `\`}, 86 {`\\i\..\c$`, `\c$`}, 87 {`\\i\..\i\c$`, `\i\c$`}, 88 {`\\i\..\I\c$`, `\I\c$`}, 89 {`\\host\share\foo\..\bar`, `\\host\share\bar`}, 90 {`//host/share/foo/../baz`, `\\host\share\baz`}, 91 {`\\a\b\..\c`, `\\a\b\c`}, 92 {`\\a\b`, `\\a\b`}, 93 } 94 95 func TestClean(t *testing.T) { 96 tests := cleantests 97 if runtime.GOOS == "windows" { 98 for i := range tests { 99 tests[i].result = filepath.FromSlash(tests[i].result) 100 } 101 tests = append(tests, wincleantests...) 102 } 103 for _, test := range tests { 104 if s := filepath.Clean(test.path); s != test.result { 105 t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result) 106 } 107 if s := filepath.Clean(test.result); s != test.result { 108 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result) 109 } 110 } 111 112 if testing.Short() { 113 t.Skip("skipping malloc count in short mode") 114 } 115 if runtime.GOMAXPROCS(0) > 1 { 116 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") 117 return 118 } 119 120 for _, test := range tests { 121 allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) }) 122 if allocs > 0 { 123 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs) 124 } 125 } 126 } 127 128 const sep = filepath.Separator 129 130 var slashtests = []PathTest{ 131 {"", ""}, 132 {"/", string(sep)}, 133 {"/a/b", string([]byte{sep, 'a', sep, 'b'})}, 134 {"a//b", string([]byte{'a', sep, sep, 'b'})}, 135 } 136 137 func TestFromAndToSlash(t *testing.T) { 138 for _, test := range slashtests { 139 if s := filepath.FromSlash(test.path); s != test.result { 140 t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result) 141 } 142 if s := filepath.ToSlash(test.result); s != test.path { 143 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path) 144 } 145 } 146 } 147 148 type SplitListTest struct { 149 list string 150 result []string 151 } 152 153 const lsep = filepath.ListSeparator 154 155 var splitlisttests = []SplitListTest{ 156 {"", []string{}}, 157 {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}}, 158 {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}}, 159 } 160 161 var winsplitlisttests = []SplitListTest{ 162 // quoted 163 {`"a"`, []string{`a`}}, 164 165 // semicolon 166 {`";"`, []string{`;`}}, 167 {`"a;b"`, []string{`a;b`}}, 168 {`";";`, []string{`;`, ``}}, 169 {`;";"`, []string{``, `;`}}, 170 171 // partially quoted 172 {`a";"b`, []string{`a;b`}}, 173 {`a; ""b`, []string{`a`, ` b`}}, 174 {`"a;b`, []string{`a;b`}}, 175 {`""a;b`, []string{`a`, `b`}}, 176 {`"""a;b`, []string{`a;b`}}, 177 {`""""a;b`, []string{`a`, `b`}}, 178 {`a";b`, []string{`a;b`}}, 179 {`a;b";c`, []string{`a`, `b;c`}}, 180 {`"a";b";c`, []string{`a`, `b;c`}}, 181 } 182 183 func TestSplitList(t *testing.T) { 184 tests := splitlisttests 185 if runtime.GOOS == "windows" { 186 tests = append(tests, winsplitlisttests...) 187 } 188 for _, test := range tests { 189 if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) { 190 t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result) 191 } 192 } 193 } 194 195 type SplitTest struct { 196 path, dir, file string 197 } 198 199 var unixsplittests = []SplitTest{ 200 {"a/b", "a/", "b"}, 201 {"a/b/", "a/b/", ""}, 202 {"a/", "a/", ""}, 203 {"a", "", "a"}, 204 {"/", "/", ""}, 205 } 206 207 var winsplittests = []SplitTest{ 208 {`c:`, `c:`, ``}, 209 {`c:/`, `c:/`, ``}, 210 {`c:/foo`, `c:/`, `foo`}, 211 {`c:/foo/bar`, `c:/foo/`, `bar`}, 212 {`//host/share`, `//host/share`, ``}, 213 {`//host/share/`, `//host/share/`, ``}, 214 {`//host/share/foo`, `//host/share/`, `foo`}, 215 {`\\host\share`, `\\host\share`, ``}, 216 {`\\host\share\`, `\\host\share\`, ``}, 217 {`\\host\share\foo`, `\\host\share\`, `foo`}, 218 } 219 220 func TestSplit(t *testing.T) { 221 var splittests []SplitTest 222 splittests = unixsplittests 223 if runtime.GOOS == "windows" { 224 splittests = append(splittests, winsplittests...) 225 } 226 for _, test := range splittests { 227 if d, f := filepath.Split(test.path); d != test.dir || f != test.file { 228 t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file) 229 } 230 } 231 } 232 233 type JoinTest struct { 234 elem []string 235 path string 236 } 237 238 var jointests = []JoinTest{ 239 // zero parameters 240 {[]string{}, ""}, 241 242 // one parameter 243 {[]string{""}, ""}, 244 {[]string{"/"}, "/"}, 245 {[]string{"a"}, "a"}, 246 247 // two parameters 248 {[]string{"a", "b"}, "a/b"}, 249 {[]string{"a", ""}, "a"}, 250 {[]string{"", "b"}, "b"}, 251 {[]string{"/", "a"}, "/a"}, 252 {[]string{"/", "a/b"}, "/a/b"}, 253 {[]string{"/", ""}, "/"}, 254 {[]string{"//", "a"}, "/a"}, 255 {[]string{"/a", "b"}, "/a/b"}, 256 {[]string{"a/", "b"}, "a/b"}, 257 {[]string{"a/", ""}, "a"}, 258 {[]string{"", ""}, ""}, 259 260 // three parameters 261 {[]string{"/", "a", "b"}, "/a/b"}, 262 } 263 264 var winjointests = []JoinTest{ 265 {[]string{`directory`, `file`}, `directory\file`}, 266 {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`}, 267 {[]string{`C:\Windows\`, ``}, `C:\Windows`}, 268 {[]string{`C:\`, `Windows`}, `C:\Windows`}, 269 {[]string{`C:`, `a`}, `C:a`}, 270 {[]string{`C:`, `a\b`}, `C:a\b`}, 271 {[]string{`C:`, `a`, `b`}, `C:a\b`}, 272 {[]string{`C:.`, `a`}, `C:a`}, 273 {[]string{`C:a`, `b`}, `C:a\b`}, 274 {[]string{`C:a`, `b`, `d`}, `C:a\b\d`}, 275 {[]string{`\\host\share`, `foo`}, `\\host\share\foo`}, 276 {[]string{`\\host\share\foo`}, `\\host\share\foo`}, 277 {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`}, 278 {[]string{`\`}, `\`}, 279 {[]string{`\`, ``}, `\`}, 280 {[]string{`\`, `a`}, `\a`}, 281 {[]string{`\\`, `a`}, `\a`}, 282 {[]string{`\`, `a`, `b`}, `\a\b`}, 283 {[]string{`\\`, `a`, `b`}, `\a\b`}, 284 {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`}, 285 {[]string{`\\a`, `b`, `c`}, `\a\b\c`}, 286 {[]string{`\\a\`, `b`, `c`}, `\a\b\c`}, 287 } 288 289 func TestJoin(t *testing.T) { 290 if runtime.GOOS == "windows" { 291 jointests = append(jointests, winjointests...) 292 } 293 for _, test := range jointests { 294 expected := filepath.FromSlash(test.path) 295 if p := filepath.Join(test.elem...); p != expected { 296 t.Errorf("join(%q) = %q, want %q", test.elem, p, expected) 297 } 298 } 299 } 300 301 type ExtTest struct { 302 path, ext string 303 } 304 305 var exttests = []ExtTest{ 306 {"path.go", ".go"}, 307 {"path.pb.go", ".go"}, 308 {"a.dir/b", ""}, 309 {"a.dir/b.go", ".go"}, 310 {"a.dir/", ""}, 311 } 312 313 func TestExt(t *testing.T) { 314 for _, test := range exttests { 315 if x := filepath.Ext(test.path); x != test.ext { 316 t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext) 317 } 318 } 319 } 320 321 type Node struct { 322 name string 323 entries []*Node // nil if the entry is a file 324 mark int 325 } 326 327 var tree = &Node{ 328 "testdata", 329 []*Node{ 330 {"a", nil, 0}, 331 {"b", []*Node{}, 0}, 332 {"c", nil, 0}, 333 { 334 "d", 335 []*Node{ 336 {"x", nil, 0}, 337 {"y", []*Node{}, 0}, 338 { 339 "z", 340 []*Node{ 341 {"u", nil, 0}, 342 {"v", nil, 0}, 343 }, 344 0, 345 }, 346 }, 347 0, 348 }, 349 }, 350 0, 351 } 352 353 func walkTree(n *Node, path string, f func(path string, n *Node)) { 354 f(path, n) 355 for _, e := range n.entries { 356 walkTree(e, filepath.Join(path, e.name), f) 357 } 358 } 359 360 func makeTree(t *testing.T) { 361 walkTree(tree, tree.name, func(path string, n *Node) { 362 if n.entries == nil { 363 fd, err := os.Create(path) 364 if err != nil { 365 t.Errorf("makeTree: %v", err) 366 return 367 } 368 fd.Close() 369 } else { 370 os.Mkdir(path, 0770) 371 } 372 }) 373 } 374 375 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } 376 377 func checkMarks(t *testing.T, report bool) { 378 walkTree(tree, tree.name, func(path string, n *Node) { 379 if n.mark != 1 && report { 380 t.Errorf("node %s mark = %d; expected 1", path, n.mark) 381 } 382 n.mark = 0 383 }) 384 } 385 386 // Assumes that each node name is unique. Good enough for a test. 387 // If clear is true, any incoming error is cleared before return. The errors 388 // are always accumulated, though. 389 func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error { 390 if err != nil { 391 *errors = append(*errors, err) 392 if clear { 393 return nil 394 } 395 return err 396 } 397 name := info.Name() 398 walkTree(tree, tree.name, func(path string, n *Node) { 399 if n.name == name { 400 n.mark++ 401 } 402 }) 403 return nil 404 } 405 406 func chtmpdir(t *testing.T) (restore func()) { 407 oldwd, err := os.Getwd() 408 if err != nil { 409 t.Fatalf("chtmpdir: %v", err) 410 } 411 d, err := ioutil.TempDir("", "test") 412 if err != nil { 413 t.Fatalf("chtmpdir: %v", err) 414 } 415 if err := os.Chdir(d); err != nil { 416 t.Fatalf("chtmpdir: %v", err) 417 } 418 return func() { 419 if err := os.Chdir(oldwd); err != nil { 420 t.Fatalf("chtmpdir: %v", err) 421 } 422 os.RemoveAll(d) 423 } 424 } 425 426 func TestWalk(t *testing.T) { 427 if runtime.GOOS == "darwin" { 428 switch runtime.GOARCH { 429 case "arm", "arm64": 430 restore := chtmpdir(t) 431 defer restore() 432 } 433 } 434 makeTree(t) 435 errors := make([]error, 0, 10) 436 clear := true 437 markFn := func(path string, info os.FileInfo, err error) error { 438 return mark(path, info, err, &errors, clear) 439 } 440 // Expect no errors. 441 err := filepath.Walk(tree.name, markFn) 442 if err != nil { 443 t.Fatalf("no error expected, found: %s", err) 444 } 445 if len(errors) != 0 { 446 t.Fatalf("unexpected errors: %s", errors) 447 } 448 checkMarks(t, true) 449 errors = errors[0:0] 450 451 // Test permission errors. Only possible if we're not root 452 // and only on some file systems (AFS, FAT). To avoid errors during 453 // all.bash on those file systems, skip during go test -short. 454 if os.Getuid() > 0 && !testing.Short() { 455 // introduce 2 errors: chmod top-level directories to 0 456 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) 457 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) 458 459 // 3) capture errors, expect two. 460 // mark respective subtrees manually 461 markTree(tree.entries[1]) 462 markTree(tree.entries[3]) 463 // correct double-marking of directory itself 464 tree.entries[1].mark-- 465 tree.entries[3].mark-- 466 err := filepath.Walk(tree.name, markFn) 467 if err != nil { 468 t.Fatalf("expected no error return from Walk, got %s", err) 469 } 470 if len(errors) != 2 { 471 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) 472 } 473 // the inaccessible subtrees were marked manually 474 checkMarks(t, true) 475 errors = errors[0:0] 476 477 // 4) capture errors, stop after first error. 478 // mark respective subtrees manually 479 markTree(tree.entries[1]) 480 markTree(tree.entries[3]) 481 // correct double-marking of directory itself 482 tree.entries[1].mark-- 483 tree.entries[3].mark-- 484 clear = false // error will stop processing 485 err = filepath.Walk(tree.name, markFn) 486 if err == nil { 487 t.Fatalf("expected error return from Walk") 488 } 489 if len(errors) != 1 { 490 t.Errorf("expected 1 error, got %d: %s", len(errors), errors) 491 } 492 // the inaccessible subtrees were marked manually 493 checkMarks(t, false) 494 errors = errors[0:0] 495 496 // restore permissions 497 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) 498 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) 499 } 500 501 // cleanup 502 if err := os.RemoveAll(tree.name); err != nil { 503 t.Errorf("removeTree: %v", err) 504 } 505 } 506 507 func touch(t *testing.T, name string) { 508 f, err := os.Create(name) 509 if err != nil { 510 t.Fatal(err) 511 } 512 if err := f.Close(); err != nil { 513 t.Fatal(err) 514 } 515 } 516 517 func TestWalkSkipDirOnFile(t *testing.T) { 518 td, err := ioutil.TempDir("", "walktest") 519 if err != nil { 520 t.Fatal(err) 521 } 522 defer os.RemoveAll(td) 523 524 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil { 525 t.Fatal(err) 526 } 527 touch(t, filepath.Join(td, "dir/foo1")) 528 touch(t, filepath.Join(td, "dir/foo2")) 529 530 sawFoo2 := false 531 walker := func(path string, info os.FileInfo, err error) error { 532 if strings.HasSuffix(path, "foo2") { 533 sawFoo2 = true 534 } 535 if strings.HasSuffix(path, "foo1") { 536 return filepath.SkipDir 537 } 538 return nil 539 } 540 541 err = filepath.Walk(td, walker) 542 if err != nil { 543 t.Fatal(err) 544 } 545 if sawFoo2 { 546 t.Errorf("SkipDir on file foo1 did not block processing of foo2") 547 } 548 549 err = filepath.Walk(filepath.Join(td, "dir"), walker) 550 if err != nil { 551 t.Fatal(err) 552 } 553 if sawFoo2 { 554 t.Errorf("SkipDir on file foo1 did not block processing of foo2") 555 } 556 } 557 558 func TestWalkFileError(t *testing.T) { 559 td, err := ioutil.TempDir("", "walktest") 560 if err != nil { 561 t.Fatal(err) 562 } 563 defer os.RemoveAll(td) 564 565 touch(t, filepath.Join(td, "foo")) 566 touch(t, filepath.Join(td, "bar")) 567 dir := filepath.Join(td, "dir") 568 if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil { 569 t.Fatal(err) 570 } 571 touch(t, filepath.Join(dir, "baz")) 572 touch(t, filepath.Join(dir, "stat-error")) 573 defer func() { 574 *filepath.LstatP = os.Lstat 575 }() 576 statErr := errors.New("some stat error") 577 *filepath.LstatP = func(path string) (os.FileInfo, error) { 578 if strings.HasSuffix(path, "stat-error") { 579 return nil, statErr 580 } 581 return os.Lstat(path) 582 } 583 got := map[string]error{} 584 err = filepath.Walk(td, func(path string, fi os.FileInfo, err error) error { 585 rel, _ := filepath.Rel(td, path) 586 got[filepath.ToSlash(rel)] = err 587 return nil 588 }) 589 if err != nil { 590 t.Errorf("Walk error: %v", err) 591 } 592 want := map[string]error{ 593 ".": nil, 594 "foo": nil, 595 "bar": nil, 596 "dir": nil, 597 "dir/baz": nil, 598 "dir/stat-error": statErr, 599 } 600 if !reflect.DeepEqual(got, want) { 601 t.Errorf("Walked %#v; want %#v", got, want) 602 } 603 } 604 605 var basetests = []PathTest{ 606 {"", "."}, 607 {".", "."}, 608 {"/.", "."}, 609 {"/", "/"}, 610 {"////", "/"}, 611 {"x/", "x"}, 612 {"abc", "abc"}, 613 {"abc/def", "def"}, 614 {"a/b/.x", ".x"}, 615 {"a/b/c.", "c."}, 616 {"a/b/c.x", "c.x"}, 617 } 618 619 var winbasetests = []PathTest{ 620 {`c:\`, `\`}, 621 {`c:.`, `.`}, 622 {`c:\a\b`, `b`}, 623 {`c:a\b`, `b`}, 624 {`c:a\b\c`, `c`}, 625 {`\\host\share\`, `\`}, 626 {`\\host\share\a`, `a`}, 627 {`\\host\share\a\b`, `b`}, 628 } 629 630 func TestBase(t *testing.T) { 631 tests := basetests 632 if runtime.GOOS == "windows" { 633 // make unix tests work on windows 634 for i := range tests { 635 tests[i].result = filepath.Clean(tests[i].result) 636 } 637 // add windows specific tests 638 tests = append(tests, winbasetests...) 639 } 640 for _, test := range tests { 641 if s := filepath.Base(test.path); s != test.result { 642 t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result) 643 } 644 } 645 } 646 647 var dirtests = []PathTest{ 648 {"", "."}, 649 {".", "."}, 650 {"/.", "/"}, 651 {"/", "/"}, 652 {"////", "/"}, 653 {"/foo", "/"}, 654 {"x/", "x"}, 655 {"abc", "."}, 656 {"abc/def", "abc"}, 657 {"a/b/.x", "a/b"}, 658 {"a/b/c.", "a/b"}, 659 {"a/b/c.x", "a/b"}, 660 } 661 662 var windirtests = []PathTest{ 663 {`c:\`, `c:\`}, 664 {`c:.`, `c:.`}, 665 {`c:\a\b`, `c:\a`}, 666 {`c:a\b`, `c:a`}, 667 {`c:a\b\c`, `c:a\b`}, 668 {`\\host\share\`, `\\host\share\`}, 669 {`\\host\share\a`, `\\host\share\`}, 670 {`\\host\share\a\b`, `\\host\share\a`}, 671 } 672 673 func TestDir(t *testing.T) { 674 tests := dirtests 675 if runtime.GOOS == "windows" { 676 // make unix tests work on windows 677 for i := range tests { 678 tests[i].result = filepath.Clean(tests[i].result) 679 } 680 // add windows specific tests 681 tests = append(tests, windirtests...) 682 } 683 for _, test := range tests { 684 if s := filepath.Dir(test.path); s != test.result { 685 t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result) 686 } 687 } 688 } 689 690 type IsAbsTest struct { 691 path string 692 isAbs bool 693 } 694 695 var isabstests = []IsAbsTest{ 696 {"", false}, 697 {"/", true}, 698 {"/usr/bin/gcc", true}, 699 {"..", false}, 700 {"/a/../bb", true}, 701 {".", false}, 702 {"./", false}, 703 {"lala", false}, 704 } 705 706 var winisabstests = []IsAbsTest{ 707 {`C:\`, true}, 708 {`c\`, false}, 709 {`c::`, false}, 710 {`c:`, false}, 711 {`/`, false}, 712 {`\`, false}, 713 {`\Windows`, false}, 714 {`c:a\b`, false}, 715 {`c:\a\b`, true}, 716 {`c:/a/b`, true}, 717 {`\\host\share\foo`, true}, 718 {`//host/share/foo/bar`, true}, 719 } 720 721 func TestIsAbs(t *testing.T) { 722 var tests []IsAbsTest 723 if runtime.GOOS == "windows" { 724 tests = append(tests, winisabstests...) 725 // All non-windows tests should fail, because they have no volume letter. 726 for _, test := range isabstests { 727 tests = append(tests, IsAbsTest{test.path, false}) 728 } 729 // All non-windows test should work as intended if prefixed with volume letter. 730 for _, test := range isabstests { 731 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) 732 } 733 } else { 734 tests = isabstests 735 } 736 737 for _, test := range tests { 738 if r := filepath.IsAbs(test.path); r != test.isAbs { 739 t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs) 740 } 741 } 742 } 743 744 type EvalSymlinksTest struct { 745 // If dest is empty, the path is created; otherwise the dest is symlinked to the path. 746 path, dest string 747 } 748 749 var EvalSymlinksTestDirs = []EvalSymlinksTest{ 750 {"test", ""}, 751 {"test/dir", ""}, 752 {"test/dir/link3", "../../"}, 753 {"test/link1", "../test"}, 754 {"test/link2", "dir"}, 755 {"test/linkabs", "/"}, 756 } 757 758 var EvalSymlinksTests = []EvalSymlinksTest{ 759 {"test", "test"}, 760 {"test/dir", "test/dir"}, 761 {"test/dir/../..", "."}, 762 {"test/link1", "test"}, 763 {"test/link2", "test/dir"}, 764 {"test/link1/dir", "test/dir"}, 765 {"test/link2/..", "test"}, 766 {"test/dir/link3", "."}, 767 {"test/link2/link3/test", "test"}, 768 {"test/linkabs", "/"}, 769 } 770 771 // findEvalSymlinksTestDirsDest searches testDirs 772 // for matching path and returns correspondent dest. 773 func findEvalSymlinksTestDirsDest(t *testing.T, testDirs []EvalSymlinksTest, path string) string { 774 for _, d := range testDirs { 775 if d.path == path { 776 return d.dest 777 } 778 } 779 t.Fatalf("did not find %q in testDirs slice", path) 780 return "" 781 } 782 783 // simpleJoin builds a file name from the directory and path. 784 // It does not use Join because we don't want ".." to be evaluated. 785 func simpleJoin(dir, path string) string { 786 return dir + string(filepath.Separator) + path 787 } 788 789 func TestEvalSymlinks(t *testing.T) { 790 testenv.MustHaveSymlink(t) 791 792 tmpDir, err := ioutil.TempDir("", "evalsymlink") 793 if err != nil { 794 t.Fatal("creating temp dir:", err) 795 } 796 defer os.RemoveAll(tmpDir) 797 798 // /tmp may itself be a symlink! Avoid the confusion, although 799 // it means trusting the thing we're testing. 800 tmpDir, err = filepath.EvalSymlinks(tmpDir) 801 if err != nil { 802 t.Fatal("eval symlink for tmp dir:", err) 803 } 804 805 tests := EvalSymlinksTests 806 testdirs := EvalSymlinksTestDirs 807 if runtime.GOOS == "windows" { 808 if len(tmpDir) < 3 { 809 t.Fatalf("tmpDir path %q is too short", tmpDir) 810 } 811 if tmpDir[1] != ':' { 812 t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir) 813 } 814 newtest := EvalSymlinksTest{"test/linkabswin", tmpDir[:3]} 815 tests = append(tests, newtest) 816 testdirs = append(testdirs, newtest) 817 } 818 819 // Create the symlink farm using relative paths. 820 for _, d := range testdirs { 821 var err error 822 path := simpleJoin(tmpDir, d.path) 823 if d.dest == "" { 824 err = os.Mkdir(path, 0755) 825 } else { 826 err = os.Symlink(d.dest, path) 827 } 828 if err != nil { 829 t.Fatal(err) 830 } 831 } 832 833 wd, err := os.Getwd() 834 if err != nil { 835 t.Fatal(err) 836 } 837 838 // Evaluate the symlink farm. 839 for _, d := range tests { 840 path := simpleJoin(tmpDir, d.path) 841 dest := simpleJoin(tmpDir, d.dest) 842 if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) { 843 dest = d.dest 844 } 845 if p, err := filepath.EvalSymlinks(path); err != nil { 846 t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) 847 } else if filepath.Clean(p) != filepath.Clean(dest) { 848 t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest) 849 } 850 851 // test EvalSymlinks(".") 852 func() { 853 defer func() { 854 err := os.Chdir(wd) 855 if err != nil { 856 t.Fatal(err) 857 } 858 }() 859 860 err := os.Chdir(path) 861 if err != nil { 862 t.Error(err) 863 return 864 } 865 p, err := filepath.EvalSymlinks(".") 866 if err != nil { 867 t.Errorf(`EvalSymlinks(".") in %q directory error: %v`, d.path, err) 868 return 869 } 870 if p == "." { 871 return 872 } 873 want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path)) 874 if p == want { 875 return 876 } 877 t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want "." or %q`, d.path, p, want) 878 }() 879 880 // test EvalSymlinks("C:.") on Windows 881 if runtime.GOOS == "windows" { 882 func() { 883 defer func() { 884 err := os.Chdir(wd) 885 if err != nil { 886 t.Fatal(err) 887 } 888 }() 889 890 err := os.Chdir(path) 891 if err != nil { 892 t.Error(err) 893 return 894 } 895 896 volDot := filepath.VolumeName(tmpDir) + "." 897 898 p, err := filepath.EvalSymlinks(volDot) 899 if err != nil { 900 t.Errorf(`EvalSymlinks("%s") in %q directory error: %v`, volDot, d.path, err) 901 return 902 } 903 if p == volDot { 904 return 905 } 906 want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path)) 907 if p == want { 908 return 909 } 910 t.Errorf(`EvalSymlinks("%s") in %q directory returns %q, want %q or %q`, volDot, d.path, p, volDot, want) 911 }() 912 } 913 914 // test EvalSymlinks(".."+path) 915 func() { 916 defer func() { 917 err := os.Chdir(wd) 918 if err != nil { 919 t.Fatal(err) 920 } 921 }() 922 923 err := os.Chdir(simpleJoin(tmpDir, "test")) 924 if err != nil { 925 t.Error(err) 926 return 927 } 928 929 path := simpleJoin("..", d.path) 930 dest := simpleJoin("..", d.dest) 931 if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) { 932 dest = d.dest 933 } 934 935 if p, err := filepath.EvalSymlinks(path); err != nil { 936 t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) 937 } else if filepath.Clean(p) != filepath.Clean(dest) { 938 t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest) 939 } 940 }() 941 942 // test EvalSymlinks where parameter is relative path 943 func() { 944 defer func() { 945 err := os.Chdir(wd) 946 if err != nil { 947 t.Fatal(err) 948 } 949 }() 950 951 err := os.Chdir(tmpDir) 952 if err != nil { 953 t.Error(err) 954 return 955 } 956 if p, err := filepath.EvalSymlinks(d.path); err != nil { 957 t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) 958 } else if filepath.Clean(p) != filepath.Clean(d.dest) { 959 t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest) 960 } 961 }() 962 } 963 } 964 965 func TestEvalSymlinksIsNotExist(t *testing.T) { 966 testenv.MustHaveSymlink(t) 967 968 defer chtmpdir(t)() 969 970 _, err := filepath.EvalSymlinks("notexist") 971 if !os.IsNotExist(err) { 972 t.Errorf("expected the file is not found, got %v\n", err) 973 } 974 975 err = os.Symlink("notexist", "link") 976 if err != nil { 977 t.Fatal(err) 978 } 979 defer os.Remove("link") 980 981 _, err = filepath.EvalSymlinks("link") 982 if !os.IsNotExist(err) { 983 t.Errorf("expected the file is not found, got %v\n", err) 984 } 985 } 986 987 func TestIssue13582(t *testing.T) { 988 testenv.MustHaveSymlink(t) 989 990 tmpDir, err := ioutil.TempDir("", "issue13582") 991 if err != nil { 992 t.Fatal(err) 993 } 994 defer os.RemoveAll(tmpDir) 995 996 dir := filepath.Join(tmpDir, "dir") 997 err = os.Mkdir(dir, 0755) 998 if err != nil { 999 t.Fatal(err) 1000 } 1001 linkToDir := filepath.Join(tmpDir, "link_to_dir") 1002 err = os.Symlink(dir, linkToDir) 1003 if err != nil { 1004 t.Fatal(err) 1005 } 1006 file := filepath.Join(linkToDir, "file") 1007 err = ioutil.WriteFile(file, nil, 0644) 1008 if err != nil { 1009 t.Fatal(err) 1010 } 1011 link1 := filepath.Join(linkToDir, "link1") 1012 err = os.Symlink(file, link1) 1013 if err != nil { 1014 t.Fatal(err) 1015 } 1016 link2 := filepath.Join(linkToDir, "link2") 1017 err = os.Symlink(link1, link2) 1018 if err != nil { 1019 t.Fatal(err) 1020 } 1021 1022 // /tmp may itself be a symlink! 1023 realTmpDir, err := filepath.EvalSymlinks(tmpDir) 1024 if err != nil { 1025 t.Fatal(err) 1026 } 1027 realDir := filepath.Join(realTmpDir, "dir") 1028 realFile := filepath.Join(realDir, "file") 1029 1030 tests := []struct { 1031 path, want string 1032 }{ 1033 {dir, realDir}, 1034 {linkToDir, realDir}, 1035 {file, realFile}, 1036 {link1, realFile}, 1037 {link2, realFile}, 1038 } 1039 for i, test := range tests { 1040 have, err := filepath.EvalSymlinks(test.path) 1041 if err != nil { 1042 t.Fatal(err) 1043 } 1044 if have != test.want { 1045 t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want) 1046 } 1047 } 1048 } 1049 1050 // Test directories relative to temporary directory. 1051 // The tests are run in absTestDirs[0]. 1052 var absTestDirs = []string{ 1053 "a", 1054 "a/b", 1055 "a/b/c", 1056 } 1057 1058 // Test paths relative to temporary directory. $ expands to the directory. 1059 // The tests are run in absTestDirs[0]. 1060 // We create absTestDirs first. 1061 var absTests = []string{ 1062 ".", 1063 "b", 1064 "b/", 1065 "../a", 1066 "../a/b", 1067 "../a/b/./c/../../.././a", 1068 "../a/b/./c/../../.././a/", 1069 "$", 1070 "$/.", 1071 "$/a/../a/b", 1072 "$/a/b/c/../../.././a", 1073 "$/a/b/c/../../.././a/", 1074 } 1075 1076 func TestAbs(t *testing.T) { 1077 root, err := ioutil.TempDir("", "TestAbs") 1078 if err != nil { 1079 t.Fatal("TempDir failed: ", err) 1080 } 1081 defer os.RemoveAll(root) 1082 1083 wd, err := os.Getwd() 1084 if err != nil { 1085 t.Fatal("getwd failed: ", err) 1086 } 1087 err = os.Chdir(root) 1088 if err != nil { 1089 t.Fatal("chdir failed: ", err) 1090 } 1091 defer os.Chdir(wd) 1092 1093 for _, dir := range absTestDirs { 1094 err = os.Mkdir(dir, 0777) 1095 if err != nil { 1096 t.Fatal("Mkdir failed: ", err) 1097 } 1098 } 1099 1100 if runtime.GOOS == "windows" { 1101 vol := filepath.VolumeName(root) 1102 var extra []string 1103 for _, path := range absTests { 1104 if strings.Contains(path, "$") { 1105 continue 1106 } 1107 path = vol + path 1108 extra = append(extra, path) 1109 } 1110 absTests = append(absTests, extra...) 1111 } 1112 1113 err = os.Chdir(absTestDirs[0]) 1114 if err != nil { 1115 t.Fatal("chdir failed: ", err) 1116 } 1117 1118 for _, path := range absTests { 1119 path = strings.Replace(path, "$", root, -1) 1120 info, err := os.Stat(path) 1121 if err != nil { 1122 t.Errorf("%s: %s", path, err) 1123 continue 1124 } 1125 1126 abspath, err := filepath.Abs(path) 1127 if err != nil { 1128 t.Errorf("Abs(%q) error: %v", path, err) 1129 continue 1130 } 1131 absinfo, err := os.Stat(abspath) 1132 if err != nil || !os.SameFile(absinfo, info) { 1133 t.Errorf("Abs(%q)=%q, not the same file", path, abspath) 1134 } 1135 if !filepath.IsAbs(abspath) { 1136 t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath) 1137 } 1138 if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) { 1139 t.Errorf("Abs(%q)=%q, isn't clean", path, abspath) 1140 } 1141 } 1142 } 1143 1144 type RelTests struct { 1145 root, path, want string 1146 } 1147 1148 var reltests = []RelTests{ 1149 {"a/b", "a/b", "."}, 1150 {"a/b/.", "a/b", "."}, 1151 {"a/b", "a/b/.", "."}, 1152 {"./a/b", "a/b", "."}, 1153 {"a/b", "./a/b", "."}, 1154 {"ab/cd", "ab/cde", "../cde"}, 1155 {"ab/cd", "ab/c", "../c"}, 1156 {"a/b", "a/b/c/d", "c/d"}, 1157 {"a/b", "a/b/../c", "../c"}, 1158 {"a/b/../c", "a/b", "../b"}, 1159 {"a/b/c", "a/c/d", "../../c/d"}, 1160 {"a/b", "c/d", "../../c/d"}, 1161 {"a/b/c/d", "a/b", "../.."}, 1162 {"a/b/c/d", "a/b/", "../.."}, 1163 {"a/b/c/d/", "a/b", "../.."}, 1164 {"a/b/c/d/", "a/b/", "../.."}, 1165 {"../../a/b", "../../a/b/c/d", "c/d"}, 1166 {"/a/b", "/a/b", "."}, 1167 {"/a/b/.", "/a/b", "."}, 1168 {"/a/b", "/a/b/.", "."}, 1169 {"/ab/cd", "/ab/cde", "../cde"}, 1170 {"/ab/cd", "/ab/c", "../c"}, 1171 {"/a/b", "/a/b/c/d", "c/d"}, 1172 {"/a/b", "/a/b/../c", "../c"}, 1173 {"/a/b/../c", "/a/b", "../b"}, 1174 {"/a/b/c", "/a/c/d", "../../c/d"}, 1175 {"/a/b", "/c/d", "../../c/d"}, 1176 {"/a/b/c/d", "/a/b", "../.."}, 1177 {"/a/b/c/d", "/a/b/", "../.."}, 1178 {"/a/b/c/d/", "/a/b", "../.."}, 1179 {"/a/b/c/d/", "/a/b/", "../.."}, 1180 {"/../../a/b", "/../../a/b/c/d", "c/d"}, 1181 {".", "a/b", "a/b"}, 1182 {".", "..", ".."}, 1183 1184 // can't do purely lexically 1185 {"..", ".", "err"}, 1186 {"..", "a", "err"}, 1187 {"../..", "..", "err"}, 1188 {"a", "/a", "err"}, 1189 {"/a", "a", "err"}, 1190 } 1191 1192 var winreltests = []RelTests{ 1193 {`C:a\b\c`, `C:a/b/d`, `..\d`}, 1194 {`C:\`, `D:\`, `err`}, 1195 {`C:`, `D:`, `err`}, 1196 {`C:\Projects`, `c:\projects\src`, `src`}, 1197 {`C:\Projects`, `c:\projects`, `.`}, 1198 {`C:\Projects\a\..`, `c:\projects`, `.`}, 1199 } 1200 1201 func TestRel(t *testing.T) { 1202 tests := append([]RelTests{}, reltests...) 1203 if runtime.GOOS == "windows" { 1204 for i := range tests { 1205 tests[i].want = filepath.FromSlash(tests[i].want) 1206 } 1207 tests = append(tests, winreltests...) 1208 } 1209 for _, test := range tests { 1210 got, err := filepath.Rel(test.root, test.path) 1211 if test.want == "err" { 1212 if err == nil { 1213 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got) 1214 } 1215 continue 1216 } 1217 if err != nil { 1218 t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err) 1219 } 1220 if got != test.want { 1221 t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want) 1222 } 1223 } 1224 } 1225 1226 type VolumeNameTest struct { 1227 path string 1228 vol string 1229 } 1230 1231 var volumenametests = []VolumeNameTest{ 1232 {`c:/foo/bar`, `c:`}, 1233 {`c:`, `c:`}, 1234 {`2:`, ``}, 1235 {``, ``}, 1236 {`\\\host`, ``}, 1237 {`\\\host\`, ``}, 1238 {`\\\host\share`, ``}, 1239 {`\\\host\\share`, ``}, 1240 {`\\host`, ``}, 1241 {`//host`, ``}, 1242 {`\\host\`, ``}, 1243 {`//host/`, ``}, 1244 {`\\host\share`, `\\host\share`}, 1245 {`//host/share`, `//host/share`}, 1246 {`\\host\share\`, `\\host\share`}, 1247 {`//host/share/`, `//host/share`}, 1248 {`\\host\share\foo`, `\\host\share`}, 1249 {`//host/share/foo`, `//host/share`}, 1250 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`}, 1251 {`//host/share//foo///bar////baz`, `//host/share`}, 1252 {`\\host\share\foo\..\bar`, `\\host\share`}, 1253 {`//host/share/foo/../bar`, `//host/share`}, 1254 } 1255 1256 func TestVolumeName(t *testing.T) { 1257 if runtime.GOOS != "windows" { 1258 return 1259 } 1260 for _, v := range volumenametests { 1261 if vol := filepath.VolumeName(v.path); vol != v.vol { 1262 t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol) 1263 } 1264 } 1265 } 1266 1267 func TestDriveLetterInEvalSymlinks(t *testing.T) { 1268 if runtime.GOOS != "windows" { 1269 return 1270 } 1271 wd, _ := os.Getwd() 1272 if len(wd) < 3 { 1273 t.Errorf("Current directory path %q is too short", wd) 1274 } 1275 lp := strings.ToLower(wd) 1276 up := strings.ToUpper(wd) 1277 flp, err := filepath.EvalSymlinks(lp) 1278 if err != nil { 1279 t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err) 1280 } 1281 fup, err := filepath.EvalSymlinks(up) 1282 if err != nil { 1283 t.Fatalf("EvalSymlinks(%q) failed: %q", up, err) 1284 } 1285 if flp != fup { 1286 t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup) 1287 } 1288 } 1289 1290 func TestBug3486(t *testing.T) { // https://golang.org/issue/3486 1291 if runtime.GOOS == "darwin" { 1292 switch runtime.GOARCH { 1293 case "arm", "arm64": 1294 t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH) 1295 } 1296 } 1297 root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test") 1298 if err != nil { 1299 t.Fatal(err) 1300 } 1301 bugs := filepath.Join(root, "fixedbugs") 1302 ken := filepath.Join(root, "ken") 1303 seenBugs := false 1304 seenKen := false 1305 err = filepath.Walk(root, func(pth string, info os.FileInfo, err error) error { 1306 if err != nil { 1307 t.Fatal(err) 1308 } 1309 1310 switch pth { 1311 case bugs: 1312 seenBugs = true 1313 return filepath.SkipDir 1314 case ken: 1315 if !seenBugs { 1316 t.Fatal("filepath.Walk out of order - ken before fixedbugs") 1317 } 1318 seenKen = true 1319 } 1320 return nil 1321 }) 1322 if err != nil { 1323 t.Fatal(err) 1324 } 1325 if !seenKen { 1326 t.Fatalf("%q not seen", ken) 1327 } 1328 } 1329