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