github.com/jd-ly/tools@v0.5.7/internal/imports/mod_test.go (about)

     1  package imports
     2  
     3  import (
     4  	"archive/zip"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"regexp"
    13  	"sort"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  
    18  	"golang.org/x/mod/module"
    19  	"github.com/jd-ly/tools/internal/gocommand"
    20  	"github.com/jd-ly/tools/internal/gopathwalk"
    21  	"github.com/jd-ly/tools/internal/proxydir"
    22  	"github.com/jd-ly/tools/internal/testenv"
    23  	"github.com/jd-ly/tools/txtar"
    24  )
    25  
    26  // Tests that we can find packages in the stdlib.
    27  func TestScanStdlib(t *testing.T) {
    28  	mt := setup(t, `
    29  -- go.mod --
    30  module x
    31  `, "")
    32  	defer mt.cleanup()
    33  
    34  	mt.assertScanFinds("fmt", "fmt")
    35  }
    36  
    37  // Tests that we handle a nested module. This is different from other tests
    38  // where the module is in scope -- here we have to figure out the import path
    39  // without any help from go list.
    40  func TestScanOutOfScopeNestedModule(t *testing.T) {
    41  	mt := setup(t, `
    42  -- go.mod --
    43  module x
    44  
    45  -- x.go --
    46  package x
    47  
    48  -- v2/go.mod --
    49  module x
    50  
    51  -- v2/x.go --
    52  package x`, "")
    53  	defer mt.cleanup()
    54  
    55  	pkg := mt.assertScanFinds("x/v2", "x")
    56  	if pkg != nil && !strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/v2") {
    57  		t.Errorf("x/v2 was found in %v, wanted .../main/v2", pkg.dir)
    58  	}
    59  	// We can't load the package name from the import path, but that should
    60  	// be okay -- if we end up adding this result, we'll add it with a name
    61  	// if necessary.
    62  }
    63  
    64  // Tests that we don't find a nested module contained in a local replace target.
    65  // The code for this case is too annoying to write, so it's just ignored.
    66  func TestScanNestedModuleInLocalReplace(t *testing.T) {
    67  	mt := setup(t, `
    68  -- go.mod --
    69  module x
    70  
    71  require y v0.0.0
    72  replace y => ./y
    73  
    74  -- x.go --
    75  package x
    76  
    77  -- y/go.mod --
    78  module y
    79  
    80  -- y/y.go --
    81  package y
    82  
    83  -- y/z/go.mod --
    84  module y/z
    85  
    86  -- y/z/z.go --
    87  package z
    88  `, "")
    89  	defer mt.cleanup()
    90  
    91  	mt.assertFound("y", "y")
    92  
    93  	scan, err := scanToSlice(mt.resolver, nil)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	for _, pkg := range scan {
    98  		if strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/y/z") {
    99  			t.Errorf("scan found a package %v in dir main/y/z, wanted none", pkg.importPathShort)
   100  		}
   101  	}
   102  }
   103  
   104  // Tests that path encoding is handled correctly. Adapted from mod_case.txt.
   105  func TestModCase(t *testing.T) {
   106  	mt := setup(t, `
   107  -- go.mod --
   108  module x
   109  
   110  require rsc.io/QUOTE v1.5.2
   111  
   112  -- x.go --
   113  package x
   114  
   115  import _ "rsc.io/QUOTE/QUOTE"
   116  `, "")
   117  	defer mt.cleanup()
   118  	mt.assertFound("rsc.io/QUOTE/QUOTE", "QUOTE")
   119  }
   120  
   121  // Not obviously relevant to goimports. Adapted from mod_domain_root.txt anyway.
   122  func TestModDomainRoot(t *testing.T) {
   123  	mt := setup(t, `
   124  -- go.mod --
   125  module x
   126  
   127  require example.com v1.0.0
   128  
   129  -- x.go --
   130  package x
   131  import _ "example.com"
   132  `, "")
   133  	defer mt.cleanup()
   134  	mt.assertFound("example.com", "x")
   135  }
   136  
   137  // Tests that scanning the module cache > 1 time is able to find the same module.
   138  func TestModMultipleScans(t *testing.T) {
   139  	mt := setup(t, `
   140  -- go.mod --
   141  module x
   142  
   143  require example.com v1.0.0
   144  
   145  -- x.go --
   146  package x
   147  import _ "example.com"
   148  `, "")
   149  	defer mt.cleanup()
   150  
   151  	mt.assertScanFinds("example.com", "x")
   152  	mt.assertScanFinds("example.com", "x")
   153  }
   154  
   155  // Tests that scanning the module cache > 1 time is able to find the same module
   156  // in the module cache.
   157  func TestModMultipleScansWithSubdirs(t *testing.T) {
   158  	mt := setup(t, `
   159  -- go.mod --
   160  module x
   161  
   162  require rsc.io/quote v1.5.2
   163  
   164  -- x.go --
   165  package x
   166  import _ "rsc.io/quote"
   167  `, "")
   168  	defer mt.cleanup()
   169  
   170  	mt.assertScanFinds("rsc.io/quote", "quote")
   171  	mt.assertScanFinds("rsc.io/quote", "quote")
   172  }
   173  
   174  // Tests that scanning the module cache > 1 after changing a package in module cache to make it unimportable
   175  // is able to find the same module.
   176  func TestModCacheEditModFile(t *testing.T) {
   177  	mt := setup(t, `
   178  -- go.mod --
   179  module x
   180  
   181  require rsc.io/quote v1.5.2
   182  -- x.go --
   183  package x
   184  import _ "rsc.io/quote"
   185  `, "")
   186  	defer mt.cleanup()
   187  	found := mt.assertScanFinds("rsc.io/quote", "quote")
   188  	if found == nil {
   189  		t.Fatal("rsc.io/quote not found in initial scan.")
   190  	}
   191  
   192  	// Update the go.mod file of example.com so that it changes its module path (not allowed).
   193  	if err := os.Chmod(filepath.Join(found.dir, "go.mod"), 0644); err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	if err := ioutil.WriteFile(filepath.Join(found.dir, "go.mod"), []byte("module bad.com\n"), 0644); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  
   200  	// Test that with its cache of module packages it still finds the package.
   201  	mt.assertScanFinds("rsc.io/quote", "quote")
   202  
   203  	// Rewrite the main package so that rsc.io/quote is not in scope.
   204  	if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "go.mod"), []byte("module x\n"), 0644); err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "x.go"), []byte("package x\n"), 0644); err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	// Uninitialize the go.mod dependent cached information and make sure it still finds the package.
   212  	mt.resolver.ClearForNewMod()
   213  	mt.assertScanFinds("rsc.io/quote", "quote")
   214  }
   215  
   216  // Tests that -mod=vendor works. Adapted from mod_vendor_build.txt.
   217  func TestModVendorBuild(t *testing.T) {
   218  	mt := setup(t, `
   219  -- go.mod --
   220  module m
   221  go 1.12
   222  require rsc.io/sampler v1.3.1
   223  -- x.go --
   224  package x
   225  import _ "rsc.io/sampler"
   226  `, "")
   227  	defer mt.cleanup()
   228  
   229  	// Sanity-check the setup.
   230  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`)
   231  
   232  	// Populate vendor/ and clear out the mod cache so we can't cheat.
   233  	if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	if _, err := mt.env.invokeGo(context.Background(), "clean", "-modcache"); err != nil {
   237  		t.Fatal(err)
   238  	}
   239  
   240  	// Clear out the resolver's cache, since we've changed the environment.
   241  	mt.resolver = newModuleResolver(mt.env)
   242  	mt.env.Env["GOFLAGS"] = "-mod=vendor"
   243  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`)
   244  }
   245  
   246  // Tests that -mod=vendor is auto-enabled only for go1.14 and higher.
   247  // Vaguely inspired by mod_vendor_auto.txt.
   248  func TestModVendorAuto(t *testing.T) {
   249  	mt := setup(t, `
   250  -- go.mod --
   251  module m
   252  go 1.14
   253  require rsc.io/sampler v1.3.1
   254  -- x.go --
   255  package x
   256  import _ "rsc.io/sampler"
   257  `, "")
   258  	defer mt.cleanup()
   259  
   260  	// Populate vendor/.
   261  	if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
   262  		t.Fatal(err)
   263  	}
   264  
   265  	wantDir := `pkg.*mod.*/sampler@.*$`
   266  	if testenv.Go1Point() >= 14 {
   267  		wantDir = `/vendor/`
   268  	}
   269  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir)
   270  }
   271  
   272  // Tests that a module replace works. Adapted from mod_list.txt. We start with
   273  // go.mod2; the first part of the test is irrelevant.
   274  func TestModList(t *testing.T) {
   275  	mt := setup(t, `
   276  -- go.mod --
   277  module x
   278  require rsc.io/quote v1.5.1
   279  replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1
   280  
   281  -- x.go --
   282  package x
   283  import _ "rsc.io/quote"
   284  `, "")
   285  	defer mt.cleanup()
   286  
   287  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.mod.*/sampler@v1.3.1$`)
   288  }
   289  
   290  // Tests that a local replace works. Adapted from mod_local_replace.txt.
   291  func TestModLocalReplace(t *testing.T) {
   292  	mt := setup(t, `
   293  -- x/y/go.mod --
   294  module x/y
   295  require zz v1.0.0
   296  replace zz v1.0.0 => ../z
   297  
   298  -- x/y/y.go --
   299  package y
   300  import _ "zz"
   301  
   302  -- x/z/go.mod --
   303  module x/z
   304  
   305  -- x/z/z.go --
   306  package z
   307  `, "x/y")
   308  	defer mt.cleanup()
   309  
   310  	mt.assertFound("zz", "z")
   311  }
   312  
   313  // Tests that the package at the root of the main module can be found.
   314  // Adapted from the first part of mod_multirepo.txt.
   315  func TestModMultirepo1(t *testing.T) {
   316  	mt := setup(t, `
   317  -- go.mod --
   318  module rsc.io/quote
   319  
   320  -- x.go --
   321  package quote
   322  `, "")
   323  	defer mt.cleanup()
   324  
   325  	mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
   326  }
   327  
   328  // Tests that a simple module dependency is found. Adapted from the third part
   329  // of mod_multirepo.txt (We skip the case where it doesn't have a go.mod
   330  // entry -- we just don't work in that case.)
   331  func TestModMultirepo3(t *testing.T) {
   332  	mt := setup(t, `
   333  -- go.mod --
   334  module rsc.io/quote
   335  
   336  require rsc.io/quote/v2 v2.0.1
   337  -- x.go --
   338  package quote
   339  
   340  import _ "rsc.io/quote/v2"
   341  `, "")
   342  	defer mt.cleanup()
   343  
   344  	mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
   345  	mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
   346  }
   347  
   348  // Tests that a nested module is found in the module cache, even though
   349  // it's checked out. Adapted from the fourth part of mod_multirepo.txt.
   350  func TestModMultirepo4(t *testing.T) {
   351  	mt := setup(t, `
   352  -- go.mod --
   353  module rsc.io/quote
   354  require rsc.io/quote/v2 v2.0.1
   355  
   356  -- x.go --
   357  package quote
   358  import _ "rsc.io/quote/v2"
   359  
   360  -- v2/go.mod --
   361  package rsc.io/quote/v2
   362  
   363  -- v2/x.go --
   364  package quote
   365  import _ "rsc.io/quote/v2"
   366  `, "")
   367  	defer mt.cleanup()
   368  
   369  	mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
   370  	mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
   371  }
   372  
   373  // Tests a simple module dependency. Adapted from the first part of mod_replace.txt.
   374  func TestModReplace1(t *testing.T) {
   375  	mt := setup(t, `
   376  -- go.mod --
   377  module quoter
   378  
   379  require rsc.io/quote/v3 v3.0.0
   380  
   381  -- main.go --
   382  
   383  package main
   384  `, "")
   385  	defer mt.cleanup()
   386  	mt.assertFound("rsc.io/quote/v3", "quote")
   387  }
   388  
   389  // Tests a local replace. Adapted from the second part of mod_replace.txt.
   390  func TestModReplace2(t *testing.T) {
   391  	mt := setup(t, `
   392  -- go.mod --
   393  module quoter
   394  
   395  require rsc.io/quote/v3 v3.0.0
   396  replace rsc.io/quote/v3 => ./local/rsc.io/quote/v3
   397  -- main.go --
   398  package main
   399  
   400  -- local/rsc.io/quote/v3/go.mod --
   401  module rsc.io/quote/v3
   402  
   403  require rsc.io/sampler v1.3.0
   404  
   405  -- local/rsc.io/quote/v3/quote.go --
   406  package quote
   407  
   408  import "rsc.io/sampler"
   409  `, "")
   410  	defer mt.cleanup()
   411  	mt.assertModuleFoundInDir("rsc.io/quote/v3", "quote", `/local/rsc.io/quote/v3`)
   412  }
   413  
   414  // Tests that a module can be replaced by a different module path. Adapted
   415  // from the third part of mod_replace.txt.
   416  func TestModReplace3(t *testing.T) {
   417  	mt := setup(t, `
   418  -- go.mod --
   419  module quoter
   420  
   421  require not-rsc.io/quote/v3 v3.1.0
   422  replace not-rsc.io/quote/v3 v3.1.0 => ./local/rsc.io/quote/v3
   423  
   424  -- usenewmodule/main.go --
   425  package main
   426  
   427  -- local/rsc.io/quote/v3/go.mod --
   428  module rsc.io/quote/v3
   429  
   430  require rsc.io/sampler v1.3.0
   431  
   432  -- local/rsc.io/quote/v3/quote.go --
   433  package quote
   434  
   435  -- local/not-rsc.io/quote/v3/go.mod --
   436  module not-rsc.io/quote/v3
   437  
   438  -- local/not-rsc.io/quote/v3/quote.go --
   439  package quote
   440  `, "")
   441  	defer mt.cleanup()
   442  	mt.assertModuleFoundInDir("not-rsc.io/quote/v3", "quote", "local/rsc.io/quote/v3")
   443  }
   444  
   445  // Tests more local replaces, notably the case where an outer module provides
   446  // a package that could also be provided by an inner module. Adapted from
   447  // mod_replace_import.txt, with example.com/v changed to /vv because Go 1.11
   448  // thinks /v is an invalid major version.
   449  func TestModReplaceImport(t *testing.T) {
   450  	mt := setup(t, `
   451  -- go.mod --
   452  module example.com/m
   453  
   454  replace (
   455  	example.com/a => ./a
   456  	example.com/a/b => ./b
   457  )
   458  
   459  replace (
   460  	example.com/x => ./x
   461  	example.com/x/v3 => ./v3
   462  )
   463  
   464  replace (
   465  	example.com/y/z/w => ./w
   466  	example.com/y => ./y
   467  )
   468  
   469  replace (
   470  	example.com/vv v1.11.0 => ./v11
   471  	example.com/vv v1.12.0 => ./v12
   472  	example.com/vv => ./vv
   473  )
   474  
   475  require (
   476  	example.com/a/b v0.0.0
   477  	example.com/x/v3 v3.0.0
   478  	example.com/y v0.0.0
   479  	example.com/y/z/w v0.0.0
   480  	example.com/vv v1.12.0
   481  )
   482  -- m.go --
   483  package main
   484  import (
   485  	_ "example.com/a/b"
   486  	_ "example.com/x/v3"
   487  	_ "example.com/y/z/w"
   488  	_ "example.com/vv"
   489  )
   490  func main() {}
   491  
   492  -- a/go.mod --
   493  module a.localhost
   494  -- a/a.go --
   495  package a
   496  -- a/b/b.go--
   497  package b
   498  
   499  -- b/go.mod --
   500  module a.localhost/b
   501  -- b/b.go --
   502  package b
   503  
   504  -- x/go.mod --
   505  module x.localhost
   506  -- x/x.go --
   507  package x
   508  -- x/v3.go --
   509  package v3
   510  import _ "x.localhost/v3"
   511  
   512  -- v3/go.mod --
   513  module x.localhost/v3
   514  -- v3/x.go --
   515  package x
   516  
   517  -- w/go.mod --
   518  module w.localhost
   519  -- w/skip/skip.go --
   520  // Package skip is nested below nonexistent package w.
   521  package skip
   522  
   523  -- y/go.mod --
   524  module y.localhost
   525  -- y/z/w/w.go --
   526  package w
   527  
   528  -- v12/go.mod --
   529  module v.localhost
   530  -- v12/v.go --
   531  package v
   532  
   533  -- v11/go.mod --
   534  module v.localhost
   535  -- v11/v.go --
   536  package v
   537  
   538  -- vv/go.mod --
   539  module v.localhost
   540  -- vv/v.go --
   541  package v
   542  `, "")
   543  	defer mt.cleanup()
   544  
   545  	mt.assertModuleFoundInDir("example.com/a/b", "b", `main/b$`)
   546  	mt.assertModuleFoundInDir("example.com/x/v3", "x", `main/v3$`)
   547  	mt.assertModuleFoundInDir("example.com/y/z/w", "w", `main/y/z/w$`)
   548  	mt.assertModuleFoundInDir("example.com/vv", "v", `main/v12$`)
   549  }
   550  
   551  // Tests that we handle GO111MODULE=on with no go.mod file. See #30855.
   552  func TestNoMainModule(t *testing.T) {
   553  	testenv.NeedsGo1Point(t, 12)
   554  	mt := setup(t, `
   555  -- x.go --
   556  package x
   557  `, "")
   558  	defer mt.cleanup()
   559  	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
   560  		t.Fatal(err)
   561  	}
   562  
   563  	mt.assertScanFinds("rsc.io/quote", "quote")
   564  }
   565  
   566  // assertFound asserts that the package at importPath is found to have pkgName,
   567  // and that scanning for pkgName finds it at importPath.
   568  func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) {
   569  	t.Helper()
   570  
   571  	names, err := t.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir)
   572  	if err != nil {
   573  		t.Errorf("loading package name for %v: %v", importPath, err)
   574  	}
   575  	if names[importPath] != pkgName {
   576  		t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName)
   577  	}
   578  	pkg := t.assertScanFinds(importPath, pkgName)
   579  
   580  	_, foundDir := t.resolver.findPackage(importPath)
   581  	return foundDir, pkg
   582  }
   583  
   584  func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg {
   585  	t.Helper()
   586  	scan, err := scanToSlice(t.resolver, nil)
   587  	if err != nil {
   588  		t.Errorf("scan failed: %v", err)
   589  	}
   590  	for _, pkg := range scan {
   591  		if pkg.importPathShort == importPath {
   592  			return pkg
   593  		}
   594  	}
   595  	t.Errorf("scanning for %v did not find %v", pkgName, importPath)
   596  	return nil
   597  }
   598  
   599  func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) {
   600  	var mu sync.Mutex
   601  	var result []*pkg
   602  	filter := &scanCallback{
   603  		rootFound: func(root gopathwalk.Root) bool {
   604  			for _, rt := range exclude {
   605  				if root.Type == rt {
   606  					return false
   607  				}
   608  			}
   609  			return true
   610  		},
   611  		dirFound: func(pkg *pkg) bool {
   612  			return true
   613  		},
   614  		packageNameLoaded: func(pkg *pkg) bool {
   615  			mu.Lock()
   616  			defer mu.Unlock()
   617  			result = append(result, pkg)
   618  			return false
   619  		},
   620  	}
   621  	err := resolver.scan(context.Background(), filter)
   622  	return result, err
   623  }
   624  
   625  // assertModuleFoundInDir is the same as assertFound, but also checks that the
   626  // package was found in an active module whose Dir matches dirRE.
   627  func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) {
   628  	t.Helper()
   629  	dir, pkg := t.assertFound(importPath, pkgName)
   630  	re, err := regexp.Compile(dirRE)
   631  	if err != nil {
   632  		t.Fatal(err)
   633  	}
   634  
   635  	if dir == "" {
   636  		t.Errorf("import path %v not found in active modules", importPath)
   637  	} else {
   638  		if !re.MatchString(filepath.ToSlash(dir)) {
   639  			t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE)
   640  		}
   641  	}
   642  	if pkg != nil {
   643  		if !re.MatchString(filepath.ToSlash(pkg.dir)) {
   644  			t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE)
   645  		}
   646  	}
   647  }
   648  
   649  var proxyOnce sync.Once
   650  var proxyDir string
   651  
   652  type modTest struct {
   653  	*testing.T
   654  	env      *ProcessEnv
   655  	gopath   string
   656  	resolver *ModuleResolver
   657  	cleanup  func()
   658  }
   659  
   660  // setup builds a test environment from a txtar and supporting modules
   661  // in testdata/mod, along the lines of TestScript in cmd/go.
   662  func setup(t *testing.T, main, wd string) *modTest {
   663  	t.Helper()
   664  	testenv.NeedsGo1Point(t, 11)
   665  	testenv.NeedsTool(t, "go")
   666  
   667  	proxyOnce.Do(func() {
   668  		var err error
   669  		proxyDir, err = ioutil.TempDir("", "proxy-")
   670  		if err != nil {
   671  			t.Fatal(err)
   672  		}
   673  		if err := writeProxy(proxyDir, "testdata/mod"); err != nil {
   674  			t.Fatal(err)
   675  		}
   676  	})
   677  
   678  	dir, err := ioutil.TempDir("", t.Name())
   679  	if err != nil {
   680  		t.Fatal(err)
   681  	}
   682  
   683  	mainDir := filepath.Join(dir, "main")
   684  	if err := writeModule(mainDir, main); err != nil {
   685  		t.Fatal(err)
   686  	}
   687  
   688  	env := &ProcessEnv{
   689  		Env: map[string]string{
   690  			"GOPATH":      filepath.Join(dir, "gopath"),
   691  			"GOMODCACHE":  "",
   692  			"GO111MODULE": "on",
   693  			"GOSUMDB":     "off",
   694  			"GOPROXY":     proxydir.ToURL(proxyDir),
   695  		},
   696  		WorkingDir:  filepath.Join(mainDir, wd),
   697  		GocmdRunner: &gocommand.Runner{},
   698  	}
   699  	if *testDebug {
   700  		env.Logf = log.Printf
   701  	}
   702  	// go mod download gets mad if we don't have a go.mod, so make sure we do.
   703  	_, err = os.Stat(filepath.Join(mainDir, "go.mod"))
   704  	if err != nil && !os.IsNotExist(err) {
   705  		t.Fatalf("checking if go.mod exists: %v", err)
   706  	}
   707  	if err == nil {
   708  		if _, err := env.invokeGo(context.Background(), "mod", "download"); err != nil {
   709  			t.Fatal(err)
   710  		}
   711  	}
   712  
   713  	resolver, err := env.GetResolver()
   714  	if err != nil {
   715  		t.Fatal(err)
   716  	}
   717  	return &modTest{
   718  		T:        t,
   719  		gopath:   env.Env["GOPATH"],
   720  		env:      env,
   721  		resolver: resolver.(*ModuleResolver),
   722  		cleanup:  func() { removeDir(dir) },
   723  	}
   724  }
   725  
   726  // writeModule writes the module in the ar, a txtar, to dir.
   727  func writeModule(dir, ar string) error {
   728  	a := txtar.Parse([]byte(ar))
   729  
   730  	for _, f := range a.Files {
   731  		fpath := filepath.Join(dir, f.Name)
   732  		if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
   733  			return err
   734  		}
   735  
   736  		if err := ioutil.WriteFile(fpath, f.Data, 0644); err != nil {
   737  			return err
   738  		}
   739  	}
   740  	return nil
   741  }
   742  
   743  // writeProxy writes all the txtar-formatted modules in arDir to a proxy
   744  // directory in dir.
   745  func writeProxy(dir, arDir string) error {
   746  	files, err := ioutil.ReadDir(arDir)
   747  	if err != nil {
   748  		return err
   749  	}
   750  
   751  	for _, fi := range files {
   752  		if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil {
   753  			return err
   754  		}
   755  	}
   756  	return nil
   757  }
   758  
   759  // writeProxyModule writes a txtar-formatted module at arPath to the module
   760  // proxy in base.
   761  func writeProxyModule(base, arPath string) error {
   762  	arName := filepath.Base(arPath)
   763  	i := strings.LastIndex(arName, "_v")
   764  	ver := strings.TrimSuffix(arName[i+1:], ".txt")
   765  	modDir := strings.Replace(arName[:i], "_", "/", -1)
   766  	modPath, err := module.UnescapePath(modDir)
   767  	if err != nil {
   768  		return err
   769  	}
   770  
   771  	dir := filepath.Join(base, modDir, "@v")
   772  	a, err := txtar.ParseFile(arPath)
   773  
   774  	if err != nil {
   775  		return err
   776  	}
   777  
   778  	if err := os.MkdirAll(dir, 0755); err != nil {
   779  		return err
   780  	}
   781  
   782  	f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
   783  	if err != nil {
   784  		return err
   785  	}
   786  	z := zip.NewWriter(f)
   787  	for _, f := range a.Files {
   788  		if f.Name[0] == '.' {
   789  			if err := ioutil.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil {
   790  				return err
   791  			}
   792  		} else {
   793  			zf, err := z.Create(modPath + "@" + ver + "/" + f.Name)
   794  			if err != nil {
   795  				return err
   796  			}
   797  			if _, err := zf.Write(f.Data); err != nil {
   798  				return err
   799  			}
   800  		}
   801  	}
   802  	if err := z.Close(); err != nil {
   803  		return err
   804  	}
   805  	if err := f.Close(); err != nil {
   806  		return err
   807  	}
   808  
   809  	list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
   810  	if err != nil {
   811  		return err
   812  	}
   813  	if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil {
   814  		return err
   815  	}
   816  	if err := list.Close(); err != nil {
   817  		return err
   818  	}
   819  	return nil
   820  }
   821  
   822  func removeDir(dir string) {
   823  	_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   824  		if err != nil {
   825  			return nil
   826  		}
   827  		if info.IsDir() {
   828  			_ = os.Chmod(path, 0777)
   829  		}
   830  		return nil
   831  	})
   832  	_ = os.RemoveAll(dir) // ignore errors
   833  }
   834  
   835  // Tests that findModFile can find the mod files from a path in the module cache.
   836  func TestFindModFileModCache(t *testing.T) {
   837  	mt := setup(t, `
   838  -- go.mod --
   839  module x
   840  
   841  require rsc.io/quote v1.5.2
   842  -- x.go --
   843  package x
   844  import _ "rsc.io/quote"
   845  `, "")
   846  	defer mt.cleanup()
   847  	want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2")
   848  
   849  	found := mt.assertScanFinds("rsc.io/quote", "quote")
   850  	modDir, _ := mt.resolver.modInfo(found.dir)
   851  	if modDir != want {
   852  		t.Errorf("expected: %s, got: %s", want, modDir)
   853  	}
   854  }
   855  
   856  // Tests that crud in the module cache is ignored.
   857  func TestInvalidModCache(t *testing.T) {
   858  	testenv.NeedsGo1Point(t, 11)
   859  	dir, err := ioutil.TempDir("", t.Name())
   860  	if err != nil {
   861  		t.Fatal(err)
   862  	}
   863  	defer removeDir(dir)
   864  
   865  	// This doesn't have module@version like it should.
   866  	if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil {
   867  		t.Fatal(err)
   868  	}
   869  	if err := ioutil.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil {
   870  		t.Fatal(err)
   871  	}
   872  	env := &ProcessEnv{
   873  		Env: map[string]string{
   874  			"GOPATH":      filepath.Join(dir, "gopath"),
   875  			"GO111MODULE": "on",
   876  			"GOSUMDB":     "off",
   877  		},
   878  		GocmdRunner: &gocommand.Runner{},
   879  		WorkingDir:  dir,
   880  	}
   881  	resolver, err := env.GetResolver()
   882  	if err != nil {
   883  		t.Fatal(err)
   884  	}
   885  	scanToSlice(resolver, nil)
   886  }
   887  
   888  func TestGetCandidatesRanking(t *testing.T) {
   889  	mt := setup(t, `
   890  -- go.mod --
   891  module example.com
   892  
   893  require rsc.io/quote v1.5.1
   894  require rsc.io/quote/v3 v3.0.0
   895  
   896  -- rpackage/x.go --
   897  package rpackage
   898  import (
   899  	_ "rsc.io/quote"
   900  	_ "rsc.io/quote/v3"
   901  )
   902  `, "")
   903  	defer mt.cleanup()
   904  
   905  	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
   906  		t.Fatal(err)
   907  	}
   908  
   909  	type res struct {
   910  		relevance  float64
   911  		name, path string
   912  	}
   913  	want := []res{
   914  		// Stdlib
   915  		{7, "bytes", "bytes"},
   916  		{7, "http", "net/http"},
   917  		// Main module
   918  		{6, "rpackage", "example.com/rpackage"},
   919  		// Direct module deps with v2+ major version
   920  		{5.003, "quote", "rsc.io/quote/v3"},
   921  		// Direct module deps
   922  		{5, "quote", "rsc.io/quote"},
   923  		// Indirect deps
   924  		{4, "language", "golang.org/x/text/language"},
   925  		// Out of scope modules
   926  		{3, "quote", "rsc.io/quote/v2"},
   927  	}
   928  	var mu sync.Mutex
   929  	var got []res
   930  	add := func(c ImportFix) {
   931  		mu.Lock()
   932  		defer mu.Unlock()
   933  		for _, w := range want {
   934  			if c.StmtInfo.ImportPath == w.path {
   935  				got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
   936  			}
   937  		}
   938  	}
   939  	if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil {
   940  		t.Fatalf("getAllCandidates() = %v", err)
   941  	}
   942  	sort.Slice(got, func(i, j int) bool {
   943  		ri, rj := got[i], got[j]
   944  		if ri.relevance != rj.relevance {
   945  			return ri.relevance > rj.relevance // Highest first.
   946  		}
   947  		return ri.name < rj.name
   948  	})
   949  	if !reflect.DeepEqual(want, got) {
   950  		t.Errorf("wanted candidates in order %v, got %v", want, got)
   951  	}
   952  }
   953  
   954  func BenchmarkScanModCache(b *testing.B) {
   955  	testenv.NeedsGo1Point(b, 11)
   956  	env := &ProcessEnv{
   957  		GocmdRunner: &gocommand.Runner{},
   958  		Logf:        log.Printf,
   959  	}
   960  	exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
   961  	resolver, err := env.GetResolver()
   962  	if err != nil {
   963  		b.Fatal(err)
   964  	}
   965  	scanToSlice(resolver, exclude)
   966  	b.ResetTimer()
   967  	for i := 0; i < b.N; i++ {
   968  		scanToSlice(resolver, exclude)
   969  		resolver.(*ModuleResolver).ClearForNewScan()
   970  	}
   971  }