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  }