github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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  	"golang.org/x/mod/module"
    23  	"github.com/powerman/golang-tools/internal/gocommand"
    24  	"github.com/powerman/golang-tools/internal/gopathwalk"
    25  	"github.com/powerman/golang-tools/internal/proxydir"
    26  	"github.com/powerman/golang-tools/internal/testenv"
    27  	"github.com/powerman/golang-tools/txtar"
    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 go.work files are respected.
   556  func TestModWorkspace(t *testing.T) {
   557  	testenv.NeedsGo1Point(t, 18)
   558  
   559  	mt := setup(t, `
   560  -- go.work --
   561  go 1.18
   562  
   563  use (
   564  	./a
   565  	./b
   566  )
   567  -- a/go.mod --
   568  module example.com/a
   569  
   570  go 1.18
   571  -- a/a.go --
   572  package a
   573  -- b/go.mod --
   574  module example.com/b
   575  
   576  go 1.18
   577  -- b/b.go --
   578  package b
   579  `, "")
   580  	defer mt.cleanup()
   581  
   582  	mt.assertModuleFoundInDir("example.com/a", "a", `main/a$`)
   583  	mt.assertModuleFoundInDir("example.com/b", "b", `main/b$`)
   584  	mt.assertScanFinds("example.com/a", "a")
   585  	mt.assertScanFinds("example.com/b", "b")
   586  }
   587  
   588  // Tests replaces in workspaces. Uses the directory layout in the cmd/go
   589  // work_replace test. It tests both that replaces in go.work files are
   590  // respected and that a wildcard replace in go.work overrides a versioned replace
   591  // in go.mod.
   592  func TestModWorkspaceReplace(t *testing.T) {
   593  	testenv.NeedsGo1Point(t, 18)
   594  
   595  	mt := setup(t, `
   596  -- go.work --
   597  use m
   598  
   599  replace example.com/dep => ./dep
   600  replace example.com/other => ./other2
   601  
   602  -- m/go.mod --
   603  module example.com/m
   604  
   605  require example.com/dep v1.0.0
   606  require example.com/other v1.0.0
   607  
   608  replace example.com/other v1.0.0 => ./other
   609  -- m/m.go --
   610  package m
   611  
   612  import "example.com/dep"
   613  import "example.com/other"
   614  
   615  func F() {
   616  	dep.G()
   617  	other.H()
   618  }
   619  -- dep/go.mod --
   620  module example.com/dep
   621  -- dep/dep.go --
   622  package dep
   623  
   624  func G() {
   625  }
   626  -- other/go.mod --
   627  module example.com/other
   628  -- other/dep.go --
   629  package other
   630  
   631  func G() {
   632  }
   633  -- other2/go.mod --
   634  module example.com/other
   635  -- other2/dep.go --
   636  package other2
   637  
   638  func G() {
   639  }
   640  `, "")
   641  	defer mt.cleanup()
   642  
   643  	mt.assertScanFinds("example.com/m", "m")
   644  	mt.assertScanFinds("example.com/dep", "dep")
   645  	mt.assertModuleFoundInDir("example.com/other", "other2", "main/other2$")
   646  	mt.assertScanFinds("example.com/other", "other2")
   647  }
   648  
   649  // Tests a case where conflicting replaces are overridden by a replace
   650  // in the go.work file.
   651  func TestModWorkspaceReplaceOverride(t *testing.T) {
   652  	testenv.NeedsGo1Point(t, 18)
   653  
   654  	mt := setup(t, `-- go.work --
   655  use m
   656  use n
   657  replace example.com/dep => ./dep3
   658  -- m/go.mod --
   659  module example.com/m
   660  
   661  require example.com/dep v1.0.0
   662  replace example.com/dep => ./dep1
   663  -- m/m.go --
   664  package m
   665  
   666  import "example.com/dep"
   667  
   668  func F() {
   669  	dep.G()
   670  }
   671  -- n/go.mod --
   672  module example.com/n
   673  
   674  require example.com/dep v1.0.0
   675  replace example.com/dep => ./dep2
   676  -- n/n.go --
   677  package n
   678  
   679  import "example.com/dep"
   680  
   681  func F() {
   682  	dep.G()
   683  }
   684  -- dep1/go.mod --
   685  module example.com/dep
   686  -- dep1/dep.go --
   687  package dep
   688  
   689  func G() {
   690  }
   691  -- dep2/go.mod --
   692  module example.com/dep
   693  -- dep2/dep.go --
   694  package dep
   695  
   696  func G() {
   697  }
   698  -- dep3/go.mod --
   699  module example.com/dep
   700  -- dep3/dep.go --
   701  package dep
   702  
   703  func G() {
   704  }
   705  `, "")
   706  
   707  	mt.assertScanFinds("example.com/m", "m")
   708  	mt.assertScanFinds("example.com/n", "n")
   709  	mt.assertScanFinds("example.com/dep", "dep")
   710  	mt.assertModuleFoundInDir("example.com/dep", "dep", "main/dep3$")
   711  }
   712  
   713  // Tests that the correct versions of modules are found in
   714  // workspaces with module pruning. This is based on the
   715  // cmd/go mod_prune_all script test.
   716  func TestModWorkspacePrune(t *testing.T) {
   717  	testenv.NeedsGo1Point(t, 18)
   718  
   719  	mt := setup(t, `
   720  -- go.work --
   721  go 1.18
   722  
   723  use (
   724  	./a
   725  	./p
   726  )
   727  
   728  replace example.com/b v1.0.0 => ./b
   729  replace example.com/q v1.0.0 => ./q1_0_0
   730  replace example.com/q v1.0.5 => ./q1_0_5
   731  replace example.com/q v1.1.0 => ./q1_1_0
   732  replace example.com/r v1.0.0 => ./r
   733  replace example.com/w v1.0.0 => ./w
   734  replace example.com/x v1.0.0 => ./x
   735  replace example.com/y v1.0.0 => ./y
   736  replace example.com/z v1.0.0 => ./z1_0_0
   737  replace example.com/z v1.1.0 => ./z1_1_0
   738  
   739  -- a/go.mod --
   740  module example.com/a
   741  
   742  go 1.18
   743  
   744  require example.com/b v1.0.0
   745  require example.com/z v1.0.0
   746  -- a/foo.go --
   747  package main
   748  
   749  import "example.com/b"
   750  
   751  func main() {
   752  	b.B()
   753  }
   754  -- b/go.mod --
   755  module example.com/b
   756  
   757  go 1.18
   758  
   759  require example.com/q v1.1.0
   760  -- b/b.go --
   761  package b
   762  
   763  func B() {
   764  }
   765  -- p/go.mod --
   766  module example.com/p
   767  
   768  go 1.18
   769  
   770  require example.com/q v1.0.0
   771  
   772  replace example.com/q v1.0.0 => ../q1_0_0
   773  replace example.com/q v1.1.0 => ../q1_1_0
   774  -- p/main.go --
   775  package main
   776  
   777  import "example.com/q"
   778  
   779  func main() {
   780  	q.PrintVersion()
   781  }
   782  -- q1_0_0/go.mod --
   783  module example.com/q
   784  
   785  go 1.18
   786  -- q1_0_0/q.go --
   787  package q
   788  
   789  import "fmt"
   790  
   791  func PrintVersion() {
   792  	fmt.Println("version 1.0.0")
   793  }
   794  -- q1_0_5/go.mod --
   795  module example.com/q
   796  
   797  go 1.18
   798  
   799  require example.com/r v1.0.0
   800  -- q1_0_5/q.go --
   801  package q
   802  
   803  import _ "example.com/r"
   804  -- q1_1_0/go.mod --
   805  module example.com/q
   806  
   807  require example.com/w v1.0.0
   808  require example.com/z v1.1.0
   809  
   810  go 1.18
   811  -- q1_1_0/q.go --
   812  package q
   813  
   814  import _ "example.com/w"
   815  import _ "example.com/z"
   816  
   817  import "fmt"
   818  
   819  func PrintVersion() {
   820  	fmt.Println("version 1.1.0")
   821  }
   822  -- r/go.mod --
   823  module example.com/r
   824  
   825  go 1.18
   826  
   827  require example.com/r v1.0.0
   828  -- r/r.go --
   829  package r
   830  -- w/go.mod --
   831  module example.com/w
   832  
   833  go 1.18
   834  
   835  require example.com/x v1.0.0
   836  -- w/w.go --
   837  package w
   838  -- w/w_test.go --
   839  package w
   840  
   841  import _ "example.com/x"
   842  -- x/go.mod --
   843  module example.com/x
   844  
   845  go 1.18
   846  -- x/x.go --
   847  package x
   848  -- x/x_test.go --
   849  package x
   850  import _ "example.com/y"
   851  -- y/go.mod --
   852  module example.com/y
   853  
   854  go 1.18
   855  -- y/y.go --
   856  package y
   857  -- z1_0_0/go.mod --
   858  module example.com/z
   859  
   860  go 1.18
   861  
   862  require example.com/q v1.0.5
   863  -- z1_0_0/z.go --
   864  package z
   865  
   866  import _ "example.com/q"
   867  -- z1_1_0/go.mod --
   868  module example.com/z
   869  
   870  go 1.18
   871  -- z1_1_0/z.go --
   872  package z
   873  `, "")
   874  
   875  	mt.assertScanFinds("example.com/w", "w")
   876  	mt.assertScanFinds("example.com/q", "q")
   877  	mt.assertScanFinds("example.com/x", "x")
   878  	mt.assertScanFinds("example.com/z", "z")
   879  	mt.assertModuleFoundInDir("example.com/w", "w", "main/w$")
   880  	mt.assertModuleFoundInDir("example.com/q", "q", "main/q1_1_0$")
   881  	mt.assertModuleFoundInDir("example.com/x", "x", "main/x$")
   882  	mt.assertModuleFoundInDir("example.com/z", "z", "main/z1_1_0$")
   883  }
   884  
   885  // Tests that we handle GO111MODULE=on with no go.mod file. See #30855.
   886  func TestNoMainModule(t *testing.T) {
   887  	testenv.NeedsGo1Point(t, 12)
   888  	mt := setup(t, `
   889  -- x.go --
   890  package x
   891  `, "")
   892  	defer mt.cleanup()
   893  	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
   894  		t.Fatal(err)
   895  	}
   896  
   897  	mt.assertScanFinds("rsc.io/quote", "quote")
   898  }
   899  
   900  // assertFound asserts that the package at importPath is found to have pkgName,
   901  // and that scanning for pkgName finds it at importPath.
   902  func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) {
   903  	t.Helper()
   904  
   905  	names, err := t.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir)
   906  	if err != nil {
   907  		t.Errorf("loading package name for %v: %v", importPath, err)
   908  	}
   909  	if names[importPath] != pkgName {
   910  		t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName)
   911  	}
   912  	pkg := t.assertScanFinds(importPath, pkgName)
   913  
   914  	_, foundDir := t.resolver.findPackage(importPath)
   915  	return foundDir, pkg
   916  }
   917  
   918  func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg {
   919  	t.Helper()
   920  	scan, err := scanToSlice(t.resolver, nil)
   921  	if err != nil {
   922  		t.Errorf("scan failed: %v", err)
   923  	}
   924  	for _, pkg := range scan {
   925  		if pkg.importPathShort == importPath {
   926  			return pkg
   927  		}
   928  	}
   929  	t.Errorf("scanning for %v did not find %v", pkgName, importPath)
   930  	return nil
   931  }
   932  
   933  func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) {
   934  	var mu sync.Mutex
   935  	var result []*pkg
   936  	filter := &scanCallback{
   937  		rootFound: func(root gopathwalk.Root) bool {
   938  			for _, rt := range exclude {
   939  				if root.Type == rt {
   940  					return false
   941  				}
   942  			}
   943  			return true
   944  		},
   945  		dirFound: func(pkg *pkg) bool {
   946  			return true
   947  		},
   948  		packageNameLoaded: func(pkg *pkg) bool {
   949  			mu.Lock()
   950  			defer mu.Unlock()
   951  			result = append(result, pkg)
   952  			return false
   953  		},
   954  	}
   955  	err := resolver.scan(context.Background(), filter)
   956  	return result, err
   957  }
   958  
   959  // assertModuleFoundInDir is the same as assertFound, but also checks that the
   960  // package was found in an active module whose Dir matches dirRE.
   961  func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) {
   962  	t.Helper()
   963  	dir, pkg := t.assertFound(importPath, pkgName)
   964  	re, err := regexp.Compile(dirRE)
   965  	if err != nil {
   966  		t.Fatal(err)
   967  	}
   968  
   969  	if dir == "" {
   970  		t.Errorf("import path %v not found in active modules", importPath)
   971  	} else {
   972  		if !re.MatchString(filepath.ToSlash(dir)) {
   973  			t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE)
   974  		}
   975  	}
   976  	if pkg != nil {
   977  		if !re.MatchString(filepath.ToSlash(pkg.dir)) {
   978  			t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE)
   979  		}
   980  	}
   981  }
   982  
   983  var proxyOnce sync.Once
   984  var proxyDir string
   985  
   986  type modTest struct {
   987  	*testing.T
   988  	env      *ProcessEnv
   989  	gopath   string
   990  	resolver *ModuleResolver
   991  	cleanup  func()
   992  }
   993  
   994  // setup builds a test environment from a txtar and supporting modules
   995  // in testdata/mod, along the lines of TestScript in cmd/go.
   996  func setup(t *testing.T, main, wd string) *modTest {
   997  	t.Helper()
   998  	testenv.NeedsGo1Point(t, 11)
   999  	testenv.NeedsTool(t, "go")
  1000  
  1001  	proxyOnce.Do(func() {
  1002  		var err error
  1003  		proxyDir, err = ioutil.TempDir("", "proxy-")
  1004  		if err != nil {
  1005  			t.Fatal(err)
  1006  		}
  1007  		if err := writeProxy(proxyDir, "testdata/mod"); err != nil {
  1008  			t.Fatal(err)
  1009  		}
  1010  	})
  1011  
  1012  	dir, err := ioutil.TempDir("", t.Name())
  1013  	if err != nil {
  1014  		t.Fatal(err)
  1015  	}
  1016  
  1017  	mainDir := filepath.Join(dir, "main")
  1018  	if err := writeModule(mainDir, main); err != nil {
  1019  		t.Fatal(err)
  1020  	}
  1021  
  1022  	env := &ProcessEnv{
  1023  		Env: map[string]string{
  1024  			"GOPATH":      filepath.Join(dir, "gopath"),
  1025  			"GOMODCACHE":  "",
  1026  			"GO111MODULE": "on",
  1027  			"GOSUMDB":     "off",
  1028  			"GOPROXY":     proxydir.ToURL(proxyDir),
  1029  		},
  1030  		WorkingDir:  filepath.Join(mainDir, wd),
  1031  		GocmdRunner: &gocommand.Runner{},
  1032  	}
  1033  	if *testDebug {
  1034  		env.Logf = log.Printf
  1035  	}
  1036  	// go mod download gets mad if we don't have a go.mod, so make sure we do.
  1037  	_, err = os.Stat(filepath.Join(mainDir, "go.mod"))
  1038  	if err != nil && !os.IsNotExist(err) {
  1039  		t.Fatalf("checking if go.mod exists: %v", err)
  1040  	}
  1041  	if err == nil {
  1042  		if _, err := env.invokeGo(context.Background(), "mod", "download", "all"); err != nil {
  1043  			t.Fatal(err)
  1044  		}
  1045  	}
  1046  
  1047  	resolver, err := env.GetResolver()
  1048  	if err != nil {
  1049  		t.Fatal(err)
  1050  	}
  1051  	return &modTest{
  1052  		T:        t,
  1053  		gopath:   env.Env["GOPATH"],
  1054  		env:      env,
  1055  		resolver: resolver.(*ModuleResolver),
  1056  		cleanup:  func() { removeDir(dir) },
  1057  	}
  1058  }
  1059  
  1060  // writeModule writes the module in the ar, a txtar, to dir.
  1061  func writeModule(dir, ar string) error {
  1062  	a := txtar.Parse([]byte(ar))
  1063  
  1064  	for _, f := range a.Files {
  1065  		fpath := filepath.Join(dir, f.Name)
  1066  		if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
  1067  			return err
  1068  		}
  1069  
  1070  		if err := ioutil.WriteFile(fpath, f.Data, 0644); err != nil {
  1071  			return err
  1072  		}
  1073  	}
  1074  	return nil
  1075  }
  1076  
  1077  // writeProxy writes all the txtar-formatted modules in arDir to a proxy
  1078  // directory in dir.
  1079  func writeProxy(dir, arDir string) error {
  1080  	files, err := ioutil.ReadDir(arDir)
  1081  	if err != nil {
  1082  		return err
  1083  	}
  1084  
  1085  	for _, fi := range files {
  1086  		if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil {
  1087  			return err
  1088  		}
  1089  	}
  1090  	return nil
  1091  }
  1092  
  1093  // writeProxyModule writes a txtar-formatted module at arPath to the module
  1094  // proxy in base.
  1095  func writeProxyModule(base, arPath string) error {
  1096  	arName := filepath.Base(arPath)
  1097  	i := strings.LastIndex(arName, "_v")
  1098  	ver := strings.TrimSuffix(arName[i+1:], ".txt")
  1099  	modDir := strings.Replace(arName[:i], "_", "/", -1)
  1100  	modPath, err := module.UnescapePath(modDir)
  1101  	if err != nil {
  1102  		return err
  1103  	}
  1104  
  1105  	dir := filepath.Join(base, modDir, "@v")
  1106  	a, err := txtar.ParseFile(arPath)
  1107  
  1108  	if err != nil {
  1109  		return err
  1110  	}
  1111  
  1112  	if err := os.MkdirAll(dir, 0755); err != nil {
  1113  		return err
  1114  	}
  1115  
  1116  	f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
  1117  	if err != nil {
  1118  		return err
  1119  	}
  1120  	z := zip.NewWriter(f)
  1121  	for _, f := range a.Files {
  1122  		if f.Name[0] == '.' {
  1123  			if err := ioutil.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil {
  1124  				return err
  1125  			}
  1126  		} else {
  1127  			zf, err := z.Create(modPath + "@" + ver + "/" + f.Name)
  1128  			if err != nil {
  1129  				return err
  1130  			}
  1131  			if _, err := zf.Write(f.Data); err != nil {
  1132  				return err
  1133  			}
  1134  		}
  1135  	}
  1136  	if err := z.Close(); err != nil {
  1137  		return err
  1138  	}
  1139  	if err := f.Close(); err != nil {
  1140  		return err
  1141  	}
  1142  
  1143  	list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
  1144  	if err != nil {
  1145  		return err
  1146  	}
  1147  	if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil {
  1148  		return err
  1149  	}
  1150  	if err := list.Close(); err != nil {
  1151  		return err
  1152  	}
  1153  	return nil
  1154  }
  1155  
  1156  func removeDir(dir string) {
  1157  	_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
  1158  		if err != nil {
  1159  			return nil
  1160  		}
  1161  		if info.IsDir() {
  1162  			_ = os.Chmod(path, 0777)
  1163  		}
  1164  		return nil
  1165  	})
  1166  	_ = os.RemoveAll(dir) // ignore errors
  1167  }
  1168  
  1169  // Tests that findModFile can find the mod files from a path in the module cache.
  1170  func TestFindModFileModCache(t *testing.T) {
  1171  	mt := setup(t, `
  1172  -- go.mod --
  1173  module x
  1174  
  1175  require rsc.io/quote v1.5.2
  1176  -- x.go --
  1177  package x
  1178  import _ "rsc.io/quote"
  1179  `, "")
  1180  	defer mt.cleanup()
  1181  	want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2")
  1182  
  1183  	found := mt.assertScanFinds("rsc.io/quote", "quote")
  1184  	modDir, _ := mt.resolver.modInfo(found.dir)
  1185  	if modDir != want {
  1186  		t.Errorf("expected: %s, got: %s", want, modDir)
  1187  	}
  1188  }
  1189  
  1190  // Tests that crud in the module cache is ignored.
  1191  func TestInvalidModCache(t *testing.T) {
  1192  	testenv.NeedsGo1Point(t, 11)
  1193  	dir, err := ioutil.TempDir("", t.Name())
  1194  	if err != nil {
  1195  		t.Fatal(err)
  1196  	}
  1197  	defer removeDir(dir)
  1198  
  1199  	// This doesn't have module@version like it should.
  1200  	if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil {
  1201  		t.Fatal(err)
  1202  	}
  1203  	if err := ioutil.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil {
  1204  		t.Fatal(err)
  1205  	}
  1206  	env := &ProcessEnv{
  1207  		Env: map[string]string{
  1208  			"GOPATH":      filepath.Join(dir, "gopath"),
  1209  			"GO111MODULE": "on",
  1210  			"GOSUMDB":     "off",
  1211  		},
  1212  		GocmdRunner: &gocommand.Runner{},
  1213  		WorkingDir:  dir,
  1214  	}
  1215  	resolver, err := env.GetResolver()
  1216  	if err != nil {
  1217  		t.Fatal(err)
  1218  	}
  1219  	scanToSlice(resolver, nil)
  1220  }
  1221  
  1222  func TestGetCandidatesRanking(t *testing.T) {
  1223  	mt := setup(t, `
  1224  -- go.mod --
  1225  module example.com
  1226  
  1227  require rsc.io/quote v1.5.1
  1228  require rsc.io/quote/v3 v3.0.0
  1229  
  1230  -- rpackage/x.go --
  1231  package rpackage
  1232  import (
  1233  	_ "rsc.io/quote"
  1234  	_ "rsc.io/quote/v3"
  1235  )
  1236  `, "")
  1237  	defer mt.cleanup()
  1238  
  1239  	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
  1240  		t.Fatal(err)
  1241  	}
  1242  
  1243  	type res struct {
  1244  		relevance  float64
  1245  		name, path string
  1246  	}
  1247  	want := []res{
  1248  		// Stdlib
  1249  		{7, "bytes", "bytes"},
  1250  		{7, "http", "net/http"},
  1251  		// Main module
  1252  		{6, "rpackage", "example.com/rpackage"},
  1253  		// Direct module deps with v2+ major version
  1254  		{5.003, "quote", "rsc.io/quote/v3"},
  1255  		// Direct module deps
  1256  		{5, "quote", "rsc.io/quote"},
  1257  		// Indirect deps
  1258  		{4, "language", "golang.org/x/text/language"},
  1259  		// Out of scope modules
  1260  		{3, "quote", "rsc.io/quote/v2"},
  1261  	}
  1262  	var mu sync.Mutex
  1263  	var got []res
  1264  	add := func(c ImportFix) {
  1265  		mu.Lock()
  1266  		defer mu.Unlock()
  1267  		for _, w := range want {
  1268  			if c.StmtInfo.ImportPath == w.path {
  1269  				got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
  1270  			}
  1271  		}
  1272  	}
  1273  	if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil {
  1274  		t.Fatalf("getAllCandidates() = %v", err)
  1275  	}
  1276  	sort.Slice(got, func(i, j int) bool {
  1277  		ri, rj := got[i], got[j]
  1278  		if ri.relevance != rj.relevance {
  1279  			return ri.relevance > rj.relevance // Highest first.
  1280  		}
  1281  		return ri.name < rj.name
  1282  	})
  1283  	if !reflect.DeepEqual(want, got) {
  1284  		t.Errorf("wanted candidates in order %v, got %v", want, got)
  1285  	}
  1286  }
  1287  
  1288  func BenchmarkScanModCache(b *testing.B) {
  1289  	testenv.NeedsGo1Point(b, 11)
  1290  	env := &ProcessEnv{
  1291  		GocmdRunner: &gocommand.Runner{},
  1292  		Logf:        log.Printf,
  1293  	}
  1294  	exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
  1295  	resolver, err := env.GetResolver()
  1296  	if err != nil {
  1297  		b.Fatal(err)
  1298  	}
  1299  	scanToSlice(resolver, exclude)
  1300  	b.ResetTimer()
  1301  	for i := 0; i < b.N; i++ {
  1302  		scanToSlice(resolver, exclude)
  1303  		resolver.(*ModuleResolver).ClearForNewScan()
  1304  	}
  1305  }