github.com/golang/dep@v0.5.4/gps/pkgtree/pkgtree_test.go (about) 1 // Copyright 2017 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 pkgtree 6 7 import ( 8 "fmt" 9 "go/build" 10 "go/scanner" 11 "go/token" 12 "io/ioutil" 13 "os" 14 "path" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "strings" 19 "testing" 20 21 "github.com/golang/dep/gps/paths" 22 "github.com/golang/dep/internal/fs" 23 _ "github.com/golang/dep/internal/test" // DO NOT REMOVE, allows go test ./... -update to work 24 "github.com/google/go-cmp/cmp" 25 ) 26 27 // PackageTree.ToReachMap() uses an easily separable algorithm, wmToReach(), 28 // to turn a discovered set of packages and their imports into a proper pair of 29 // internal and external reach maps. 30 // 31 // That algorithm is purely symbolic (no filesystem interaction), and thus is 32 // easy to test. This is that test. 33 func TestWorkmapToReach(t *testing.T) { 34 empty := func() map[string]bool { 35 return make(map[string]bool) 36 } 37 38 table := map[string]struct { 39 workmap map[string]wm 40 rm ReachMap 41 em map[string]*ProblemImportError 42 backprop bool 43 }{ 44 "single": { 45 workmap: map[string]wm{ 46 "foo": { 47 ex: empty(), 48 in: empty(), 49 }, 50 }, 51 rm: ReachMap{ 52 "foo": {}, 53 }, 54 }, 55 "no external": { 56 workmap: map[string]wm{ 57 "foo": { 58 ex: empty(), 59 in: empty(), 60 }, 61 "foo/bar": { 62 ex: empty(), 63 in: empty(), 64 }, 65 }, 66 rm: ReachMap{ 67 "foo": {}, 68 "foo/bar": {}, 69 }, 70 }, 71 "no external with subpkg": { 72 workmap: map[string]wm{ 73 "foo": { 74 ex: empty(), 75 in: map[string]bool{ 76 "foo/bar": true, 77 }, 78 }, 79 "foo/bar": { 80 ex: empty(), 81 in: empty(), 82 }, 83 }, 84 rm: ReachMap{ 85 "foo": { 86 Internal: []string{"foo/bar"}, 87 }, 88 "foo/bar": {}, 89 }, 90 }, 91 "simple base transitive": { 92 workmap: map[string]wm{ 93 "foo": { 94 ex: empty(), 95 in: map[string]bool{ 96 "foo/bar": true, 97 }, 98 }, 99 "foo/bar": { 100 ex: map[string]bool{ 101 "baz": true, 102 }, 103 in: empty(), 104 }, 105 }, 106 rm: ReachMap{ 107 "foo": { 108 External: []string{"baz"}, 109 Internal: []string{"foo/bar"}, 110 }, 111 "foo/bar": { 112 External: []string{"baz"}, 113 }, 114 }, 115 }, 116 "missing package is poison": { 117 workmap: map[string]wm{ 118 "A": { 119 ex: map[string]bool{ 120 "B/foo": true, 121 }, 122 in: map[string]bool{ 123 "A/foo": true, // missing 124 "A/bar": true, 125 }, 126 }, 127 "A/bar": { 128 ex: map[string]bool{ 129 "B/baz": true, 130 }, 131 in: empty(), 132 }, 133 }, 134 rm: ReachMap{ 135 "A/bar": { 136 External: []string{"B/baz"}, 137 }, 138 }, 139 em: map[string]*ProblemImportError{ 140 "A": { 141 ImportPath: "A", 142 Cause: []string{"A/foo"}, 143 Err: missingPkgErr("A/foo"), 144 }, 145 }, 146 backprop: true, 147 }, 148 "transitive missing package is poison": { 149 workmap: map[string]wm{ 150 "A": { 151 ex: map[string]bool{ 152 "B/foo": true, 153 }, 154 in: map[string]bool{ 155 "A/foo": true, // transitively missing 156 "A/quux": true, 157 }, 158 }, 159 "A/foo": { 160 ex: map[string]bool{ 161 "C/flugle": true, 162 }, 163 in: map[string]bool{ 164 "A/bar": true, // missing 165 }, 166 }, 167 "A/quux": { 168 ex: map[string]bool{ 169 "B/baz": true, 170 }, 171 in: empty(), 172 }, 173 }, 174 rm: ReachMap{ 175 "A/quux": { 176 External: []string{"B/baz"}, 177 }, 178 }, 179 em: map[string]*ProblemImportError{ 180 "A": { 181 ImportPath: "A", 182 Cause: []string{"A/foo", "A/bar"}, 183 Err: missingPkgErr("A/bar"), 184 }, 185 "A/foo": { 186 ImportPath: "A/foo", 187 Cause: []string{"A/bar"}, 188 Err: missingPkgErr("A/bar"), 189 }, 190 }, 191 backprop: true, 192 }, 193 "err'd package is poison": { 194 workmap: map[string]wm{ 195 "A": { 196 ex: map[string]bool{ 197 "B/foo": true, 198 }, 199 in: map[string]bool{ 200 "A/foo": true, // err'd 201 "A/bar": true, 202 }, 203 }, 204 "A/foo": { 205 err: fmt.Errorf("err pkg"), 206 }, 207 "A/bar": { 208 ex: map[string]bool{ 209 "B/baz": true, 210 }, 211 in: empty(), 212 }, 213 }, 214 rm: ReachMap{ 215 "A/bar": { 216 External: []string{"B/baz"}, 217 }, 218 }, 219 em: map[string]*ProblemImportError{ 220 "A": { 221 ImportPath: "A", 222 Cause: []string{"A/foo"}, 223 Err: fmt.Errorf("err pkg"), 224 }, 225 "A/foo": { 226 ImportPath: "A/foo", 227 Err: fmt.Errorf("err pkg"), 228 }, 229 }, 230 backprop: true, 231 }, 232 "transitive err'd package is poison": { 233 workmap: map[string]wm{ 234 "A": { 235 ex: map[string]bool{ 236 "B/foo": true, 237 }, 238 in: map[string]bool{ 239 "A/foo": true, // transitively err'd 240 "A/quux": true, 241 }, 242 }, 243 "A/foo": { 244 ex: map[string]bool{ 245 "C/flugle": true, 246 }, 247 in: map[string]bool{ 248 "A/bar": true, // err'd 249 }, 250 }, 251 "A/bar": { 252 err: fmt.Errorf("err pkg"), 253 }, 254 "A/quux": { 255 ex: map[string]bool{ 256 "B/baz": true, 257 }, 258 in: empty(), 259 }, 260 }, 261 rm: ReachMap{ 262 "A/quux": { 263 External: []string{"B/baz"}, 264 }, 265 }, 266 em: map[string]*ProblemImportError{ 267 "A": { 268 ImportPath: "A", 269 Cause: []string{"A/foo", "A/bar"}, 270 Err: fmt.Errorf("err pkg"), 271 }, 272 "A/foo": { 273 ImportPath: "A/foo", 274 Cause: []string{"A/bar"}, 275 Err: fmt.Errorf("err pkg"), 276 }, 277 "A/bar": { 278 ImportPath: "A/bar", 279 Err: fmt.Errorf("err pkg"), 280 }, 281 }, 282 backprop: true, 283 }, 284 "transitive err'd package no backprop": { 285 workmap: map[string]wm{ 286 "A": { 287 ex: map[string]bool{ 288 "B/foo": true, 289 }, 290 in: map[string]bool{ 291 "A/foo": true, // transitively err'd 292 "A/quux": true, 293 }, 294 }, 295 "A/foo": { 296 ex: map[string]bool{ 297 "C/flugle": true, 298 }, 299 in: map[string]bool{ 300 "A/bar": true, // err'd 301 }, 302 }, 303 "A/bar": { 304 err: fmt.Errorf("err pkg"), 305 }, 306 "A/quux": { 307 ex: map[string]bool{ 308 "B/baz": true, 309 }, 310 in: empty(), 311 }, 312 }, 313 rm: ReachMap{ 314 "A": { 315 Internal: []string{"A/bar", "A/foo", "A/quux"}, 316 //Internal: []string{"A/foo", "A/quux"}, 317 External: []string{"B/baz", "B/foo", "C/flugle"}, 318 }, 319 "A/foo": { 320 Internal: []string{"A/bar"}, 321 External: []string{"C/flugle"}, 322 }, 323 "A/quux": { 324 External: []string{"B/baz"}, 325 }, 326 }, 327 em: map[string]*ProblemImportError{ 328 "A/bar": { 329 ImportPath: "A/bar", 330 Err: fmt.Errorf("err pkg"), 331 }, 332 }, 333 }, 334 // The following tests are mostly about regressions and weeding out 335 // weird assumptions 336 "internal diamond": { 337 workmap: map[string]wm{ 338 "A": { 339 ex: map[string]bool{ 340 "B/foo": true, 341 }, 342 in: map[string]bool{ 343 "A/foo": true, 344 "A/bar": true, 345 }, 346 }, 347 "A/foo": { 348 ex: map[string]bool{ 349 "C": true, 350 }, 351 in: map[string]bool{ 352 "A/quux": true, 353 }, 354 }, 355 "A/bar": { 356 ex: map[string]bool{ 357 "D": true, 358 }, 359 in: map[string]bool{ 360 "A/quux": true, 361 }, 362 }, 363 "A/quux": { 364 ex: map[string]bool{ 365 "B/baz": true, 366 }, 367 in: empty(), 368 }, 369 }, 370 rm: ReachMap{ 371 "A": { 372 External: []string{ 373 "B/baz", 374 "B/foo", 375 "C", 376 "D", 377 }, 378 Internal: []string{ 379 "A/bar", 380 "A/foo", 381 "A/quux", 382 }, 383 }, 384 "A/foo": { 385 External: []string{ 386 "B/baz", 387 "C", 388 }, 389 Internal: []string{ 390 "A/quux", 391 }, 392 }, 393 "A/bar": { 394 External: []string{ 395 "B/baz", 396 "D", 397 }, 398 Internal: []string{ 399 "A/quux", 400 }, 401 }, 402 "A/quux": { 403 External: []string{"B/baz"}, 404 }, 405 }, 406 }, 407 "rootmost gets imported": { 408 workmap: map[string]wm{ 409 "A": { 410 ex: map[string]bool{ 411 "B": true, 412 }, 413 in: empty(), 414 }, 415 "A/foo": { 416 ex: map[string]bool{ 417 "C": true, 418 }, 419 in: map[string]bool{ 420 "A": true, 421 }, 422 }, 423 }, 424 rm: ReachMap{ 425 "A": { 426 External: []string{"B"}, 427 }, 428 "A/foo": { 429 External: []string{ 430 "B", 431 "C", 432 }, 433 Internal: []string{ 434 "A", 435 }, 436 }, 437 }, 438 }, 439 "self cycle": { 440 workmap: map[string]wm{ 441 "A": {in: map[string]bool{"A": true}}, 442 }, 443 rm: ReachMap{ 444 "A": {Internal: []string{"A"}}, 445 }, 446 }, 447 "simple cycle": { 448 workmap: map[string]wm{ 449 "A": {in: map[string]bool{"B": true}}, 450 "B": {in: map[string]bool{"A": true}}, 451 }, 452 rm: ReachMap{ 453 "A": {Internal: []string{"A", "B"}}, 454 "B": {Internal: []string{"A", "B"}}, 455 }, 456 }, 457 "cycle with external dependency": { 458 workmap: map[string]wm{ 459 "A": { 460 in: map[string]bool{"B": true}, 461 }, 462 "B": { 463 ex: map[string]bool{"C": true}, 464 in: map[string]bool{"A": true}, 465 }, 466 }, 467 rm: ReachMap{ 468 "A": { 469 External: []string{"C"}, 470 Internal: []string{"A", "B"}, 471 }, 472 "B": { 473 External: []string{"C"}, 474 Internal: []string{"A", "B"}, 475 }, 476 }, 477 }, 478 "cycle with transitive external dependency": { 479 workmap: map[string]wm{ 480 "A": { 481 in: map[string]bool{"B": true}, 482 }, 483 "B": { 484 in: map[string]bool{"A": true, "C": true}, 485 }, 486 "C": { 487 ex: map[string]bool{"D": true}, 488 }, 489 }, 490 rm: ReachMap{ 491 "A": { 492 External: []string{"D"}, 493 Internal: []string{"A", "B", "C"}, 494 }, 495 "B": { 496 External: []string{"D"}, 497 Internal: []string{"A", "B", "C"}, 498 }, 499 "C": { 500 External: []string{"D"}, 501 }, 502 }, 503 }, 504 "internal cycle": { 505 workmap: map[string]wm{ 506 "A": { 507 ex: map[string]bool{"B": true}, 508 in: map[string]bool{"C": true}, 509 }, 510 "C": { 511 in: map[string]bool{"D": true}, 512 }, 513 "D": { 514 in: map[string]bool{"E": true}, 515 }, 516 "E": { 517 in: map[string]bool{"C": true}, 518 }, 519 }, 520 rm: ReachMap{ 521 "A": { 522 External: []string{"B"}, 523 Internal: []string{"C", "D", "E"}, 524 }, 525 "C": { 526 Internal: []string{"C", "D", "E"}, 527 }, 528 "D": { 529 Internal: []string{"C", "D", "E"}, 530 }, 531 "E": { 532 Internal: []string{"C", "D", "E"}, 533 }, 534 }, 535 }, 536 } 537 538 for name, fix := range table { 539 name, fix := name, fix 540 t.Run(name, func(t *testing.T) { 541 t.Parallel() 542 543 // Avoid erroneous errors by initializing the fixture's error map if 544 // needed 545 if fix.em == nil { 546 fix.em = make(map[string]*ProblemImportError) 547 } 548 549 rm, em := wmToReach(fix.workmap, fix.backprop) 550 if diff := cmp.Diff(rm, fix.rm); diff != "" { 551 //t.Error(pretty.Sprintf("wmToReach(%q): Did not get expected reach map:\n\t(GOT): %s\n\t(WNT): %s", name, rm, fix.rm)) 552 t.Errorf("Did not get expected reach map:\n\t(GOT): %s\n\t(WNT): %s", rm, fix.rm) 553 } 554 if diff := cmp.Diff(em, fix.em, cmp.Comparer(func(x error, y error) bool { 555 return x.Error() == y.Error() 556 })); diff != "" { 557 //t.Error(pretty.Sprintf("wmToReach(%q): Did not get expected error map:\n\t(GOT): %# v\n\t(WNT): %# v", name, em, fix.em)) 558 t.Errorf("Did not get expected error map:\n\t(GOT): %v\n\t(WNT): %v", em, fix.em) 559 } 560 }) 561 } 562 } 563 564 func TestListPackagesNoDir(t *testing.T) { 565 out, err := ListPackages(filepath.Join(getTestdataRootDir(t), "notexist"), "notexist") 566 if err == nil { 567 t.Error("ListPackages should have errored on pointing to a nonexistent dir") 568 } 569 if !reflect.DeepEqual(PackageTree{}, out) { 570 t.Error("should've gotten back an empty PackageTree") 571 } 572 } 573 574 func TestListPackages(t *testing.T) { 575 srcdir := filepath.Join(getTestdataRootDir(t), "src") 576 j := func(s ...string) string { 577 return filepath.Join(srcdir, filepath.Join(s...)) 578 } 579 580 table := map[string]struct { 581 fileRoot string 582 importRoot string 583 out PackageTree 584 err error 585 }{ 586 "empty": { 587 fileRoot: j("empty"), 588 importRoot: "empty", 589 out: PackageTree{ 590 ImportRoot: "empty", 591 Packages: map[string]PackageOrErr{ 592 "empty": { 593 Err: &build.NoGoError{ 594 Dir: j("empty"), 595 }, 596 }, 597 }, 598 }, 599 }, 600 "code only": { 601 fileRoot: j("simple"), 602 importRoot: "simple", 603 out: PackageTree{ 604 ImportRoot: "simple", 605 Packages: map[string]PackageOrErr{ 606 "simple": { 607 P: Package{ 608 ImportPath: "simple", 609 CommentPath: "", 610 Name: "simple", 611 Imports: []string{ 612 "github.com/golang/dep/gps", 613 "sort", 614 }, 615 }, 616 }, 617 }, 618 }, 619 }, 620 "impose import path": { 621 fileRoot: j("simple"), 622 importRoot: "arbitrary", 623 out: PackageTree{ 624 ImportRoot: "arbitrary", 625 Packages: map[string]PackageOrErr{ 626 "arbitrary": { 627 P: Package{ 628 ImportPath: "arbitrary", 629 CommentPath: "", 630 Name: "simple", 631 Imports: []string{ 632 "github.com/golang/dep/gps", 633 "sort", 634 }, 635 }, 636 }, 637 }, 638 }, 639 }, 640 "test only": { 641 fileRoot: j("t"), 642 importRoot: "simple", 643 out: PackageTree{ 644 ImportRoot: "simple", 645 Packages: map[string]PackageOrErr{ 646 "simple": { 647 P: Package{ 648 ImportPath: "simple", 649 CommentPath: "", 650 Name: "simple", 651 Imports: []string{}, 652 TestImports: []string{ 653 "math/rand", 654 "strconv", 655 }, 656 }, 657 }, 658 }, 659 }, 660 }, 661 "xtest only": { 662 fileRoot: j("xt"), 663 importRoot: "simple", 664 out: PackageTree{ 665 ImportRoot: "simple", 666 Packages: map[string]PackageOrErr{ 667 "simple": { 668 P: Package{ 669 ImportPath: "simple", 670 CommentPath: "", 671 Name: "simple", 672 Imports: []string{}, 673 TestImports: []string{ 674 "sort", 675 "strconv", 676 }, 677 }, 678 }, 679 }, 680 }, 681 }, 682 "code and test": { 683 fileRoot: j("simplet"), 684 importRoot: "simple", 685 out: PackageTree{ 686 ImportRoot: "simple", 687 Packages: map[string]PackageOrErr{ 688 "simple": { 689 P: Package{ 690 ImportPath: "simple", 691 CommentPath: "", 692 Name: "simple", 693 Imports: []string{ 694 "github.com/golang/dep/gps", 695 "sort", 696 }, 697 TestImports: []string{ 698 "math/rand", 699 "strconv", 700 }, 701 }, 702 }, 703 }, 704 }, 705 }, 706 "code and xtest": { 707 fileRoot: j("simplext"), 708 importRoot: "simple", 709 out: PackageTree{ 710 ImportRoot: "simple", 711 Packages: map[string]PackageOrErr{ 712 "simple": { 713 P: Package{ 714 ImportPath: "simple", 715 CommentPath: "", 716 Name: "simple", 717 Imports: []string{ 718 "github.com/golang/dep/gps", 719 "sort", 720 }, 721 TestImports: []string{ 722 "sort", 723 "strconv", 724 }, 725 }, 726 }, 727 }, 728 }, 729 }, 730 "code, test, xtest": { 731 fileRoot: j("simpleallt"), 732 importRoot: "simple", 733 out: PackageTree{ 734 ImportRoot: "simple", 735 Packages: map[string]PackageOrErr{ 736 "simple": { 737 P: Package{ 738 ImportPath: "simple", 739 CommentPath: "", 740 Name: "simple", 741 Imports: []string{ 742 "github.com/golang/dep/gps", 743 "sort", 744 }, 745 TestImports: []string{ 746 "math/rand", 747 "sort", 748 "strconv", 749 }, 750 }, 751 }, 752 }, 753 }, 754 }, 755 "one pkg multifile": { 756 fileRoot: j("m1p"), 757 importRoot: "m1p", 758 out: PackageTree{ 759 ImportRoot: "m1p", 760 Packages: map[string]PackageOrErr{ 761 "m1p": { 762 P: Package{ 763 ImportPath: "m1p", 764 CommentPath: "", 765 Name: "m1p", 766 Imports: []string{ 767 "github.com/golang/dep/gps", 768 "os", 769 "sort", 770 }, 771 }, 772 }, 773 }, 774 }, 775 }, 776 "one nested below": { 777 fileRoot: j("nest"), 778 importRoot: "nest", 779 out: PackageTree{ 780 ImportRoot: "nest", 781 Packages: map[string]PackageOrErr{ 782 "nest": { 783 P: Package{ 784 ImportPath: "nest", 785 CommentPath: "", 786 Name: "simple", 787 Imports: []string{ 788 "github.com/golang/dep/gps", 789 "sort", 790 }, 791 }, 792 }, 793 "nest/m1p": { 794 P: Package{ 795 ImportPath: "nest/m1p", 796 CommentPath: "", 797 Name: "m1p", 798 Imports: []string{ 799 "github.com/golang/dep/gps", 800 "os", 801 "sort", 802 }, 803 }, 804 }, 805 }, 806 }, 807 }, 808 "malformed go file": { 809 fileRoot: j("bad"), 810 importRoot: "bad", 811 out: PackageTree{ 812 ImportRoot: "bad", 813 Packages: map[string]PackageOrErr{ 814 "bad": { 815 Err: scanner.ErrorList{ 816 &scanner.Error{ 817 Pos: token.Position{ 818 Filename: j("bad", "bad.go"), 819 Offset: 273, 820 Line: 6, 821 Column: 43, 822 }, 823 Msg: "expected 'package', found 'EOF'", 824 }, 825 }, 826 }, 827 }, 828 }, 829 }, 830 "two nested under empty root": { 831 fileRoot: j("ren"), 832 importRoot: "ren", 833 out: PackageTree{ 834 ImportRoot: "ren", 835 Packages: map[string]PackageOrErr{ 836 "ren": { 837 Err: &build.NoGoError{ 838 Dir: j("ren"), 839 }, 840 }, 841 "ren/m1p": { 842 P: Package{ 843 ImportPath: "ren/m1p", 844 CommentPath: "", 845 Name: "m1p", 846 Imports: []string{ 847 "github.com/golang/dep/gps", 848 "os", 849 "sort", 850 }, 851 }, 852 }, 853 "ren/simple": { 854 P: Package{ 855 ImportPath: "ren/simple", 856 CommentPath: "", 857 Name: "simple", 858 Imports: []string{ 859 "github.com/golang/dep/gps", 860 "sort", 861 }, 862 }, 863 }, 864 }, 865 }, 866 }, 867 "internal name mismatch": { 868 fileRoot: j("doublenest"), 869 importRoot: "doublenest", 870 out: PackageTree{ 871 ImportRoot: "doublenest", 872 Packages: map[string]PackageOrErr{ 873 "doublenest": { 874 P: Package{ 875 ImportPath: "doublenest", 876 CommentPath: "", 877 Name: "base", 878 Imports: []string{ 879 "github.com/golang/dep/gps", 880 "go/parser", 881 }, 882 }, 883 }, 884 "doublenest/namemismatch": { 885 P: Package{ 886 ImportPath: "doublenest/namemismatch", 887 CommentPath: "", 888 Name: "nm", 889 Imports: []string{ 890 "github.com/Masterminds/semver", 891 "os", 892 }, 893 }, 894 }, 895 "doublenest/namemismatch/m1p": { 896 P: Package{ 897 ImportPath: "doublenest/namemismatch/m1p", 898 CommentPath: "", 899 Name: "m1p", 900 Imports: []string{ 901 "github.com/golang/dep/gps", 902 "os", 903 "sort", 904 }, 905 }, 906 }, 907 }, 908 }, 909 }, 910 "file and importroot mismatch": { 911 fileRoot: j("doublenest"), 912 importRoot: "other", 913 out: PackageTree{ 914 ImportRoot: "other", 915 Packages: map[string]PackageOrErr{ 916 "other": { 917 P: Package{ 918 ImportPath: "other", 919 CommentPath: "", 920 Name: "base", 921 Imports: []string{ 922 "github.com/golang/dep/gps", 923 "go/parser", 924 }, 925 }, 926 }, 927 "other/namemismatch": { 928 P: Package{ 929 ImportPath: "other/namemismatch", 930 CommentPath: "", 931 Name: "nm", 932 Imports: []string{ 933 "github.com/Masterminds/semver", 934 "os", 935 }, 936 }, 937 }, 938 "other/namemismatch/m1p": { 939 P: Package{ 940 ImportPath: "other/namemismatch/m1p", 941 CommentPath: "", 942 Name: "m1p", 943 Imports: []string{ 944 "github.com/golang/dep/gps", 945 "os", 946 "sort", 947 }, 948 }, 949 }, 950 }, 951 }, 952 }, 953 "code and ignored main": { 954 fileRoot: j("igmain"), 955 importRoot: "simple", 956 out: PackageTree{ 957 ImportRoot: "simple", 958 Packages: map[string]PackageOrErr{ 959 "simple": { 960 P: Package{ 961 ImportPath: "simple", 962 CommentPath: "", 963 Name: "simple", 964 Imports: []string{ 965 "github.com/golang/dep/gps", 966 "sort", 967 "unicode", 968 }, 969 }, 970 }, 971 }, 972 }, 973 }, 974 "code and ignored main, order check": { 975 fileRoot: j("igmainfirst"), 976 importRoot: "simple", 977 out: PackageTree{ 978 ImportRoot: "simple", 979 Packages: map[string]PackageOrErr{ 980 "simple": { 981 P: Package{ 982 ImportPath: "simple", 983 CommentPath: "", 984 Name: "simple", 985 Imports: []string{ 986 "github.com/golang/dep/gps", 987 "sort", 988 "unicode", 989 }, 990 }, 991 }, 992 }, 993 }, 994 }, 995 "code and ignored main with comment leader": { 996 fileRoot: j("igmainlong"), 997 importRoot: "simple", 998 out: PackageTree{ 999 ImportRoot: "simple", 1000 Packages: map[string]PackageOrErr{ 1001 "simple": { 1002 P: Package{ 1003 ImportPath: "simple", 1004 CommentPath: "", 1005 Name: "simple", 1006 Imports: []string{ 1007 "github.com/golang/dep/gps", 1008 "sort", 1009 "unicode", 1010 }, 1011 }, 1012 }, 1013 }, 1014 }, 1015 }, 1016 "code, tests, and ignored main": { 1017 fileRoot: j("igmaint"), 1018 importRoot: "simple", 1019 out: PackageTree{ 1020 ImportRoot: "simple", 1021 Packages: map[string]PackageOrErr{ 1022 "simple": { 1023 P: Package{ 1024 ImportPath: "simple", 1025 CommentPath: "", 1026 Name: "simple", 1027 Imports: []string{ 1028 "github.com/golang/dep/gps", 1029 "sort", 1030 "unicode", 1031 }, 1032 TestImports: []string{ 1033 "math/rand", 1034 "strconv", 1035 }, 1036 }, 1037 }, 1038 }, 1039 }, 1040 }, 1041 // imports a missing pkg 1042 "missing import": { 1043 fileRoot: j("missing"), 1044 importRoot: "missing", 1045 out: PackageTree{ 1046 ImportRoot: "missing", 1047 Packages: map[string]PackageOrErr{ 1048 "missing": { 1049 P: Package{ 1050 ImportPath: "missing", 1051 CommentPath: "", 1052 Name: "simple", 1053 Imports: []string{ 1054 "github.com/golang/dep/gps", 1055 "missing/missing", 1056 "sort", 1057 }, 1058 }, 1059 }, 1060 "missing/m1p": { 1061 P: Package{ 1062 ImportPath: "missing/m1p", 1063 CommentPath: "", 1064 Name: "m1p", 1065 Imports: []string{ 1066 "github.com/golang/dep/gps", 1067 "os", 1068 "sort", 1069 }, 1070 }, 1071 }, 1072 }, 1073 }, 1074 }, 1075 // import cycle of three packages. ListPackages doesn't do anything 1076 // special with cycles - that's the reach calculator's job - so this is 1077 // error-free 1078 "import cycle, len 3": { 1079 fileRoot: j("cycle"), 1080 importRoot: "cycle", 1081 out: PackageTree{ 1082 ImportRoot: "cycle", 1083 Packages: map[string]PackageOrErr{ 1084 "cycle": { 1085 P: Package{ 1086 ImportPath: "cycle", 1087 CommentPath: "", 1088 Name: "cycle", 1089 Imports: []string{ 1090 "cycle/one", 1091 "github.com/golang/dep/gps", 1092 }, 1093 }, 1094 }, 1095 "cycle/one": { 1096 P: Package{ 1097 ImportPath: "cycle/one", 1098 CommentPath: "", 1099 Name: "one", 1100 Imports: []string{ 1101 "cycle/two", 1102 "github.com/golang/dep/gps", 1103 }, 1104 }, 1105 }, 1106 "cycle/two": { 1107 P: Package{ 1108 ImportPath: "cycle/two", 1109 CommentPath: "", 1110 Name: "two", 1111 Imports: []string{ 1112 "cycle", 1113 "github.com/golang/dep/gps", 1114 }, 1115 }, 1116 }, 1117 }, 1118 }, 1119 }, 1120 // has disallowed dir names 1121 "disallowed dirs": { 1122 fileRoot: j("disallow"), 1123 importRoot: "disallow", 1124 out: PackageTree{ 1125 ImportRoot: "disallow", 1126 Packages: map[string]PackageOrErr{ 1127 "disallow": { 1128 P: Package{ 1129 ImportPath: "disallow", 1130 CommentPath: "", 1131 Name: "disallow", 1132 Imports: []string{ 1133 "disallow/testdata", 1134 "github.com/golang/dep/gps", 1135 "sort", 1136 }, 1137 }, 1138 }, 1139 "disallow/testdata": { 1140 P: Package{ 1141 ImportPath: "disallow/testdata", 1142 CommentPath: "", 1143 Name: "testdata", 1144 Imports: []string{ 1145 "hash", 1146 }, 1147 }, 1148 }, 1149 }, 1150 }, 1151 }, 1152 "relative imports": { 1153 fileRoot: j("relimport"), 1154 importRoot: "relimport", 1155 out: PackageTree{ 1156 ImportRoot: "relimport", 1157 Packages: map[string]PackageOrErr{ 1158 "relimport": { 1159 P: Package{ 1160 ImportPath: "relimport", 1161 CommentPath: "", 1162 Name: "relimport", 1163 Imports: []string{ 1164 "sort", 1165 }, 1166 }, 1167 }, 1168 "relimport/dot": { 1169 P: Package{ 1170 ImportPath: "relimport/dot", 1171 CommentPath: "", 1172 Name: "dot", 1173 Imports: []string{ 1174 ".", 1175 "sort", 1176 }, 1177 }, 1178 }, 1179 "relimport/dotdot": { 1180 Err: &LocalImportsError{ 1181 Dir: j("relimport/dotdot"), 1182 ImportPath: "relimport/dotdot", 1183 LocalImports: []string{ 1184 "..", 1185 }, 1186 }, 1187 }, 1188 "relimport/dotslash": { 1189 Err: &LocalImportsError{ 1190 Dir: j("relimport/dotslash"), 1191 ImportPath: "relimport/dotslash", 1192 LocalImports: []string{ 1193 "./simple", 1194 }, 1195 }, 1196 }, 1197 "relimport/dotdotslash": { 1198 Err: &LocalImportsError{ 1199 Dir: j("relimport/dotdotslash"), 1200 ImportPath: "relimport/dotdotslash", 1201 LocalImports: []string{ 1202 "../github.com/golang/dep/gps", 1203 }, 1204 }, 1205 }, 1206 }, 1207 }, 1208 }, 1209 "skip underscore": { 1210 fileRoot: j("skip_"), 1211 importRoot: "skip_", 1212 out: PackageTree{ 1213 ImportRoot: "skip_", 1214 Packages: map[string]PackageOrErr{ 1215 "skip_": { 1216 P: Package{ 1217 ImportPath: "skip_", 1218 CommentPath: "", 1219 Name: "skip", 1220 Imports: []string{ 1221 "github.com/golang/dep/gps", 1222 "sort", 1223 }, 1224 }, 1225 }, 1226 }, 1227 }, 1228 }, 1229 // This case mostly exists for the PackageTree methods, but it does 1230 // cover a bit of range 1231 "varied": { 1232 fileRoot: j("varied"), 1233 importRoot: "varied", 1234 out: PackageTree{ 1235 ImportRoot: "varied", 1236 Packages: map[string]PackageOrErr{ 1237 "varied": { 1238 P: Package{ 1239 ImportPath: "varied", 1240 CommentPath: "", 1241 Name: "main", 1242 Imports: []string{ 1243 "net/http", 1244 "varied/namemismatch", 1245 "varied/otherpath", 1246 "varied/simple", 1247 }, 1248 }, 1249 }, 1250 "varied/otherpath": { 1251 P: Package{ 1252 ImportPath: "varied/otherpath", 1253 CommentPath: "", 1254 Name: "otherpath", 1255 Imports: []string{}, 1256 TestImports: []string{ 1257 "varied/m1p", 1258 }, 1259 }, 1260 }, 1261 "varied/simple": { 1262 P: Package{ 1263 ImportPath: "varied/simple", 1264 CommentPath: "", 1265 Name: "simple", 1266 Imports: []string{ 1267 "github.com/golang/dep/gps", 1268 "go/parser", 1269 "varied/simple/another", 1270 }, 1271 }, 1272 }, 1273 "varied/simple/another": { 1274 P: Package{ 1275 ImportPath: "varied/simple/another", 1276 CommentPath: "", 1277 Name: "another", 1278 Imports: []string{ 1279 "hash", 1280 "varied/m1p", 1281 }, 1282 TestImports: []string{ 1283 "encoding/binary", 1284 }, 1285 }, 1286 }, 1287 "varied/namemismatch": { 1288 P: Package{ 1289 ImportPath: "varied/namemismatch", 1290 CommentPath: "", 1291 Name: "nm", 1292 Imports: []string{ 1293 "github.com/Masterminds/semver", 1294 "os", 1295 }, 1296 }, 1297 }, 1298 "varied/m1p": { 1299 P: Package{ 1300 ImportPath: "varied/m1p", 1301 CommentPath: "", 1302 Name: "m1p", 1303 Imports: []string{ 1304 "github.com/golang/dep/gps", 1305 "os", 1306 "sort", 1307 }, 1308 }, 1309 }, 1310 }, 1311 }, 1312 }, 1313 "varied with hidden dirs": { 1314 fileRoot: j("varied_hidden"), 1315 importRoot: "varied", 1316 out: PackageTree{ 1317 ImportRoot: "varied", 1318 Packages: map[string]PackageOrErr{ 1319 "varied": { 1320 P: Package{ 1321 ImportPath: "varied", 1322 CommentPath: "", 1323 Name: "main", 1324 Imports: []string{ 1325 "net/http", 1326 "varied/_frommain", 1327 "varied/simple", 1328 }, 1329 }, 1330 }, 1331 "varied/always": { 1332 P: Package{ 1333 ImportPath: "varied/always", 1334 CommentPath: "", 1335 Name: "always", 1336 Imports: []string{}, 1337 TestImports: []string{ 1338 "varied/.onlyfromtests", 1339 }, 1340 }, 1341 }, 1342 "varied/.onlyfromtests": { 1343 P: Package{ 1344 ImportPath: "varied/.onlyfromtests", 1345 CommentPath: "", 1346 Name: "onlyfromtests", 1347 Imports: []string{ 1348 "github.com/golang/dep/gps", 1349 "os", 1350 "sort", 1351 "varied/_secondorder", 1352 }, 1353 }, 1354 }, 1355 "varied/simple": { 1356 P: Package{ 1357 ImportPath: "varied/simple", 1358 CommentPath: "", 1359 Name: "simple", 1360 Imports: []string{ 1361 "github.com/golang/dep/gps", 1362 "go/parser", 1363 "varied/simple/testdata", 1364 }, 1365 }, 1366 }, 1367 "varied/simple/testdata": { 1368 P: Package{ 1369 ImportPath: "varied/simple/testdata", 1370 CommentPath: "", 1371 Name: "testdata", 1372 Imports: []string{ 1373 "varied/dotdotslash", 1374 }, 1375 }, 1376 }, 1377 "varied/_secondorder": { 1378 P: Package{ 1379 ImportPath: "varied/_secondorder", 1380 CommentPath: "", 1381 Name: "secondorder", 1382 Imports: []string{ 1383 "hash", 1384 }, 1385 }, 1386 }, 1387 "varied/_never": { 1388 P: Package{ 1389 ImportPath: "varied/_never", 1390 CommentPath: "", 1391 Name: "never", 1392 Imports: []string{ 1393 "github.com/golang/dep/gps", 1394 "sort", 1395 }, 1396 }, 1397 }, 1398 "varied/_frommain": { 1399 P: Package{ 1400 ImportPath: "varied/_frommain", 1401 CommentPath: "", 1402 Name: "frommain", 1403 Imports: []string{ 1404 "github.com/golang/dep/gps", 1405 "sort", 1406 }, 1407 }, 1408 }, 1409 "varied/dotdotslash": { 1410 Err: &LocalImportsError{ 1411 Dir: j("varied_hidden/dotdotslash"), 1412 ImportPath: "varied/dotdotslash", 1413 LocalImports: []string{ 1414 "../github.com/golang/dep/gps", 1415 }, 1416 }, 1417 }, 1418 }, 1419 }, 1420 }, 1421 "invalid buildtag like comments should be ignored": { 1422 fileRoot: j("buildtag"), 1423 importRoot: "buildtag", 1424 out: PackageTree{ 1425 ImportRoot: "buildtag", 1426 Packages: map[string]PackageOrErr{ 1427 "buildtag": { 1428 P: Package{ 1429 ImportPath: "buildtag", 1430 CommentPath: "", 1431 Name: "buildtag", 1432 Imports: []string{ 1433 "sort", 1434 }, 1435 }, 1436 }, 1437 }, 1438 }, 1439 }, 1440 "does not skip directories starting with '.'": { 1441 fileRoot: j("dotgodir"), 1442 importRoot: "dotgodir", 1443 out: PackageTree{ 1444 ImportRoot: "dotgodir", 1445 Packages: map[string]PackageOrErr{ 1446 "dotgodir": { 1447 P: Package{ 1448 ImportPath: "dotgodir", 1449 Imports: []string{}, 1450 }, 1451 }, 1452 "dotgodir/.go": { 1453 P: Package{ 1454 ImportPath: "dotgodir/.go", 1455 Name: "dot", 1456 Imports: []string{}, 1457 }, 1458 }, 1459 "dotgodir/foo.go": { 1460 P: Package{ 1461 ImportPath: "dotgodir/foo.go", 1462 Name: "foo", 1463 Imports: []string{"sort"}, 1464 }, 1465 }, 1466 "dotgodir/.m1p": { 1467 P: Package{ 1468 ImportPath: "dotgodir/.m1p", 1469 CommentPath: "", 1470 Name: "m1p", 1471 Imports: []string{ 1472 "github.com/golang/dep/gps", 1473 "os", 1474 "sort", 1475 }, 1476 }, 1477 }, 1478 }, 1479 }, 1480 }, 1481 "canonical": { 1482 fileRoot: j("canonical"), 1483 importRoot: "canonical", 1484 out: PackageTree{ 1485 ImportRoot: "canonical", 1486 Packages: map[string]PackageOrErr{ 1487 "canonical": { 1488 P: Package{ 1489 ImportPath: "canonical", 1490 CommentPath: "canonical", 1491 Name: "pkg", 1492 Imports: []string{}, 1493 }, 1494 }, 1495 "canonical/sub": { 1496 P: Package{ 1497 ImportPath: "canonical/sub", 1498 CommentPath: "canonical/subpackage", 1499 Name: "sub", 1500 Imports: []string{}, 1501 }, 1502 }, 1503 }, 1504 }, 1505 }, 1506 "conflicting canonical comments": { 1507 fileRoot: j("canon_confl"), 1508 importRoot: "canon_confl", 1509 out: PackageTree{ 1510 ImportRoot: "canon_confl", 1511 Packages: map[string]PackageOrErr{ 1512 "canon_confl": { 1513 Err: &ConflictingImportComments{ 1514 ImportPath: "canon_confl", 1515 ConflictingImportComments: []string{ 1516 "vanity1", 1517 "vanity2", 1518 }, 1519 }, 1520 }, 1521 }, 1522 }, 1523 }, 1524 "non-canonical": { 1525 fileRoot: j("canonical"), 1526 importRoot: "noncanonical", 1527 out: PackageTree{ 1528 ImportRoot: "noncanonical", 1529 Packages: map[string]PackageOrErr{ 1530 "noncanonical": { 1531 Err: &NonCanonicalImportRoot{ 1532 ImportRoot: "noncanonical", 1533 Canonical: "canonical", 1534 }, 1535 }, 1536 "noncanonical/sub": { 1537 Err: &NonCanonicalImportRoot{ 1538 ImportRoot: "noncanonical", 1539 Canonical: "canonical/subpackage", 1540 }, 1541 }, 1542 }, 1543 }, 1544 }, 1545 "slash-star": { 1546 fileRoot: j("slash-star_confl"), 1547 importRoot: "slash-star_confl", 1548 out: PackageTree{ 1549 ImportRoot: "slash-star_confl", 1550 Packages: map[string]PackageOrErr{ 1551 "slash-star_confl": { 1552 Err: &ConflictingImportComments{ 1553 ImportPath: "slash-star_confl", 1554 ConflictingImportComments: []string{ 1555 "vanity1", 1556 "vanity2", 1557 }, 1558 }, 1559 }, 1560 }, 1561 }, 1562 }, 1563 } 1564 1565 for name, fix := range table { 1566 t.Run(name, func(t *testing.T) { 1567 if _, err := os.Stat(fix.fileRoot); err != nil { 1568 t.Errorf("error on fileRoot %s: %s", fix.fileRoot, err) 1569 } 1570 1571 out, err := ListPackages(fix.fileRoot, fix.importRoot) 1572 1573 if err != nil && fix.err == nil { 1574 t.Errorf("Received error but none expected: %s", err) 1575 } else if fix.err != nil && err == nil { 1576 t.Errorf("Error expected but none received") 1577 } else if fix.err != nil && err != nil { 1578 if !reflect.DeepEqual(fix.err, err) { 1579 t.Errorf("Did not receive expected error:\n\t(GOT): %s\n\t(WNT): %s", err, fix.err) 1580 } 1581 } 1582 1583 if fix.out.ImportRoot != "" && fix.out.Packages != nil { 1584 if !reflect.DeepEqual(out, fix.out) { 1585 if fix.out.ImportRoot != out.ImportRoot { 1586 t.Errorf("Expected ImportRoot %s, got %s", fix.out.ImportRoot, out.ImportRoot) 1587 } 1588 1589 // overwrite the out one to see if we still have a real problem 1590 out.ImportRoot = fix.out.ImportRoot 1591 1592 if !reflect.DeepEqual(out, fix.out) { 1593 // TODO (kris-nova) We need to disable this bypass here, and in the .travis.yml 1594 // as soon as dep#501 is fixed 1595 bypass := os.Getenv("DEPTESTBYPASS501") 1596 if bypass != "" { 1597 t.Log("bypassing fix.out.Packages check < 2") 1598 } else { 1599 if len(fix.out.Packages) < 2 { 1600 t.Errorf("Did not get expected PackageOrErrs:\n\t(GOT): %#v\n\t(WNT): %#v", out, fix.out) 1601 } else { 1602 seen := make(map[string]bool) 1603 for path, perr := range fix.out.Packages { 1604 seen[path] = true 1605 if operr, exists := out.Packages[path]; !exists { 1606 t.Errorf("Expected PackageOrErr for path %s was missing from output:\n\t%s", path, perr) 1607 } else { 1608 if !reflect.DeepEqual(perr, operr) { 1609 t.Errorf("PkgOrErr for path %s was not as expected:\n\t(GOT): %#v\n\t(WNT): %#v", path, operr, perr) 1610 } 1611 } 1612 } 1613 1614 for path, operr := range out.Packages { 1615 if seen[path] { 1616 continue 1617 } 1618 1619 t.Errorf("Got PackageOrErr for path %s, but none was expected:\n\t%s", path, operr) 1620 } 1621 } 1622 } 1623 } 1624 } 1625 } 1626 }) 1627 } 1628 } 1629 1630 // Transform Table Test that operates solely on the varied_hidden fixture. 1631 func TestTrimHiddenPackages(t *testing.T) { 1632 base, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "varied_hidden"), "varied") 1633 if err != nil { 1634 panic(err) 1635 } 1636 1637 table := map[string]struct { 1638 main, tests bool // literal params to TrimHiddenPackages 1639 ignore []string // transformed into IgnoredRuleset param to TrimHiddenPackages 1640 trimmed []string // list of packages that should be trimmed in result PackageTree 1641 }{ 1642 // All of these implicitly verify that the varied/_never pkg is always 1643 // trimmed, and that the varied/dotdotslash pkg is not trimmed even 1644 // though it has errors. 1645 "minimal trim": { 1646 main: true, 1647 tests: true, 1648 }, 1649 "ignore simple, lose testdata": { 1650 main: true, 1651 tests: true, 1652 ignore: []string{"simple"}, 1653 trimmed: []string{"simple", "simple/testdata"}, 1654 }, 1655 "no tests": { 1656 main: true, 1657 tests: false, 1658 trimmed: []string{".onlyfromtests", "_secondorder"}, 1659 }, 1660 "ignore a reachable hidden": { 1661 main: true, 1662 tests: true, 1663 ignore: []string{"_secondorder"}, 1664 trimmed: []string{"_secondorder"}, 1665 }, 1666 "ignore a reachable hidden with another hidden solely reachable through it": { 1667 main: true, 1668 tests: true, 1669 ignore: []string{".onlyfromtests"}, 1670 trimmed: []string{".onlyfromtests", "_secondorder"}, 1671 }, 1672 "no main": { 1673 main: false, 1674 tests: true, 1675 trimmed: []string{"", "_frommain"}, 1676 }, 1677 "no main or tests": { 1678 main: false, 1679 tests: false, 1680 trimmed: []string{"", "_frommain", ".onlyfromtests", "_secondorder"}, 1681 }, 1682 "no main or tests and ignore simple": { 1683 main: false, 1684 tests: false, 1685 ignore: []string{"simple"}, 1686 trimmed: []string{"", "_frommain", ".onlyfromtests", "_secondorder", "simple", "simple/testdata"}, 1687 }, 1688 } 1689 1690 for name, fix := range table { 1691 t.Run(name, func(t *testing.T) { 1692 want := base.Copy() 1693 1694 var ig []string 1695 for _, v := range fix.ignore { 1696 ig = append(ig, path.Join("varied", v)) 1697 } 1698 got := base.TrimHiddenPackages(fix.main, fix.tests, NewIgnoredRuleset(ig)) 1699 1700 for _, ip := range append(fix.trimmed, "_never") { 1701 ip = path.Join("varied", ip) 1702 if _, has := want.Packages[ip]; !has { 1703 panic(fmt.Sprintf("bad input, %s does not exist in fixture ptree", ip)) 1704 } 1705 delete(want.Packages, ip) 1706 } 1707 1708 if !reflect.DeepEqual(want, got) { 1709 if len(want.Packages) < 2 { 1710 t.Errorf("Did not get expected PackageOrErrs:\n\t(GOT): %#v\n\t(WNT): %#v", got, want) 1711 } else { 1712 seen := make(map[string]bool) 1713 for path, perr := range want.Packages { 1714 seen[path] = true 1715 if operr, exists := got.Packages[path]; !exists { 1716 t.Errorf("Expected PackageOrErr for path %s was missing from output:\n\t%s", path, perr) 1717 } else { 1718 if !reflect.DeepEqual(perr, operr) { 1719 t.Errorf("PkgOrErr for path %s was not as expected:\n\t(GOT): %#v\n\t(WNT): %#v", path, operr, perr) 1720 } 1721 } 1722 } 1723 1724 for path, operr := range got.Packages { 1725 if seen[path] { 1726 continue 1727 } 1728 1729 t.Errorf("Got PackageOrErr for path %s, but none was expected:\n\t%s", path, operr) 1730 } 1731 } 1732 } 1733 }) 1734 } 1735 } 1736 1737 // Test that ListPackages skips directories for which it lacks permissions to 1738 // enter and files it lacks permissions to read. 1739 func TestListPackagesNoPerms(t *testing.T) { 1740 if runtime.GOOS == "windows" { 1741 // TODO This test doesn't work on windows because I wasn't able to easily 1742 // figure out how to chmod a dir in a way that made it untraversable. 1743 // 1744 // It's not a big deal, though, because the os.IsPermission() call we 1745 // use in the real code is effectively what's being tested here, and 1746 // that's designed to be cross-platform. So, if the unix tests pass, we 1747 // have every reason to believe windows tests would too, if the situation 1748 // arises. 1749 t.Skip() 1750 } 1751 tmp, err := ioutil.TempDir("", "listpkgsnp") 1752 if err != nil { 1753 t.Fatalf("Failed to create temp dir: %s", err) 1754 } 1755 defer os.RemoveAll(tmp) 1756 1757 srcdir := filepath.Join(getTestdataRootDir(t), "src", "ren") 1758 workdir := filepath.Join(tmp, "ren") 1759 fs.CopyDir(srcdir, workdir) 1760 1761 // chmod the simple dir and m1p/b.go file so they can't be read 1762 err = os.Chmod(filepath.Join(workdir, "simple"), 0) 1763 if err != nil { 1764 t.Fatalf("Error while chmodding simple dir: %s", err) 1765 } 1766 os.Chmod(filepath.Join(workdir, "m1p", "b.go"), 0) 1767 if err != nil { 1768 t.Fatalf("Error while chmodding b.go file: %s", err) 1769 } 1770 1771 want := PackageTree{ 1772 ImportRoot: "ren", 1773 Packages: map[string]PackageOrErr{ 1774 "ren": { 1775 Err: &build.NoGoError{ 1776 Dir: workdir, 1777 }, 1778 }, 1779 "ren/m1p": { 1780 P: Package{ 1781 ImportPath: "ren/m1p", 1782 CommentPath: "", 1783 Name: "m1p", 1784 Imports: []string{ 1785 "github.com/golang/dep/gps", 1786 "sort", 1787 }, 1788 }, 1789 }, 1790 }, 1791 } 1792 1793 got, err := ListPackages(workdir, "ren") 1794 1795 if err != nil { 1796 t.Fatalf("Unexpected err from ListPackages: %s", err) 1797 } 1798 if want.ImportRoot != got.ImportRoot { 1799 t.Fatalf("Expected ImportRoot %s, got %s", want.ImportRoot, got.ImportRoot) 1800 } 1801 1802 if !reflect.DeepEqual(got, want) { 1803 t.Errorf("Did not get expected PackageOrErrs:\n\t(GOT): %#v\n\t(WNT): %#v", got, want) 1804 if len(got.Packages) != 2 { 1805 if len(got.Packages) == 3 { 1806 t.Error("Wrong number of PackageOrErrs - did 'simple' subpackage make it into results somehow?") 1807 } else { 1808 t.Error("Wrong number of PackageOrErrs") 1809 } 1810 } 1811 1812 if got.Packages["ren"].Err == nil { 1813 t.Error("Should have gotten error on empty root directory") 1814 } 1815 1816 if !reflect.DeepEqual(got.Packages["ren/m1p"].P.Imports, want.Packages["ren/m1p"].P.Imports) { 1817 t.Error("Mismatch between imports in m1p") 1818 } 1819 } 1820 } 1821 1822 func TestToReachMap(t *testing.T) { 1823 // There's enough in the 'varied' test case to test most of what matters 1824 vptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "github.com", "example", "varied"), "github.com/example/varied") 1825 if err != nil { 1826 t.Fatalf("ListPackages failed on varied test case: %s", err) 1827 } 1828 1829 // Helper to add github.com/varied/example prefix 1830 b := func(s string) string { 1831 if s == "" { 1832 return "github.com/example/varied" 1833 } 1834 return "github.com/example/varied/" + s 1835 } 1836 bl := func(parts ...string) string { 1837 for k, s := range parts { 1838 parts[k] = b(s) 1839 } 1840 return strings.Join(parts, " ") 1841 } 1842 1843 // Set up vars for validate closure 1844 var want ReachMap 1845 var name string 1846 var main, tests bool 1847 var ignore []string 1848 1849 validate := func() { 1850 got, em := vptree.ToReachMap(main, tests, true, NewIgnoredRuleset(ignore)) 1851 if len(em) != 0 { 1852 t.Errorf("Should not have any error packages from ToReachMap, got %s", em) 1853 } 1854 if !reflect.DeepEqual(want, got) { 1855 seen := make(map[string]bool) 1856 for ip, wantie := range want { 1857 seen[ip] = true 1858 if gotie, exists := got[ip]; !exists { 1859 t.Errorf("ver(%q): expected import path %s was not present in result", name, ip) 1860 } else { 1861 if !reflect.DeepEqual(wantie, gotie) { 1862 t.Errorf("ver(%q): did not get expected import set for pkg %s:\n\t(GOT): %#v\n\t(WNT): %#v", name, ip, gotie, wantie) 1863 } 1864 } 1865 } 1866 1867 for ip, ie := range got { 1868 if seen[ip] { 1869 continue 1870 } 1871 t.Errorf("ver(%q): Got packages for import path %s, but none were expected:\n\t%s", name, ip, ie) 1872 } 1873 } 1874 } 1875 1876 // maps of each internal package, and their expected external and internal 1877 // imports in the maximal case. 1878 allex := map[string][]string{ 1879 b(""): {"encoding/binary", "github.com/Masterminds/semver", "github.com/golang/dep/gps", "go/parser", "hash", "net/http", "os", "sort"}, 1880 b("m1p"): {"github.com/golang/dep/gps", "os", "sort"}, 1881 b("namemismatch"): {"github.com/Masterminds/semver", "os"}, 1882 b("otherpath"): {"github.com/golang/dep/gps", "os", "sort"}, 1883 b("simple"): {"encoding/binary", "github.com/golang/dep/gps", "go/parser", "hash", "os", "sort"}, 1884 b("simple/another"): {"encoding/binary", "github.com/golang/dep/gps", "hash", "os", "sort"}, 1885 } 1886 1887 allin := map[string][]string{ 1888 b(""): {b("m1p"), b("namemismatch"), b("otherpath"), b("simple"), b("simple/another")}, 1889 b("m1p"): {}, 1890 b("namemismatch"): {}, 1891 b("otherpath"): {b("m1p")}, 1892 b("simple"): {b("m1p"), b("simple/another")}, 1893 b("simple/another"): {b("m1p")}, 1894 } 1895 1896 // build a map to validate the exception inputs. do this because shit is 1897 // hard enough to keep track of that it's preferable not to have silent 1898 // success if a typo creeps in and we're trying to except an import that 1899 // isn't in a pkg in the first place 1900 valid := make(map[string]map[string]bool) 1901 for ip, expkgs := range allex { 1902 m := make(map[string]bool) 1903 for _, pkg := range expkgs { 1904 m[pkg] = true 1905 } 1906 valid[ip] = m 1907 } 1908 validin := make(map[string]map[string]bool) 1909 for ip, inpkgs := range allin { 1910 m := make(map[string]bool) 1911 for _, pkg := range inpkgs { 1912 m[pkg] = true 1913 } 1914 validin[ip] = m 1915 } 1916 1917 // helper to compose want, excepting specific packages 1918 // 1919 // this makes it easier to see what we're taking out on each test 1920 except := func(pkgig ...string) { 1921 // reinit expect with everything from all 1922 want = make(ReachMap) 1923 for ip, expkgs := range allex { 1924 var ie struct{ Internal, External []string } 1925 1926 inpkgs := allin[ip] 1927 lenex, lenin := len(expkgs), len(inpkgs) 1928 if lenex > 0 { 1929 ie.External = make([]string, len(expkgs)) 1930 copy(ie.External, expkgs) 1931 } 1932 1933 if lenin > 0 { 1934 ie.Internal = make([]string, len(inpkgs)) 1935 copy(ie.Internal, inpkgs) 1936 } 1937 1938 want[ip] = ie 1939 } 1940 1941 // now build the dropmap 1942 drop := make(map[string]map[string]bool) 1943 for _, igstr := range pkgig { 1944 // split on space; first elem is import path to pkg, the rest are 1945 // the imports to drop. 1946 not := strings.Split(igstr, " ") 1947 var ip string 1948 ip, not = not[0], not[1:] 1949 if _, exists := valid[ip]; !exists { 1950 t.Fatalf("%s is not a package name we're working with, doofus", ip) 1951 } 1952 1953 // if only a single elem was passed, though, drop the whole thing 1954 if len(not) == 0 { 1955 delete(want, ip) 1956 continue 1957 } 1958 1959 m := make(map[string]bool) 1960 for _, imp := range not { 1961 if strings.HasPrefix(imp, "github.com/example/varied") { 1962 if !validin[ip][imp] { 1963 t.Fatalf("%s is not a reachable import of %s, even in the all case", imp, ip) 1964 } 1965 } else { 1966 if !valid[ip][imp] { 1967 t.Fatalf("%s is not a reachable import of %s, even in the all case", imp, ip) 1968 } 1969 } 1970 m[imp] = true 1971 } 1972 1973 drop[ip] = m 1974 } 1975 1976 for ip, ie := range want { 1977 var nie struct{ Internal, External []string } 1978 for _, imp := range ie.Internal { 1979 if !drop[ip][imp] { 1980 nie.Internal = append(nie.Internal, imp) 1981 } 1982 } 1983 1984 for _, imp := range ie.External { 1985 if !drop[ip][imp] { 1986 nie.External = append(nie.External, imp) 1987 } 1988 } 1989 1990 want[ip] = nie 1991 } 1992 } 1993 1994 /* PREP IS DONE, BEGIN ACTUAL TESTING */ 1995 1996 // first, validate all 1997 name = "all" 1998 main, tests = true, true 1999 except() 2000 validate() 2001 2002 // turn off main pkgs, which necessarily doesn't affect anything else 2003 name = "no main" 2004 main = false 2005 except(b("")) 2006 validate() 2007 2008 // ignoring the "varied" pkg has same effect as disabling main pkgs 2009 name = "ignore root" 2010 ignore = []string{b("")} 2011 main = true 2012 validate() 2013 2014 // when we drop tests, varied/otherpath loses its link to varied/m1p and 2015 // varied/simple/another loses its test import, which has a fairly big 2016 // cascade 2017 name = "no tests" 2018 tests = false 2019 ignore = nil 2020 except( 2021 b("")+" encoding/binary", 2022 b("simple")+" encoding/binary", 2023 b("simple/another")+" encoding/binary", 2024 b("otherpath")+" github.com/golang/dep/gps os sort", 2025 ) 2026 2027 // almost the same as previous, but varied just goes away completely 2028 name = "no main or tests" 2029 main = false 2030 except( 2031 b(""), 2032 b("simple")+" encoding/binary", 2033 b("simple/another")+" encoding/binary", 2034 bl("otherpath", "m1p")+" github.com/golang/dep/gps os sort", 2035 ) 2036 validate() 2037 2038 // focus on ignores now, so reset main and tests 2039 main, tests = true, true 2040 2041 // now, the fun stuff. punch a hole in the middle by cutting out 2042 // varied/simple 2043 name = "ignore varied/simple" 2044 ignore = []string{b("simple")} 2045 except( 2046 // root pkg loses on everything in varied/simple/another 2047 // FIXME this is a bit odd, but should probably exclude m1p as well, 2048 // because it actually shouldn't be valid to import a package that only 2049 // has tests. This whole model misses that nuance right now, though. 2050 bl("", "simple", "simple/another")+" hash encoding/binary go/parser", 2051 b("simple"), 2052 ) 2053 validate() 2054 2055 // widen the hole by excluding otherpath 2056 name = "ignore varied/{otherpath,simple}" 2057 ignore = []string{ 2058 b("otherpath"), 2059 b("simple"), 2060 } 2061 except( 2062 // root pkg loses on everything in varied/simple/another and varied/m1p 2063 bl("", "simple", "simple/another", "m1p", "otherpath")+" hash encoding/binary go/parser github.com/golang/dep/gps sort", 2064 b("otherpath"), 2065 b("simple"), 2066 ) 2067 validate() 2068 2069 // remove namemismatch, though we're mostly beating a dead horse now 2070 name = "ignore varied/{otherpath,simple,namemismatch}" 2071 ignore = append(ignore, b("namemismatch")) 2072 except( 2073 // root pkg loses on everything in varied/simple/another and varied/m1p 2074 bl("", "simple", "simple/another", "m1p", "otherpath", "namemismatch")+" hash encoding/binary go/parser github.com/golang/dep/gps sort os github.com/Masterminds/semver", 2075 b("otherpath"), 2076 b("simple"), 2077 b("namemismatch"), 2078 ) 2079 validate() 2080 } 2081 2082 func TestFlattenReachMap(t *testing.T) { 2083 // There's enough in the 'varied' test case to test most of what matters 2084 vptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "github.com", "example", "varied"), "github.com/example/varied") 2085 if err != nil { 2086 t.Fatalf("listPackages failed on varied test case: %s", err) 2087 } 2088 2089 all := []string{ 2090 "encoding/binary", 2091 "github.com/Masterminds/semver", 2092 "github.com/golang/dep/gps", 2093 "go/parser", 2094 "hash", 2095 "net/http", 2096 "os", 2097 "sort", 2098 } 2099 2100 // helper to generate testCase.expect as: all, except for a couple packages 2101 // 2102 // this makes it easier to see what we're taking out on each test 2103 except := func(not ...string) []string { 2104 expect := make([]string, len(all)-len(not)) 2105 2106 drop := make(map[string]bool) 2107 for _, npath := range not { 2108 drop[npath] = true 2109 } 2110 2111 k := 0 2112 for _, path := range all { 2113 if !drop[path] { 2114 expect[k] = path 2115 k++ 2116 } 2117 } 2118 return expect 2119 } 2120 2121 for _, testCase := range []*flattenReachMapCase{ 2122 // everything on 2123 { 2124 name: "simple", 2125 expect: except(), 2126 isStdLibFn: nil, 2127 main: true, 2128 tests: true, 2129 }, 2130 // no stdlib 2131 { 2132 name: "no stdlib", 2133 expect: except("encoding/binary", "go/parser", "hash", "net/http", "os", "sort"), 2134 isStdLibFn: paths.IsStandardImportPath, 2135 main: true, 2136 tests: true, 2137 }, 2138 // stdlib back in; now exclude tests, which should just cut one 2139 { 2140 name: "no tests", 2141 expect: except("encoding/binary"), 2142 isStdLibFn: nil, 2143 main: true, 2144 tests: false, 2145 }, 2146 // Now skip main, which still just cuts out one 2147 { 2148 name: "no main", 2149 expect: except("net/http"), 2150 isStdLibFn: nil, 2151 main: false, 2152 tests: true, 2153 }, 2154 // No test and no main, which should be additive 2155 { 2156 name: "no tests, no main", 2157 expect: except("net/http", "encoding/binary"), 2158 isStdLibFn: nil, 2159 main: false, 2160 tests: false, 2161 }, 2162 // now, the ignore tests. turn main and tests back on 2163 // start with non-matching 2164 { 2165 name: "non-matching ignore", 2166 expect: except(), 2167 isStdLibFn: nil, 2168 main: true, 2169 tests: true, 2170 ignore: NewIgnoredRuleset([]string{ 2171 "nomatch", 2172 }), 2173 }, 2174 // should have the same effect as ignoring main 2175 { 2176 name: "ignore the root", 2177 expect: except("net/http"), 2178 isStdLibFn: nil, 2179 main: true, 2180 tests: true, 2181 ignore: NewIgnoredRuleset([]string{ 2182 "github.com/example/varied", 2183 }), 2184 }, 2185 // now drop a more interesting one 2186 // we get github.com/golang/dep/gps from m1p, too, so it should still be there 2187 { 2188 name: "ignore simple", 2189 expect: except("go/parser"), 2190 isStdLibFn: nil, 2191 main: true, 2192 tests: true, 2193 ignore: NewIgnoredRuleset([]string{ 2194 "github.com/example/varied/simple", 2195 }), 2196 }, 2197 // now drop two 2198 { 2199 name: "ignore simple and nameismatch", 2200 expect: except("go/parser", "github.com/Masterminds/semver"), 2201 isStdLibFn: nil, 2202 main: true, 2203 tests: true, 2204 ignore: NewIgnoredRuleset([]string{ 2205 "github.com/example/varied/simple", 2206 "github.com/example/varied/namemismatch", 2207 }), 2208 }, 2209 // make sure tests and main play nice with ignore 2210 { 2211 name: "ignore simple and nameismatch, and no tests", 2212 expect: except("go/parser", "github.com/Masterminds/semver", "encoding/binary"), 2213 isStdLibFn: nil, 2214 main: true, 2215 tests: false, 2216 ignore: NewIgnoredRuleset([]string{ 2217 "github.com/example/varied/simple", 2218 "github.com/example/varied/namemismatch", 2219 }), 2220 }, 2221 { 2222 name: "ignore simple and namemismatch, and no main", 2223 expect: except("go/parser", "github.com/Masterminds/semver", "net/http"), 2224 isStdLibFn: nil, 2225 main: false, 2226 tests: true, 2227 ignore: NewIgnoredRuleset([]string{ 2228 "github.com/example/varied/simple", 2229 "github.com/example/varied/namemismatch", 2230 }), 2231 }, 2232 { 2233 name: "ignore simple and namemismatch, and no main or tests", 2234 expect: except("go/parser", "github.com/Masterminds/semver", "net/http", "encoding/binary"), 2235 isStdLibFn: nil, 2236 main: false, 2237 tests: false, 2238 ignore: NewIgnoredRuleset([]string{ 2239 "github.com/example/varied/simple", 2240 "github.com/example/varied/namemismatch", 2241 }), 2242 }, 2243 // ignore two that should knock out gps 2244 { 2245 name: "ignore both importers", 2246 expect: except("sort", "github.com/golang/dep/gps", "go/parser"), 2247 isStdLibFn: nil, 2248 main: true, 2249 tests: true, 2250 ignore: NewIgnoredRuleset([]string{ 2251 "github.com/example/varied/simple", 2252 "github.com/example/varied/m1p", 2253 }), 2254 }, 2255 // finally, directly ignore some external packages 2256 { 2257 name: "ignore external", 2258 expect: except("sort", "github.com/golang/dep/gps", "go/parser"), 2259 isStdLibFn: nil, 2260 main: true, 2261 tests: true, 2262 ignore: NewIgnoredRuleset([]string{ 2263 "github.com/golang/dep/gps", 2264 "go/parser", 2265 "sort", 2266 }), 2267 }, 2268 } { 2269 t.Run(testCase.name, testFlattenReachMap(&vptree, testCase)) 2270 } 2271 2272 // The only thing varied *doesn't* cover is disallowed path patterns 2273 ptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "disallow"), "disallow") 2274 if err != nil { 2275 t.Fatalf("ListPackages failed on disallow test case: %s", err) 2276 } 2277 2278 t.Run("disallowed", testFlattenReachMap(&ptree, &flattenReachMapCase{ 2279 name: "disallowed", 2280 expect: []string{"github.com/golang/dep/gps", "hash", "sort"}, 2281 isStdLibFn: nil, 2282 main: false, 2283 tests: false, 2284 })) 2285 } 2286 2287 type flattenReachMapCase struct { 2288 expect []string 2289 name string 2290 ignore *IgnoredRuleset 2291 main, tests bool 2292 isStdLibFn func(string) bool 2293 } 2294 2295 func testFlattenReachMap(ptree *PackageTree, testCase *flattenReachMapCase) func(*testing.T) { 2296 return func(t *testing.T) { 2297 t.Parallel() 2298 rm, em := ptree.ToReachMap(testCase.main, testCase.tests, true, testCase.ignore) 2299 if len(em) != 0 { 2300 t.Errorf("Should not have any error pkgs from ToReachMap, got %s", em) 2301 } 2302 result := rm.FlattenFn(testCase.isStdLibFn) 2303 if !reflect.DeepEqual(testCase.expect, result) { 2304 t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", testCase.name, result, testCase.expect) 2305 } 2306 } 2307 } 2308 2309 // Verify that we handle import cycles correctly - drop em all 2310 func TestToReachMapCycle(t *testing.T) { 2311 ptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "cycle"), "cycle") 2312 if err != nil { 2313 t.Fatalf("ListPackages failed on cycle test case: %s", err) 2314 } 2315 2316 rm, em := ptree.ToReachMap(true, true, false, nil) 2317 if len(em) != 0 { 2318 t.Errorf("Should not have any error packages from ToReachMap, got %s", em) 2319 } 2320 2321 // FIXME TEMPORARILY COMMENTED UNTIL WE CREATE A BETTER LISTPACKAGES MODEL - 2322 //if len(rm) > 0 { 2323 //t.Errorf("should be empty reachmap when all packages are in a cycle, got %v", rm) 2324 //} 2325 2326 if len(rm) == 0 { 2327 t.Error("TEMPORARY: should ignore import cycles, but cycle was eliminated") 2328 } 2329 } 2330 2331 func TestToReachMapFilterDot(t *testing.T) { 2332 ptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "relimport"), "relimport") 2333 if err != nil { 2334 t.Fatalf("ListPackages failed on relimport test case: %s", err) 2335 } 2336 2337 rm, _ := ptree.ToReachMap(true, true, false, nil) 2338 if _, has := rm["relimport/dot"]; !has { 2339 t.Fatal("relimport/dot should not have had errors") 2340 } 2341 2342 imports := dedupeStrings(rm["relimport/dot"].External, rm["relimport/dot"].Internal) 2343 for _, imp := range imports { 2344 if imp == "." { 2345 t.Fatal("dot import should have been filtered by ToReachMap") 2346 } 2347 } 2348 } 2349 2350 func getTestdataRootDir(t *testing.T) string { 2351 cwd, err := os.Getwd() 2352 if err != nil { 2353 t.Fatal(err) 2354 } 2355 return filepath.Join(cwd, "..", "_testdata") 2356 } 2357 2358 // Canary regression test to make sure that if PackageTree ever gains new 2359 // fields, we update the Copy method accordingly. 2360 func TestCanaryPackageTreeCopy(t *testing.T) { 2361 ptreeFields := []string{ 2362 "ImportRoot", 2363 "Packages", 2364 } 2365 packageFields := []string{ 2366 "Name", 2367 "ImportPath", 2368 "CommentPath", 2369 "Imports", 2370 "TestImports", 2371 } 2372 2373 fieldNames := func(typ reflect.Type) []string { 2374 var names []string 2375 for i := 0; i < typ.NumField(); i++ { 2376 names = append(names, typ.Field(i).Name) 2377 } 2378 return names 2379 } 2380 2381 ptreeRefl := fieldNames(reflect.TypeOf(PackageTree{})) 2382 packageRefl := fieldNames(reflect.TypeOf(Package{})) 2383 2384 if !reflect.DeepEqual(ptreeFields, ptreeRefl) { 2385 t.Errorf("PackageTree.Copy is designed to work with an exact set of fields in the PackageTree struct - make sure it (and this test) have been updated!\n\t(GOT):%s\n\t(WNT):%s", ptreeFields, ptreeRefl) 2386 } 2387 2388 if !reflect.DeepEqual(packageFields, packageRefl) { 2389 t.Errorf("PackageTree.Copy is designed to work with an exact set of fields in the Package struct - make sure it (and this test) have been updated!\n\t(GOT):%s\n\t(WNT):%s", packageFields, packageRefl) 2390 } 2391 } 2392 2393 func TestPackageTreeCopy(t *testing.T) { 2394 want := PackageTree{ 2395 ImportRoot: "ren", 2396 Packages: map[string]PackageOrErr{ 2397 "ren": { 2398 Err: &build.NoGoError{ 2399 Dir: "some/dir", 2400 }, 2401 }, 2402 "ren/m1p": { 2403 P: Package{ 2404 ImportPath: "ren/m1p", 2405 CommentPath: "", 2406 Name: "m1p", 2407 Imports: []string{ 2408 "github.com/sdboyer/gps", 2409 "sort", 2410 }, 2411 }, 2412 }, 2413 }, 2414 } 2415 got := want.Copy() 2416 if !reflect.DeepEqual(want, got) { 2417 t.Errorf("Did not get expected PackageOrErrs:\n\t(GOT): %+v\n\t(WNT): %+v", got, want) 2418 } 2419 }