github.com/v2fly/tools@v0.100.0/internal/imports/mod_test.go (about)

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