github.com/sdboyer/gps@v0.16.3/pkgtree/pkgtree_test.go (about) 1 package pkgtree 2 3 import ( 4 "fmt" 5 "go/build" 6 "go/scanner" 7 "go/token" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "reflect" 12 "runtime" 13 "strings" 14 "testing" 15 16 "github.com/sdboyer/gps/internal" 17 "github.com/sdboyer/gps/internal/fs" 18 ) 19 20 // Stores a reference to original IsStdLib, so we could restore overridden version. 21 var doIsStdLib = internal.IsStdLib 22 23 func init() { 24 overrideIsStdLib() 25 } 26 27 // sets the IsStdLib func to always return false, otherwise it would identify 28 // pretty much all of our fixtures as being stdlib and skip everything. 29 func overrideIsStdLib() { 30 internal.IsStdLib = func(path string) bool { 31 return false 32 } 33 } 34 35 // PackageTree.ToReachMap() uses an easily separable algorithm, wmToReach(), 36 // to turn a discovered set of packages and their imports into a proper pair of 37 // internal and external reach maps. 38 // 39 // That algorithm is purely symbolic (no filesystem interaction), and thus is 40 // easy to test. This is that test. 41 func TestWorkmapToReach(t *testing.T) { 42 empty := func() map[string]bool { 43 return make(map[string]bool) 44 } 45 46 e := struct { 47 Internal, External []string 48 }{} 49 table := map[string]struct { 50 workmap map[string]wm 51 rm ReachMap 52 em map[string]*ProblemImportError 53 backprop bool 54 }{ 55 "single": { 56 workmap: map[string]wm{ 57 "foo": { 58 ex: empty(), 59 in: empty(), 60 }, 61 }, 62 rm: ReachMap{ 63 "foo": e, 64 }, 65 }, 66 "no external": { 67 workmap: map[string]wm{ 68 "foo": { 69 ex: empty(), 70 in: empty(), 71 }, 72 "foo/bar": { 73 ex: empty(), 74 in: empty(), 75 }, 76 }, 77 rm: ReachMap{ 78 "foo": e, 79 "foo/bar": e, 80 }, 81 }, 82 "no external with subpkg": { 83 workmap: map[string]wm{ 84 "foo": { 85 ex: empty(), 86 in: map[string]bool{ 87 "foo/bar": true, 88 }, 89 }, 90 "foo/bar": { 91 ex: empty(), 92 in: empty(), 93 }, 94 }, 95 rm: ReachMap{ 96 "foo": { 97 Internal: []string{"foo/bar"}, 98 }, 99 "foo/bar": e, 100 }, 101 }, 102 "simple base transitive": { 103 workmap: map[string]wm{ 104 "foo": { 105 ex: empty(), 106 in: map[string]bool{ 107 "foo/bar": true, 108 }, 109 }, 110 "foo/bar": { 111 ex: map[string]bool{ 112 "baz": true, 113 }, 114 in: empty(), 115 }, 116 }, 117 rm: ReachMap{ 118 "foo": { 119 External: []string{"baz"}, 120 Internal: []string{"foo/bar"}, 121 }, 122 "foo/bar": { 123 External: []string{"baz"}, 124 }, 125 }, 126 }, 127 "missing package is poison": { 128 workmap: map[string]wm{ 129 "A": { 130 ex: map[string]bool{ 131 "B/foo": true, 132 }, 133 in: map[string]bool{ 134 "A/foo": true, // missing 135 "A/bar": true, 136 }, 137 }, 138 "A/bar": { 139 ex: map[string]bool{ 140 "B/baz": true, 141 }, 142 in: empty(), 143 }, 144 }, 145 rm: ReachMap{ 146 "A/bar": { 147 External: []string{"B/baz"}, 148 }, 149 }, 150 em: map[string]*ProblemImportError{ 151 "A": &ProblemImportError{ 152 ImportPath: "A", 153 Cause: []string{"A/foo"}, 154 Err: missingPkgErr("A/foo"), 155 }, 156 }, 157 backprop: true, 158 }, 159 "transitive missing package is poison": { 160 workmap: map[string]wm{ 161 "A": { 162 ex: map[string]bool{ 163 "B/foo": true, 164 }, 165 in: map[string]bool{ 166 "A/foo": true, // transitively missing 167 "A/quux": true, 168 }, 169 }, 170 "A/foo": { 171 ex: map[string]bool{ 172 "C/flugle": true, 173 }, 174 in: map[string]bool{ 175 "A/bar": true, // missing 176 }, 177 }, 178 "A/quux": { 179 ex: map[string]bool{ 180 "B/baz": true, 181 }, 182 in: empty(), 183 }, 184 }, 185 rm: ReachMap{ 186 "A/quux": { 187 External: []string{"B/baz"}, 188 }, 189 }, 190 em: map[string]*ProblemImportError{ 191 "A": &ProblemImportError{ 192 ImportPath: "A", 193 Cause: []string{"A/foo", "A/bar"}, 194 Err: missingPkgErr("A/bar"), 195 }, 196 "A/foo": &ProblemImportError{ 197 ImportPath: "A/foo", 198 Cause: []string{"A/bar"}, 199 Err: missingPkgErr("A/bar"), 200 }, 201 }, 202 backprop: true, 203 }, 204 "err'd package is poison": { 205 workmap: map[string]wm{ 206 "A": { 207 ex: map[string]bool{ 208 "B/foo": true, 209 }, 210 in: map[string]bool{ 211 "A/foo": true, // err'd 212 "A/bar": true, 213 }, 214 }, 215 "A/foo": { 216 err: fmt.Errorf("err pkg"), 217 }, 218 "A/bar": { 219 ex: map[string]bool{ 220 "B/baz": true, 221 }, 222 in: empty(), 223 }, 224 }, 225 rm: ReachMap{ 226 "A/bar": { 227 External: []string{"B/baz"}, 228 }, 229 }, 230 em: map[string]*ProblemImportError{ 231 "A": &ProblemImportError{ 232 ImportPath: "A", 233 Cause: []string{"A/foo"}, 234 Err: fmt.Errorf("err pkg"), 235 }, 236 "A/foo": &ProblemImportError{ 237 ImportPath: "A/foo", 238 Err: fmt.Errorf("err pkg"), 239 }, 240 }, 241 backprop: true, 242 }, 243 "transitive err'd package is poison": { 244 workmap: map[string]wm{ 245 "A": { 246 ex: map[string]bool{ 247 "B/foo": true, 248 }, 249 in: map[string]bool{ 250 "A/foo": true, // transitively err'd 251 "A/quux": true, 252 }, 253 }, 254 "A/foo": { 255 ex: map[string]bool{ 256 "C/flugle": true, 257 }, 258 in: map[string]bool{ 259 "A/bar": true, // err'd 260 }, 261 }, 262 "A/bar": { 263 err: fmt.Errorf("err pkg"), 264 }, 265 "A/quux": { 266 ex: map[string]bool{ 267 "B/baz": true, 268 }, 269 in: empty(), 270 }, 271 }, 272 rm: ReachMap{ 273 "A/quux": { 274 External: []string{"B/baz"}, 275 }, 276 }, 277 em: map[string]*ProblemImportError{ 278 "A": &ProblemImportError{ 279 ImportPath: "A", 280 Cause: []string{"A/foo", "A/bar"}, 281 Err: fmt.Errorf("err pkg"), 282 }, 283 "A/foo": &ProblemImportError{ 284 ImportPath: "A/foo", 285 Cause: []string{"A/bar"}, 286 Err: fmt.Errorf("err pkg"), 287 }, 288 "A/bar": &ProblemImportError{ 289 ImportPath: "A/bar", 290 Err: fmt.Errorf("err pkg"), 291 }, 292 }, 293 backprop: true, 294 }, 295 "transitive err'd package no backprop": { 296 workmap: map[string]wm{ 297 "A": { 298 ex: map[string]bool{ 299 "B/foo": true, 300 }, 301 in: map[string]bool{ 302 "A/foo": true, // transitively err'd 303 "A/quux": true, 304 }, 305 }, 306 "A/foo": { 307 ex: map[string]bool{ 308 "C/flugle": true, 309 }, 310 in: map[string]bool{ 311 "A/bar": true, // err'd 312 }, 313 }, 314 "A/bar": { 315 err: fmt.Errorf("err pkg"), 316 }, 317 "A/quux": { 318 ex: map[string]bool{ 319 "B/baz": true, 320 }, 321 in: empty(), 322 }, 323 }, 324 rm: ReachMap{ 325 "A": { 326 Internal: []string{"A/bar", "A/foo", "A/quux"}, 327 //Internal: []string{"A/foo", "A/quux"}, 328 External: []string{"B/baz", "B/foo", "C/flugle"}, 329 }, 330 "A/foo": { 331 Internal: []string{"A/bar"}, 332 External: []string{"C/flugle"}, 333 }, 334 "A/quux": { 335 External: []string{"B/baz"}, 336 }, 337 }, 338 em: map[string]*ProblemImportError{ 339 "A/bar": &ProblemImportError{ 340 ImportPath: "A/bar", 341 Err: fmt.Errorf("err pkg"), 342 }, 343 }, 344 }, 345 // The following tests are mostly about regressions and weeding out 346 // weird assumptions 347 "internal diamond": { 348 workmap: map[string]wm{ 349 "A": { 350 ex: map[string]bool{ 351 "B/foo": true, 352 }, 353 in: map[string]bool{ 354 "A/foo": true, 355 "A/bar": true, 356 }, 357 }, 358 "A/foo": { 359 ex: map[string]bool{ 360 "C": true, 361 }, 362 in: map[string]bool{ 363 "A/quux": true, 364 }, 365 }, 366 "A/bar": { 367 ex: map[string]bool{ 368 "D": true, 369 }, 370 in: map[string]bool{ 371 "A/quux": true, 372 }, 373 }, 374 "A/quux": { 375 ex: map[string]bool{ 376 "B/baz": true, 377 }, 378 in: empty(), 379 }, 380 }, 381 rm: ReachMap{ 382 "A": { 383 External: []string{ 384 "B/baz", 385 "B/foo", 386 "C", 387 "D", 388 }, 389 Internal: []string{ 390 "A/bar", 391 "A/foo", 392 "A/quux", 393 }, 394 }, 395 "A/foo": { 396 External: []string{ 397 "B/baz", 398 "C", 399 }, 400 Internal: []string{ 401 "A/quux", 402 }, 403 }, 404 "A/bar": { 405 External: []string{ 406 "B/baz", 407 "D", 408 }, 409 Internal: []string{ 410 "A/quux", 411 }, 412 }, 413 "A/quux": { 414 External: []string{"B/baz"}, 415 }, 416 }, 417 }, 418 "rootmost gets imported": { 419 workmap: map[string]wm{ 420 "A": { 421 ex: map[string]bool{ 422 "B": true, 423 }, 424 in: empty(), 425 }, 426 "A/foo": { 427 ex: map[string]bool{ 428 "C": true, 429 }, 430 in: map[string]bool{ 431 "A": true, 432 }, 433 }, 434 }, 435 rm: ReachMap{ 436 "A": { 437 External: []string{"B"}, 438 }, 439 "A/foo": { 440 External: []string{ 441 "B", 442 "C", 443 }, 444 Internal: []string{ 445 "A", 446 }, 447 }, 448 }, 449 }, 450 } 451 452 for name, fix := range table { 453 name, fix := name, fix 454 t.Run(name, func(t *testing.T) { 455 t.Parallel() 456 457 // Avoid erroneous errors by initializing the fixture's error map if 458 // needed 459 if fix.em == nil { 460 fix.em = make(map[string]*ProblemImportError) 461 } 462 463 rm, em := wmToReach(fix.workmap, fix.backprop) 464 if !reflect.DeepEqual(rm, fix.rm) { 465 //t.Error(pretty.Sprintf("wmToReach(%q): Did not get expected reach map:\n\t(GOT): %s\n\t(WNT): %s", name, rm, fix.rm)) 466 t.Errorf("Did not get expected reach map:\n\t(GOT): %s\n\t(WNT): %s", rm, fix.rm) 467 } 468 if !reflect.DeepEqual(em, fix.em) { 469 //t.Error(pretty.Sprintf("wmToReach(%q): Did not get expected error map:\n\t(GOT): %# v\n\t(WNT): %# v", name, em, fix.em)) 470 t.Errorf("Did not get expected error map:\n\t(GOT): %v\n\t(WNT): %v", em, fix.em) 471 } 472 }) 473 } 474 } 475 476 func TestListPackagesNoDir(t *testing.T) { 477 out, err := ListPackages(filepath.Join(getTestdataRootDir(t), "notexist"), "notexist") 478 if err == nil { 479 t.Error("ListPackages should have errored on pointing to a nonexistent dir") 480 } 481 if !reflect.DeepEqual(PackageTree{}, out) { 482 t.Error("should've gotten back an empty PackageTree") 483 } 484 } 485 486 func TestListPackages(t *testing.T) { 487 srcdir := filepath.Join(getTestdataRootDir(t), "src") 488 j := func(s ...string) string { 489 return filepath.Join(srcdir, filepath.Join(s...)) 490 } 491 492 table := map[string]struct { 493 fileRoot string 494 importRoot string 495 out PackageTree 496 err error 497 }{ 498 "empty": { 499 fileRoot: j("empty"), 500 importRoot: "empty", 501 out: PackageTree{ 502 ImportRoot: "empty", 503 Packages: map[string]PackageOrErr{ 504 "empty": { 505 Err: &build.NoGoError{ 506 Dir: j("empty"), 507 }, 508 }, 509 }, 510 }, 511 }, 512 "code only": { 513 fileRoot: j("simple"), 514 importRoot: "simple", 515 out: PackageTree{ 516 ImportRoot: "simple", 517 Packages: map[string]PackageOrErr{ 518 "simple": { 519 P: Package{ 520 ImportPath: "simple", 521 CommentPath: "", 522 Name: "simple", 523 Imports: []string{ 524 "github.com/sdboyer/gps", 525 "sort", 526 }, 527 }, 528 }, 529 }, 530 }, 531 }, 532 "impose import path": { 533 fileRoot: j("simple"), 534 importRoot: "arbitrary", 535 out: PackageTree{ 536 ImportRoot: "arbitrary", 537 Packages: map[string]PackageOrErr{ 538 "arbitrary": { 539 P: Package{ 540 ImportPath: "arbitrary", 541 CommentPath: "", 542 Name: "simple", 543 Imports: []string{ 544 "github.com/sdboyer/gps", 545 "sort", 546 }, 547 }, 548 }, 549 }, 550 }, 551 }, 552 "test only": { 553 fileRoot: j("t"), 554 importRoot: "simple", 555 out: PackageTree{ 556 ImportRoot: "simple", 557 Packages: map[string]PackageOrErr{ 558 "simple": { 559 P: Package{ 560 ImportPath: "simple", 561 CommentPath: "", 562 Name: "simple", 563 Imports: []string{}, 564 TestImports: []string{ 565 "math/rand", 566 "strconv", 567 }, 568 }, 569 }, 570 }, 571 }, 572 }, 573 "xtest only": { 574 fileRoot: j("xt"), 575 importRoot: "simple", 576 out: PackageTree{ 577 ImportRoot: "simple", 578 Packages: map[string]PackageOrErr{ 579 "simple": { 580 P: Package{ 581 ImportPath: "simple", 582 CommentPath: "", 583 Name: "simple", 584 Imports: []string{}, 585 TestImports: []string{ 586 "sort", 587 "strconv", 588 }, 589 }, 590 }, 591 }, 592 }, 593 }, 594 "code and test": { 595 fileRoot: j("simplet"), 596 importRoot: "simple", 597 out: PackageTree{ 598 ImportRoot: "simple", 599 Packages: map[string]PackageOrErr{ 600 "simple": { 601 P: Package{ 602 ImportPath: "simple", 603 CommentPath: "", 604 Name: "simple", 605 Imports: []string{ 606 "github.com/sdboyer/gps", 607 "sort", 608 }, 609 TestImports: []string{ 610 "math/rand", 611 "strconv", 612 }, 613 }, 614 }, 615 }, 616 }, 617 }, 618 "code and xtest": { 619 fileRoot: j("simplext"), 620 importRoot: "simple", 621 out: PackageTree{ 622 ImportRoot: "simple", 623 Packages: map[string]PackageOrErr{ 624 "simple": { 625 P: Package{ 626 ImportPath: "simple", 627 CommentPath: "", 628 Name: "simple", 629 Imports: []string{ 630 "github.com/sdboyer/gps", 631 "sort", 632 }, 633 TestImports: []string{ 634 "sort", 635 "strconv", 636 }, 637 }, 638 }, 639 }, 640 }, 641 }, 642 "code, test, xtest": { 643 fileRoot: j("simpleallt"), 644 importRoot: "simple", 645 out: PackageTree{ 646 ImportRoot: "simple", 647 Packages: map[string]PackageOrErr{ 648 "simple": { 649 P: Package{ 650 ImportPath: "simple", 651 CommentPath: "", 652 Name: "simple", 653 Imports: []string{ 654 "github.com/sdboyer/gps", 655 "sort", 656 }, 657 TestImports: []string{ 658 "math/rand", 659 "sort", 660 "strconv", 661 }, 662 }, 663 }, 664 }, 665 }, 666 }, 667 "one pkg multifile": { 668 fileRoot: j("m1p"), 669 importRoot: "m1p", 670 out: PackageTree{ 671 ImportRoot: "m1p", 672 Packages: map[string]PackageOrErr{ 673 "m1p": { 674 P: Package{ 675 ImportPath: "m1p", 676 CommentPath: "", 677 Name: "m1p", 678 Imports: []string{ 679 "github.com/sdboyer/gps", 680 "os", 681 "sort", 682 }, 683 }, 684 }, 685 }, 686 }, 687 }, 688 "one nested below": { 689 fileRoot: j("nest"), 690 importRoot: "nest", 691 out: PackageTree{ 692 ImportRoot: "nest", 693 Packages: map[string]PackageOrErr{ 694 "nest": { 695 P: Package{ 696 ImportPath: "nest", 697 CommentPath: "", 698 Name: "simple", 699 Imports: []string{ 700 "github.com/sdboyer/gps", 701 "sort", 702 }, 703 }, 704 }, 705 "nest/m1p": { 706 P: Package{ 707 ImportPath: "nest/m1p", 708 CommentPath: "", 709 Name: "m1p", 710 Imports: []string{ 711 "github.com/sdboyer/gps", 712 "os", 713 "sort", 714 }, 715 }, 716 }, 717 }, 718 }, 719 }, 720 "malformed go file": { 721 fileRoot: j("bad"), 722 importRoot: "bad", 723 out: PackageTree{ 724 ImportRoot: "bad", 725 Packages: map[string]PackageOrErr{ 726 "bad": { 727 Err: scanner.ErrorList{ 728 &scanner.Error{ 729 Pos: token.Position{ 730 Filename: j("bad", "bad.go"), 731 Offset: 113, 732 Line: 2, 733 Column: 43, 734 }, 735 Msg: "expected 'package', found 'EOF'", 736 }, 737 }, 738 }, 739 }, 740 }, 741 }, 742 "two nested under empty root": { 743 fileRoot: j("ren"), 744 importRoot: "ren", 745 out: PackageTree{ 746 ImportRoot: "ren", 747 Packages: map[string]PackageOrErr{ 748 "ren": { 749 Err: &build.NoGoError{ 750 Dir: j("ren"), 751 }, 752 }, 753 "ren/m1p": { 754 P: Package{ 755 ImportPath: "ren/m1p", 756 CommentPath: "", 757 Name: "m1p", 758 Imports: []string{ 759 "github.com/sdboyer/gps", 760 "os", 761 "sort", 762 }, 763 }, 764 }, 765 "ren/simple": { 766 P: Package{ 767 ImportPath: "ren/simple", 768 CommentPath: "", 769 Name: "simple", 770 Imports: []string{ 771 "github.com/sdboyer/gps", 772 "sort", 773 }, 774 }, 775 }, 776 }, 777 }, 778 }, 779 "internal name mismatch": { 780 fileRoot: j("doublenest"), 781 importRoot: "doublenest", 782 out: PackageTree{ 783 ImportRoot: "doublenest", 784 Packages: map[string]PackageOrErr{ 785 "doublenest": { 786 P: Package{ 787 ImportPath: "doublenest", 788 CommentPath: "", 789 Name: "base", 790 Imports: []string{ 791 "github.com/sdboyer/gps", 792 "go/parser", 793 }, 794 }, 795 }, 796 "doublenest/namemismatch": { 797 P: Package{ 798 ImportPath: "doublenest/namemismatch", 799 CommentPath: "", 800 Name: "nm", 801 Imports: []string{ 802 "github.com/Masterminds/semver", 803 "os", 804 }, 805 }, 806 }, 807 "doublenest/namemismatch/m1p": { 808 P: Package{ 809 ImportPath: "doublenest/namemismatch/m1p", 810 CommentPath: "", 811 Name: "m1p", 812 Imports: []string{ 813 "github.com/sdboyer/gps", 814 "os", 815 "sort", 816 }, 817 }, 818 }, 819 }, 820 }, 821 }, 822 "file and importroot mismatch": { 823 fileRoot: j("doublenest"), 824 importRoot: "other", 825 out: PackageTree{ 826 ImportRoot: "other", 827 Packages: map[string]PackageOrErr{ 828 "other": { 829 P: Package{ 830 ImportPath: "other", 831 CommentPath: "", 832 Name: "base", 833 Imports: []string{ 834 "github.com/sdboyer/gps", 835 "go/parser", 836 }, 837 }, 838 }, 839 "other/namemismatch": { 840 P: Package{ 841 ImportPath: "other/namemismatch", 842 CommentPath: "", 843 Name: "nm", 844 Imports: []string{ 845 "github.com/Masterminds/semver", 846 "os", 847 }, 848 }, 849 }, 850 "other/namemismatch/m1p": { 851 P: Package{ 852 ImportPath: "other/namemismatch/m1p", 853 CommentPath: "", 854 Name: "m1p", 855 Imports: []string{ 856 "github.com/sdboyer/gps", 857 "os", 858 "sort", 859 }, 860 }, 861 }, 862 }, 863 }, 864 }, 865 "code and ignored main": { 866 fileRoot: j("igmain"), 867 importRoot: "simple", 868 out: PackageTree{ 869 ImportRoot: "simple", 870 Packages: map[string]PackageOrErr{ 871 "simple": { 872 P: Package{ 873 ImportPath: "simple", 874 CommentPath: "", 875 Name: "simple", 876 Imports: []string{ 877 "github.com/sdboyer/gps", 878 "sort", 879 "unicode", 880 }, 881 }, 882 }, 883 }, 884 }, 885 }, 886 "code and ignored main, order check": { 887 fileRoot: j("igmainfirst"), 888 importRoot: "simple", 889 out: PackageTree{ 890 ImportRoot: "simple", 891 Packages: map[string]PackageOrErr{ 892 "simple": { 893 P: Package{ 894 ImportPath: "simple", 895 CommentPath: "", 896 Name: "simple", 897 Imports: []string{ 898 "github.com/sdboyer/gps", 899 "sort", 900 "unicode", 901 }, 902 }, 903 }, 904 }, 905 }, 906 }, 907 "code and ignored main with comment leader": { 908 fileRoot: j("igmainlong"), 909 importRoot: "simple", 910 out: PackageTree{ 911 ImportRoot: "simple", 912 Packages: map[string]PackageOrErr{ 913 "simple": { 914 P: Package{ 915 ImportPath: "simple", 916 CommentPath: "", 917 Name: "simple", 918 Imports: []string{ 919 "github.com/sdboyer/gps", 920 "sort", 921 "unicode", 922 }, 923 }, 924 }, 925 }, 926 }, 927 }, 928 "code, tests, and ignored main": { 929 fileRoot: j("igmaint"), 930 importRoot: "simple", 931 out: PackageTree{ 932 ImportRoot: "simple", 933 Packages: map[string]PackageOrErr{ 934 "simple": { 935 P: Package{ 936 ImportPath: "simple", 937 CommentPath: "", 938 Name: "simple", 939 Imports: []string{ 940 "github.com/sdboyer/gps", 941 "sort", 942 "unicode", 943 }, 944 TestImports: []string{ 945 "math/rand", 946 "strconv", 947 }, 948 }, 949 }, 950 }, 951 }, 952 }, 953 // New code allows this because it doesn't care if the code compiles (kinda) or not, 954 // so maybe this is actually not an error anymore? 955 // 956 // TODO re-enable this case after the full and proper ListPackages() 957 // refactor in #99 958 /*"two pkgs": { 959 fileRoot: j("twopkgs"), 960 importRoot: "twopkgs", 961 out: PackageTree{ 962 ImportRoot: "twopkgs", 963 Packages: map[string]PackageOrErr{ 964 "twopkgs": { 965 Err: &build.MultiplePackageError{ 966 Dir: j("twopkgs"), 967 Packages: []string{"simple", "m1p"}, 968 Files: []string{"a.go", "b.go"}, 969 }, 970 }, 971 }, 972 }, 973 }, */ 974 // imports a missing pkg 975 "missing import": { 976 fileRoot: j("missing"), 977 importRoot: "missing", 978 out: PackageTree{ 979 ImportRoot: "missing", 980 Packages: map[string]PackageOrErr{ 981 "missing": { 982 P: Package{ 983 ImportPath: "missing", 984 CommentPath: "", 985 Name: "simple", 986 Imports: []string{ 987 "github.com/sdboyer/gps", 988 "missing/missing", 989 "sort", 990 }, 991 }, 992 }, 993 "missing/m1p": { 994 P: Package{ 995 ImportPath: "missing/m1p", 996 CommentPath: "", 997 Name: "m1p", 998 Imports: []string{ 999 "github.com/sdboyer/gps", 1000 "os", 1001 "sort", 1002 }, 1003 }, 1004 }, 1005 }, 1006 }, 1007 }, 1008 // import cycle of three packages. ListPackages doesn't do anything 1009 // special with cycles - that's the reach calculator's job - so this is 1010 // error-free 1011 "import cycle, len 3": { 1012 fileRoot: j("cycle"), 1013 importRoot: "cycle", 1014 out: PackageTree{ 1015 ImportRoot: "cycle", 1016 Packages: map[string]PackageOrErr{ 1017 "cycle": { 1018 P: Package{ 1019 ImportPath: "cycle", 1020 CommentPath: "", 1021 Name: "cycle", 1022 Imports: []string{ 1023 "cycle/one", 1024 "github.com/sdboyer/gps", 1025 }, 1026 }, 1027 }, 1028 "cycle/one": { 1029 P: Package{ 1030 ImportPath: "cycle/one", 1031 CommentPath: "", 1032 Name: "one", 1033 Imports: []string{ 1034 "cycle/two", 1035 "github.com/sdboyer/gps", 1036 }, 1037 }, 1038 }, 1039 "cycle/two": { 1040 P: Package{ 1041 ImportPath: "cycle/two", 1042 CommentPath: "", 1043 Name: "two", 1044 Imports: []string{ 1045 "cycle", 1046 "github.com/sdboyer/gps", 1047 }, 1048 }, 1049 }, 1050 }, 1051 }, 1052 }, 1053 // has disallowed dir names 1054 "disallowed dirs": { 1055 fileRoot: j("disallow"), 1056 importRoot: "disallow", 1057 out: PackageTree{ 1058 ImportRoot: "disallow", 1059 Packages: map[string]PackageOrErr{ 1060 "disallow": { 1061 P: Package{ 1062 ImportPath: "disallow", 1063 CommentPath: "", 1064 Name: "disallow", 1065 Imports: []string{ 1066 "disallow/testdata", 1067 "github.com/sdboyer/gps", 1068 "sort", 1069 }, 1070 }, 1071 }, 1072 // disallow/.m1p is ignored by listPackages...for now. Kept 1073 // here commented because this might change again... 1074 //"disallow/.m1p": { 1075 //P: Package{ 1076 //ImportPath: "disallow/.m1p", 1077 //CommentPath: "", 1078 //Name: "m1p", 1079 //Imports: []string{ 1080 //"github.com/sdboyer/gps", 1081 //"os", 1082 //"sort", 1083 //}, 1084 //}, 1085 //}, 1086 "disallow/testdata": { 1087 P: Package{ 1088 ImportPath: "disallow/testdata", 1089 CommentPath: "", 1090 Name: "testdata", 1091 Imports: []string{ 1092 "hash", 1093 }, 1094 }, 1095 }, 1096 }, 1097 }, 1098 }, 1099 "relative imports": { 1100 fileRoot: j("relimport"), 1101 importRoot: "relimport", 1102 out: PackageTree{ 1103 ImportRoot: "relimport", 1104 Packages: map[string]PackageOrErr{ 1105 "relimport": { 1106 P: Package{ 1107 ImportPath: "relimport", 1108 CommentPath: "", 1109 Name: "relimport", 1110 Imports: []string{ 1111 "sort", 1112 }, 1113 }, 1114 }, 1115 "relimport/dot": { 1116 P: Package{ 1117 ImportPath: "relimport/dot", 1118 CommentPath: "", 1119 Name: "dot", 1120 Imports: []string{ 1121 ".", 1122 "sort", 1123 }, 1124 }, 1125 }, 1126 "relimport/dotdot": { 1127 Err: &LocalImportsError{ 1128 Dir: j("relimport/dotdot"), 1129 ImportPath: "relimport/dotdot", 1130 LocalImports: []string{ 1131 "..", 1132 }, 1133 }, 1134 }, 1135 "relimport/dotslash": { 1136 Err: &LocalImportsError{ 1137 Dir: j("relimport/dotslash"), 1138 ImportPath: "relimport/dotslash", 1139 LocalImports: []string{ 1140 "./simple", 1141 }, 1142 }, 1143 }, 1144 "relimport/dotdotslash": { 1145 Err: &LocalImportsError{ 1146 Dir: j("relimport/dotdotslash"), 1147 ImportPath: "relimport/dotdotslash", 1148 LocalImports: []string{ 1149 "../github.com/sdboyer/gps", 1150 }, 1151 }, 1152 }, 1153 }, 1154 }, 1155 }, 1156 "skip underscore": { 1157 fileRoot: j("skip_"), 1158 importRoot: "skip_", 1159 out: PackageTree{ 1160 ImportRoot: "skip_", 1161 Packages: map[string]PackageOrErr{ 1162 "skip_": { 1163 P: Package{ 1164 ImportPath: "skip_", 1165 CommentPath: "", 1166 Name: "skip", 1167 Imports: []string{ 1168 "github.com/sdboyer/gps", 1169 "sort", 1170 }, 1171 }, 1172 }, 1173 }, 1174 }, 1175 }, 1176 // This case mostly exists for the PackageTree methods, but it does 1177 // cover a bit of range 1178 "varied": { 1179 fileRoot: j("varied"), 1180 importRoot: "varied", 1181 out: PackageTree{ 1182 ImportRoot: "varied", 1183 Packages: map[string]PackageOrErr{ 1184 "varied": { 1185 P: Package{ 1186 ImportPath: "varied", 1187 CommentPath: "", 1188 Name: "main", 1189 Imports: []string{ 1190 "net/http", 1191 "varied/namemismatch", 1192 "varied/otherpath", 1193 "varied/simple", 1194 }, 1195 }, 1196 }, 1197 "varied/otherpath": { 1198 P: Package{ 1199 ImportPath: "varied/otherpath", 1200 CommentPath: "", 1201 Name: "otherpath", 1202 Imports: []string{}, 1203 TestImports: []string{ 1204 "varied/m1p", 1205 }, 1206 }, 1207 }, 1208 "varied/simple": { 1209 P: Package{ 1210 ImportPath: "varied/simple", 1211 CommentPath: "", 1212 Name: "simple", 1213 Imports: []string{ 1214 "github.com/sdboyer/gps", 1215 "go/parser", 1216 "varied/simple/another", 1217 }, 1218 }, 1219 }, 1220 "varied/simple/another": { 1221 P: Package{ 1222 ImportPath: "varied/simple/another", 1223 CommentPath: "", 1224 Name: "another", 1225 Imports: []string{ 1226 "hash", 1227 "varied/m1p", 1228 }, 1229 TestImports: []string{ 1230 "encoding/binary", 1231 }, 1232 }, 1233 }, 1234 "varied/namemismatch": { 1235 P: Package{ 1236 ImportPath: "varied/namemismatch", 1237 CommentPath: "", 1238 Name: "nm", 1239 Imports: []string{ 1240 "github.com/Masterminds/semver", 1241 "os", 1242 }, 1243 }, 1244 }, 1245 "varied/m1p": { 1246 P: Package{ 1247 ImportPath: "varied/m1p", 1248 CommentPath: "", 1249 Name: "m1p", 1250 Imports: []string{ 1251 "github.com/sdboyer/gps", 1252 "os", 1253 "sort", 1254 }, 1255 }, 1256 }, 1257 }, 1258 }, 1259 }, 1260 "invalid buildtag like comments should be ignored": { 1261 fileRoot: j("buildtag"), 1262 importRoot: "buildtag", 1263 out: PackageTree{ 1264 ImportRoot: "buildtag", 1265 Packages: map[string]PackageOrErr{ 1266 "buildtag": { 1267 P: Package{ 1268 ImportPath: "buildtag", 1269 CommentPath: "", 1270 Name: "buildtag", 1271 Imports: []string{ 1272 "sort", 1273 }, 1274 }, 1275 }, 1276 }, 1277 }, 1278 }, 1279 } 1280 1281 for name, fix := range table { 1282 t.Run(name, func(t *testing.T) { 1283 if _, err := os.Stat(fix.fileRoot); err != nil { 1284 t.Errorf("error on fileRoot %s: %s", fix.fileRoot, err) 1285 } 1286 1287 out, err := ListPackages(fix.fileRoot, fix.importRoot) 1288 1289 if err != nil && fix.err == nil { 1290 t.Errorf("Received error but none expected: %s", err) 1291 } else if fix.err != nil && err == nil { 1292 t.Errorf("Error expected but none received") 1293 } else if fix.err != nil && err != nil { 1294 if !reflect.DeepEqual(fix.err, err) { 1295 t.Errorf("Did not receive expected error:\n\t(GOT): %s\n\t(WNT): %s", err, fix.err) 1296 } 1297 } 1298 1299 if fix.out.ImportRoot != "" && fix.out.Packages != nil { 1300 if !reflect.DeepEqual(out, fix.out) { 1301 if fix.out.ImportRoot != out.ImportRoot { 1302 t.Errorf("Expected ImportRoot %s, got %s", fix.out.ImportRoot, out.ImportRoot) 1303 } 1304 1305 // overwrite the out one to see if we still have a real problem 1306 out.ImportRoot = fix.out.ImportRoot 1307 1308 if !reflect.DeepEqual(out, fix.out) { 1309 if len(fix.out.Packages) < 2 { 1310 t.Errorf("Did not get expected PackageOrErrs:\n\t(GOT): %#v\n\t(WNT): %#v", out, fix.out) 1311 } else { 1312 seen := make(map[string]bool) 1313 for path, perr := range fix.out.Packages { 1314 seen[path] = true 1315 if operr, exists := out.Packages[path]; !exists { 1316 t.Errorf("Expected PackageOrErr for path %s was missing from output:\n\t%s", path, perr) 1317 } else { 1318 if !reflect.DeepEqual(perr, operr) { 1319 t.Errorf("PkgOrErr for path %s was not as expected:\n\t(GOT): %#v\n\t(WNT): %#v", path, operr, perr) 1320 } 1321 } 1322 } 1323 1324 for path, operr := range out.Packages { 1325 if seen[path] { 1326 continue 1327 } 1328 1329 t.Errorf("Got PackageOrErr for path %s, but none was expected:\n\t%s", path, operr) 1330 } 1331 } 1332 } 1333 } 1334 } 1335 }) 1336 } 1337 } 1338 1339 // Test that ListPackages skips directories for which it lacks permissions to 1340 // enter and files it lacks permissions to read. 1341 func TestListPackagesNoPerms(t *testing.T) { 1342 if runtime.GOOS == "windows" { 1343 // TODO This test doesn't work on windows because I wasn't able to easily 1344 // figure out how to chmod a dir in a way that made it untraversable. 1345 // 1346 // It's not a big deal, though, because the os.IsPermission() call we 1347 // use in the real code is effectively what's being tested here, and 1348 // that's designed to be cross-platform. So, if the unix tests pass, we 1349 // have every reason to believe windows tests would to, if the situation 1350 // arises. 1351 t.Skip() 1352 } 1353 tmp, err := ioutil.TempDir("", "listpkgsnp") 1354 if err != nil { 1355 t.Fatalf("Failed to create temp dir: %s", err) 1356 } 1357 defer os.RemoveAll(tmp) 1358 1359 srcdir := filepath.Join(getTestdataRootDir(t), "src", "ren") 1360 workdir := filepath.Join(tmp, "ren") 1361 fs.CopyDir(srcdir, workdir) 1362 1363 // chmod the simple dir and m1p/b.go file so they can't be read 1364 err = os.Chmod(filepath.Join(workdir, "simple"), 0) 1365 if err != nil { 1366 t.Fatalf("Error while chmodding simple dir: %s", err) 1367 } 1368 os.Chmod(filepath.Join(workdir, "m1p", "b.go"), 0) 1369 if err != nil { 1370 t.Fatalf("Error while chmodding b.go file: %s", err) 1371 } 1372 1373 want := PackageTree{ 1374 ImportRoot: "ren", 1375 Packages: map[string]PackageOrErr{ 1376 "ren": { 1377 Err: &build.NoGoError{ 1378 Dir: workdir, 1379 }, 1380 }, 1381 "ren/m1p": { 1382 P: Package{ 1383 ImportPath: "ren/m1p", 1384 CommentPath: "", 1385 Name: "m1p", 1386 Imports: []string{ 1387 "github.com/sdboyer/gps", 1388 "sort", 1389 }, 1390 }, 1391 }, 1392 }, 1393 } 1394 1395 got, err := ListPackages(workdir, "ren") 1396 1397 if err != nil { 1398 t.Fatalf("Unexpected err from ListPackages: %s", err) 1399 } 1400 if want.ImportRoot != got.ImportRoot { 1401 t.Fatalf("Expected ImportRoot %s, got %s", want.ImportRoot, got.ImportRoot) 1402 } 1403 1404 if !reflect.DeepEqual(got, want) { 1405 t.Errorf("Did not get expected PackageOrErrs:\n\t(GOT): %#v\n\t(WNT): %#v", got, want) 1406 if len(got.Packages) != 2 { 1407 if len(got.Packages) == 3 { 1408 t.Error("Wrong number of PackageOrErrs - did 'simple' subpackage make it into results somehow?") 1409 } else { 1410 t.Error("Wrong number of PackageOrErrs") 1411 } 1412 } 1413 1414 if got.Packages["ren"].Err == nil { 1415 t.Error("Should have gotten error on empty root directory") 1416 } 1417 1418 if !reflect.DeepEqual(got.Packages["ren/m1p"].P.Imports, want.Packages["ren/m1p"].P.Imports) { 1419 t.Error("Mismatch between imports in m1p") 1420 } 1421 } 1422 } 1423 1424 func TestToReachMap(t *testing.T) { 1425 // There's enough in the 'varied' test case to test most of what matters 1426 vptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "github.com", "example", "varied"), "github.com/example/varied") 1427 if err != nil { 1428 t.Fatalf("ListPackages failed on varied test case: %s", err) 1429 } 1430 1431 // Helper to add github.com/varied/example prefix 1432 b := func(s string) string { 1433 if s == "" { 1434 return "github.com/example/varied" 1435 } 1436 return "github.com/example/varied/" + s 1437 } 1438 bl := func(parts ...string) string { 1439 for k, s := range parts { 1440 parts[k] = b(s) 1441 } 1442 return strings.Join(parts, " ") 1443 } 1444 1445 // Set up vars for validate closure 1446 var want ReachMap 1447 var name string 1448 var main, tests bool 1449 var ignore map[string]bool 1450 1451 validate := func() { 1452 got, em := vptree.ToReachMap(main, tests, true, ignore) 1453 if len(em) != 0 { 1454 t.Errorf("Should not have any error packages from ToReachMap, got %s", em) 1455 } 1456 if !reflect.DeepEqual(want, got) { 1457 seen := make(map[string]bool) 1458 for ip, wantie := range want { 1459 seen[ip] = true 1460 if gotie, exists := got[ip]; !exists { 1461 t.Errorf("ver(%q): expected import path %s was not present in result", name, ip) 1462 } else { 1463 if !reflect.DeepEqual(wantie, gotie) { 1464 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) 1465 } 1466 } 1467 } 1468 1469 for ip, ie := range got { 1470 if seen[ip] { 1471 continue 1472 } 1473 t.Errorf("ver(%q): Got packages for import path %s, but none were expected:\n\t%s", name, ip, ie) 1474 } 1475 } 1476 } 1477 1478 // maps of each internal package, and their expected external and internal 1479 // imports in the maximal case. 1480 allex := map[string][]string{ 1481 b(""): {"encoding/binary", "github.com/Masterminds/semver", "github.com/sdboyer/gps", "go/parser", "hash", "net/http", "os", "sort"}, 1482 b("m1p"): {"github.com/sdboyer/gps", "os", "sort"}, 1483 b("namemismatch"): {"github.com/Masterminds/semver", "os"}, 1484 b("otherpath"): {"github.com/sdboyer/gps", "os", "sort"}, 1485 b("simple"): {"encoding/binary", "github.com/sdboyer/gps", "go/parser", "hash", "os", "sort"}, 1486 b("simple/another"): {"encoding/binary", "github.com/sdboyer/gps", "hash", "os", "sort"}, 1487 } 1488 1489 allin := map[string][]string{ 1490 b(""): {b("m1p"), b("namemismatch"), b("otherpath"), b("simple"), b("simple/another")}, 1491 b("m1p"): {}, 1492 b("namemismatch"): {}, 1493 b("otherpath"): {b("m1p")}, 1494 b("simple"): {b("m1p"), b("simple/another")}, 1495 b("simple/another"): {b("m1p")}, 1496 } 1497 1498 // build a map to validate the exception inputs. do this because shit is 1499 // hard enough to keep track of that it's preferable not to have silent 1500 // success if a typo creeps in and we're trying to except an import that 1501 // isn't in a pkg in the first place 1502 valid := make(map[string]map[string]bool) 1503 for ip, expkgs := range allex { 1504 m := make(map[string]bool) 1505 for _, pkg := range expkgs { 1506 m[pkg] = true 1507 } 1508 valid[ip] = m 1509 } 1510 validin := make(map[string]map[string]bool) 1511 for ip, inpkgs := range allin { 1512 m := make(map[string]bool) 1513 for _, pkg := range inpkgs { 1514 m[pkg] = true 1515 } 1516 validin[ip] = m 1517 } 1518 1519 // helper to compose want, excepting specific packages 1520 // 1521 // this makes it easier to see what we're taking out on each test 1522 except := func(pkgig ...string) { 1523 // reinit expect with everything from all 1524 want = make(ReachMap) 1525 for ip, expkgs := range allex { 1526 var ie struct{ Internal, External []string } 1527 1528 inpkgs := allin[ip] 1529 lenex, lenin := len(expkgs), len(inpkgs) 1530 if lenex > 0 { 1531 ie.External = make([]string, len(expkgs)) 1532 copy(ie.External, expkgs) 1533 } 1534 1535 if lenin > 0 { 1536 ie.Internal = make([]string, len(inpkgs)) 1537 copy(ie.Internal, inpkgs) 1538 } 1539 1540 want[ip] = ie 1541 } 1542 1543 // now build the dropmap 1544 drop := make(map[string]map[string]bool) 1545 for _, igstr := range pkgig { 1546 // split on space; first elem is import path to pkg, the rest are 1547 // the imports to drop. 1548 not := strings.Split(igstr, " ") 1549 var ip string 1550 ip, not = not[0], not[1:] 1551 if _, exists := valid[ip]; !exists { 1552 t.Fatalf("%s is not a package name we're working with, doofus", ip) 1553 } 1554 1555 // if only a single elem was passed, though, drop the whole thing 1556 if len(not) == 0 { 1557 delete(want, ip) 1558 continue 1559 } 1560 1561 m := make(map[string]bool) 1562 for _, imp := range not { 1563 if strings.HasPrefix(imp, "github.com/example/varied") { 1564 if !validin[ip][imp] { 1565 t.Fatalf("%s is not a reachable import of %s, even in the all case", imp, ip) 1566 } 1567 } else { 1568 if !valid[ip][imp] { 1569 t.Fatalf("%s is not a reachable import of %s, even in the all case", imp, ip) 1570 } 1571 } 1572 m[imp] = true 1573 } 1574 1575 drop[ip] = m 1576 } 1577 1578 for ip, ie := range want { 1579 var nie struct{ Internal, External []string } 1580 for _, imp := range ie.Internal { 1581 if !drop[ip][imp] { 1582 nie.Internal = append(nie.Internal, imp) 1583 } 1584 } 1585 1586 for _, imp := range ie.External { 1587 if !drop[ip][imp] { 1588 nie.External = append(nie.External, imp) 1589 } 1590 } 1591 1592 want[ip] = nie 1593 } 1594 } 1595 1596 /* PREP IS DONE, BEGIN ACTUAL TESTING */ 1597 1598 // first, validate all 1599 name = "all" 1600 main, tests = true, true 1601 except() 1602 validate() 1603 1604 // turn off main pkgs, which necessarily doesn't affect anything else 1605 name = "no main" 1606 main = false 1607 except(b("")) 1608 validate() 1609 1610 // ignoring the "varied" pkg has same effect as disabling main pkgs 1611 name = "ignore root" 1612 ignore = map[string]bool{ 1613 b(""): true, 1614 } 1615 main = true 1616 validate() 1617 1618 // when we drop tests, varied/otherpath loses its link to varied/m1p and 1619 // varied/simple/another loses its test import, which has a fairly big 1620 // cascade 1621 name = "no tests" 1622 tests = false 1623 ignore = nil 1624 except( 1625 b("")+" encoding/binary", 1626 b("simple")+" encoding/binary", 1627 b("simple/another")+" encoding/binary", 1628 b("otherpath")+" github.com/sdboyer/gps os sort", 1629 ) 1630 1631 // almost the same as previous, but varied just goes away completely 1632 name = "no main or tests" 1633 main = false 1634 except( 1635 b(""), 1636 b("simple")+" encoding/binary", 1637 b("simple/another")+" encoding/binary", 1638 bl("otherpath", "m1p")+" github.com/sdboyer/gps os sort", 1639 ) 1640 validate() 1641 1642 // focus on ignores now, so reset main and tests 1643 main, tests = true, true 1644 1645 // now, the fun stuff. punch a hole in the middle by cutting out 1646 // varied/simple 1647 name = "ignore varied/simple" 1648 ignore = map[string]bool{ 1649 b("simple"): true, 1650 } 1651 except( 1652 // root pkg loses on everything in varied/simple/another 1653 // FIXME this is a bit odd, but should probably exclude m1p as well, 1654 // because it actually shouldn't be valid to import a package that only 1655 // has tests. This whole model misses that nuance right now, though. 1656 bl("", "simple", "simple/another")+" hash encoding/binary go/parser", 1657 b("simple"), 1658 ) 1659 validate() 1660 1661 // widen the hole by excluding otherpath 1662 name = "ignore varied/{otherpath,simple}" 1663 ignore = map[string]bool{ 1664 b("otherpath"): true, 1665 b("simple"): true, 1666 } 1667 except( 1668 // root pkg loses on everything in varied/simple/another and varied/m1p 1669 bl("", "simple", "simple/another", "m1p", "otherpath")+" hash encoding/binary go/parser github.com/sdboyer/gps sort", 1670 b("otherpath"), 1671 b("simple"), 1672 ) 1673 validate() 1674 1675 // remove namemismatch, though we're mostly beating a dead horse now 1676 name = "ignore varied/{otherpath,simple,namemismatch}" 1677 ignore[b("namemismatch")] = true 1678 except( 1679 // root pkg loses on everything in varied/simple/another and varied/m1p 1680 bl("", "simple", "simple/another", "m1p", "otherpath", "namemismatch")+" hash encoding/binary go/parser github.com/sdboyer/gps sort os github.com/Masterminds/semver", 1681 b("otherpath"), 1682 b("simple"), 1683 b("namemismatch"), 1684 ) 1685 validate() 1686 } 1687 1688 func TestFlattenReachMap(t *testing.T) { 1689 // There's enough in the 'varied' test case to test most of what matters 1690 vptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "github.com", "example", "varied"), "github.com/example/varied") 1691 if err != nil { 1692 t.Fatalf("listPackages failed on varied test case: %s", err) 1693 } 1694 1695 var expect []string 1696 var name string 1697 var ignore map[string]bool 1698 var stdlib, main, tests bool 1699 1700 validate := func() { 1701 rm, em := vptree.ToReachMap(main, tests, true, ignore) 1702 if len(em) != 0 { 1703 t.Errorf("Should not have any error pkgs from ToReachMap, got %s", em) 1704 } 1705 result := rm.Flatten(stdlib) 1706 if !reflect.DeepEqual(expect, result) { 1707 t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", name, result, expect) 1708 } 1709 } 1710 1711 all := []string{ 1712 "encoding/binary", 1713 "github.com/Masterminds/semver", 1714 "github.com/sdboyer/gps", 1715 "go/parser", 1716 "hash", 1717 "net/http", 1718 "os", 1719 "sort", 1720 } 1721 1722 // helper to rewrite expect, except for a couple packages 1723 // 1724 // this makes it easier to see what we're taking out on each test 1725 except := func(not ...string) { 1726 expect = make([]string, len(all)-len(not)) 1727 1728 drop := make(map[string]bool) 1729 for _, npath := range not { 1730 drop[npath] = true 1731 } 1732 1733 k := 0 1734 for _, path := range all { 1735 if !drop[path] { 1736 expect[k] = path 1737 k++ 1738 } 1739 } 1740 } 1741 1742 // everything on 1743 name = "simple" 1744 except() 1745 stdlib, main, tests = true, true, true 1746 validate() 1747 1748 // turning off stdlib should cut most things, but we need to override the 1749 // function 1750 internal.IsStdLib = doIsStdLib 1751 name = "no stdlib" 1752 stdlib = false 1753 except("encoding/binary", "go/parser", "hash", "net/http", "os", "sort") 1754 validate() 1755 // restore stdlib func override 1756 overrideIsStdLib() 1757 1758 // stdlib back in; now exclude tests, which should just cut one 1759 name = "no tests" 1760 stdlib, tests = true, false 1761 except("encoding/binary") 1762 validate() 1763 1764 // Now skip main, which still just cuts out one 1765 name = "no main" 1766 main, tests = false, true 1767 except("net/http") 1768 validate() 1769 1770 // No test and no main, which should be additive 1771 name = "no test, no main" 1772 main, tests = false, false 1773 except("net/http", "encoding/binary") 1774 validate() 1775 1776 // now, the ignore tests. turn main and tests back on 1777 main, tests = true, true 1778 1779 // start with non-matching 1780 name = "non-matching ignore" 1781 ignore = map[string]bool{ 1782 "nomatch": true, 1783 } 1784 except() 1785 validate() 1786 1787 // should have the same effect as ignoring main 1788 name = "ignore the root" 1789 ignore = map[string]bool{ 1790 "github.com/example/varied": true, 1791 } 1792 except("net/http") 1793 validate() 1794 1795 // now drop a more interesting one 1796 name = "ignore simple" 1797 ignore = map[string]bool{ 1798 "github.com/example/varied/simple": true, 1799 } 1800 // we get github.com/sdboyer/gps from m1p, too, so it should still be there 1801 except("go/parser") 1802 validate() 1803 1804 // now drop two 1805 name = "ignore simple and namemismatch" 1806 ignore = map[string]bool{ 1807 "github.com/example/varied/simple": true, 1808 "github.com/example/varied/namemismatch": true, 1809 } 1810 except("go/parser", "github.com/Masterminds/semver") 1811 validate() 1812 1813 // make sure tests and main play nice with ignore 1814 name = "ignore simple and namemismatch, and no tests" 1815 tests = false 1816 except("go/parser", "github.com/Masterminds/semver", "encoding/binary") 1817 validate() 1818 name = "ignore simple and namemismatch, and no main" 1819 main, tests = false, true 1820 except("go/parser", "github.com/Masterminds/semver", "net/http") 1821 validate() 1822 name = "ignore simple and namemismatch, and no main or tests" 1823 main, tests = false, false 1824 except("go/parser", "github.com/Masterminds/semver", "net/http", "encoding/binary") 1825 validate() 1826 1827 main, tests = true, true 1828 1829 // ignore two that should knock out gps 1830 name = "ignore both importers" 1831 ignore = map[string]bool{ 1832 "github.com/example/varied/simple": true, 1833 "github.com/example/varied/m1p": true, 1834 } 1835 except("sort", "github.com/sdboyer/gps", "go/parser") 1836 validate() 1837 1838 // finally, directly ignore some external packages 1839 name = "ignore external" 1840 ignore = map[string]bool{ 1841 "github.com/sdboyer/gps": true, 1842 "go/parser": true, 1843 "sort": true, 1844 } 1845 except("sort", "github.com/sdboyer/gps", "go/parser") 1846 validate() 1847 1848 // The only thing varied *doesn't* cover is disallowed path patterns 1849 ptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "disallow"), "disallow") 1850 if err != nil { 1851 t.Fatalf("ListPackages failed on disallow test case: %s", err) 1852 } 1853 1854 rm, em := ptree.ToReachMap(false, false, true, nil) 1855 if len(em) != 0 { 1856 t.Errorf("Should not have any error packages from ToReachMap, got %s", em) 1857 } 1858 result := rm.Flatten(true) 1859 expect = []string{"github.com/sdboyer/gps", "hash", "sort"} 1860 if !reflect.DeepEqual(expect, result) { 1861 t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", name, result, expect) 1862 } 1863 } 1864 1865 // Verify that we handle import cycles correctly - drop em all 1866 func TestToReachMapCycle(t *testing.T) { 1867 ptree, err := ListPackages(filepath.Join(getTestdataRootDir(t), "src", "cycle"), "cycle") 1868 if err != nil { 1869 t.Fatalf("ListPackages failed on cycle test case: %s", err) 1870 } 1871 1872 rm, em := ptree.ToReachMap(true, true, false, nil) 1873 if len(em) != 0 { 1874 t.Errorf("Should not have any error packages from ToReachMap, got %s", em) 1875 } 1876 1877 // FIXME TEMPORARILY COMMENTED UNTIL WE CREATE A BETTER LISTPACKAGES MODEL - 1878 //if len(rm) > 0 { 1879 //t.Errorf("should be empty reachmap when all packages are in a cycle, got %v", rm) 1880 //} 1881 1882 if len(rm) == 0 { 1883 t.Error("TEMPORARY: should ignore import cycles, but cycle was eliminated") 1884 } 1885 } 1886 1887 func getTestdataRootDir(t *testing.T) string { 1888 cwd, err := os.Getwd() 1889 if err != nil { 1890 t.Fatal(err) 1891 } 1892 return filepath.Join(cwd, "..", "_testdata") 1893 }