golang.org/x/tools@v0.21.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  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"regexp"
    16  	"sort"
    17  	"strings"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"golang.org/x/mod/module"
    23  	"golang.org/x/tools/internal/gocommand"
    24  	"golang.org/x/tools/internal/gopathwalk"
    25  	"golang.org/x/tools/internal/proxydir"
    26  	"golang.org/x/tools/internal/testenv"
    27  	"golang.org/x/tools/txtar"
    28  )
    29  
    30  // Tests that we can find packages in the stdlib.
    31  func TestScanStdlib(t *testing.T) {
    32  	mt := setup(t, nil, `
    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, nil, `
    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, nil, `
    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.env.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, nil, `
   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, nil, `
   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, nil, `
   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, nil, `
   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, nil, `
   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 := os.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 := os.WriteFile(filepath.Join(mt.env.WorkingDir, "go.mod"), []byte("module x\n"), 0644); err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	if err := os.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.env.ClearModuleInfo()
   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, nil, `
   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.env.Env["GOFLAGS"] = "-mod=vendor"
   246  	mt.env.ClearModuleInfo()
   247  	mt.env.UpdateResolver(mt.env.resolver.ClearForNewScan())
   248  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`)
   249  }
   250  
   251  // Tests that -mod=vendor is auto-enabled only for go1.14 and higher.
   252  // Vaguely inspired by mod_vendor_auto.txt.
   253  func TestModVendorAuto(t *testing.T) {
   254  	mt := setup(t, nil, `
   255  -- go.mod --
   256  module m
   257  go 1.14
   258  require rsc.io/sampler v1.3.1
   259  -- x.go --
   260  package x
   261  import _ "rsc.io/sampler"
   262  `, "")
   263  	defer mt.cleanup()
   264  
   265  	// Populate vendor/.
   266  	if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
   267  		t.Fatal(err)
   268  	}
   269  
   270  	wantDir := `pkg.*mod.*/sampler@.*$`
   271  	if testenv.Go1Point() >= 14 {
   272  		wantDir = `/vendor/`
   273  	}
   274  
   275  	// Clear out the resolver's module info, since we've changed the environment.
   276  	// (the presence of a /vendor directory affects `go list -m`).
   277  	mt.env.ClearModuleInfo()
   278  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir)
   279  }
   280  
   281  // Tests that a module replace works. Adapted from mod_list.txt. We start with
   282  // go.mod2; the first part of the test is irrelevant.
   283  func TestModList(t *testing.T) {
   284  	mt := setup(t, nil, `
   285  -- go.mod --
   286  module x
   287  require rsc.io/quote v1.5.1
   288  replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1
   289  
   290  -- x.go --
   291  package x
   292  import _ "rsc.io/quote"
   293  `, "")
   294  	defer mt.cleanup()
   295  
   296  	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.mod.*/sampler@v1.3.1$`)
   297  }
   298  
   299  // Tests that a local replace works. Adapted from mod_local_replace.txt.
   300  func TestModLocalReplace(t *testing.T) {
   301  	mt := setup(t, nil, `
   302  -- x/y/go.mod --
   303  module x/y
   304  require zz v1.0.0
   305  replace zz v1.0.0 => ../z
   306  
   307  -- x/y/y.go --
   308  package y
   309  import _ "zz"
   310  
   311  -- x/z/go.mod --
   312  module x/z
   313  
   314  -- x/z/z.go --
   315  package z
   316  `, "x/y")
   317  	defer mt.cleanup()
   318  
   319  	mt.assertFound("zz", "z")
   320  }
   321  
   322  // Tests that the package at the root of the main module can be found.
   323  // Adapted from the first part of mod_multirepo.txt.
   324  func TestModMultirepo1(t *testing.T) {
   325  	mt := setup(t, nil, `
   326  -- go.mod --
   327  module rsc.io/quote
   328  
   329  -- x.go --
   330  package quote
   331  `, "")
   332  	defer mt.cleanup()
   333  
   334  	mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
   335  }
   336  
   337  // Tests that a simple module dependency is found. Adapted from the third part
   338  // of mod_multirepo.txt (We skip the case where it doesn't have a go.mod
   339  // entry -- we just don't work in that case.)
   340  func TestModMultirepo3(t *testing.T) {
   341  	mt := setup(t, nil, `
   342  -- go.mod --
   343  module rsc.io/quote
   344  
   345  require rsc.io/quote/v2 v2.0.1
   346  -- x.go --
   347  package quote
   348  
   349  import _ "rsc.io/quote/v2"
   350  `, "")
   351  	defer mt.cleanup()
   352  
   353  	mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
   354  	mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
   355  }
   356  
   357  // Tests that a nested module is found in the module cache, even though
   358  // it's checked out. Adapted from the fourth part of mod_multirepo.txt.
   359  func TestModMultirepo4(t *testing.T) {
   360  	mt := setup(t, nil, `
   361  -- go.mod --
   362  module rsc.io/quote
   363  require rsc.io/quote/v2 v2.0.1
   364  
   365  -- x.go --
   366  package quote
   367  import _ "rsc.io/quote/v2"
   368  
   369  -- v2/go.mod --
   370  package rsc.io/quote/v2
   371  
   372  -- v2/x.go --
   373  package quote
   374  import _ "rsc.io/quote/v2"
   375  `, "")
   376  	defer mt.cleanup()
   377  
   378  	mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
   379  	mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
   380  }
   381  
   382  // Tests a simple module dependency. Adapted from the first part of mod_replace.txt.
   383  func TestModReplace1(t *testing.T) {
   384  	mt := setup(t, nil, `
   385  -- go.mod --
   386  module quoter
   387  
   388  require rsc.io/quote/v3 v3.0.0
   389  
   390  -- main.go --
   391  
   392  package main
   393  `, "")
   394  	defer mt.cleanup()
   395  	mt.assertFound("rsc.io/quote/v3", "quote")
   396  }
   397  
   398  // Tests a local replace. Adapted from the second part of mod_replace.txt.
   399  func TestModReplace2(t *testing.T) {
   400  	mt := setup(t, nil, `
   401  -- go.mod --
   402  module quoter
   403  
   404  require rsc.io/quote/v3 v3.0.0
   405  replace rsc.io/quote/v3 => ./local/rsc.io/quote/v3
   406  -- main.go --
   407  package main
   408  
   409  -- local/rsc.io/quote/v3/go.mod --
   410  module rsc.io/quote/v3
   411  
   412  require rsc.io/sampler v1.3.0
   413  
   414  -- local/rsc.io/quote/v3/quote.go --
   415  package quote
   416  
   417  import "rsc.io/sampler"
   418  `, "")
   419  	defer mt.cleanup()
   420  	mt.assertModuleFoundInDir("rsc.io/quote/v3", "quote", `/local/rsc.io/quote/v3`)
   421  }
   422  
   423  // Tests that a module can be replaced by a different module path. Adapted
   424  // from the third part of mod_replace.txt.
   425  func TestModReplace3(t *testing.T) {
   426  	mt := setup(t, nil, `
   427  -- go.mod --
   428  module quoter
   429  
   430  require not-rsc.io/quote/v3 v3.1.0
   431  replace not-rsc.io/quote/v3 v3.1.0 => ./local/rsc.io/quote/v3
   432  
   433  -- usenewmodule/main.go --
   434  package main
   435  
   436  -- local/rsc.io/quote/v3/go.mod --
   437  module rsc.io/quote/v3
   438  
   439  require rsc.io/sampler v1.3.0
   440  
   441  -- local/rsc.io/quote/v3/quote.go --
   442  package quote
   443  
   444  -- local/not-rsc.io/quote/v3/go.mod --
   445  module not-rsc.io/quote/v3
   446  
   447  -- local/not-rsc.io/quote/v3/quote.go --
   448  package quote
   449  `, "")
   450  	defer mt.cleanup()
   451  	mt.assertModuleFoundInDir("not-rsc.io/quote/v3", "quote", "local/rsc.io/quote/v3")
   452  }
   453  
   454  // Tests more local replaces, notably the case where an outer module provides
   455  // a package that could also be provided by an inner module. Adapted from
   456  // mod_replace_import.txt, with example.com/v changed to /vv because Go 1.11
   457  // thinks /v is an invalid major version.
   458  func TestModReplaceImport(t *testing.T) {
   459  	mt := setup(t, nil, `
   460  -- go.mod --
   461  module example.com/m
   462  
   463  replace (
   464  	example.com/a => ./a
   465  	example.com/a/b => ./b
   466  )
   467  
   468  replace (
   469  	example.com/x => ./x
   470  	example.com/x/v3 => ./v3
   471  )
   472  
   473  replace (
   474  	example.com/y/z/w => ./w
   475  	example.com/y => ./y
   476  )
   477  
   478  replace (
   479  	example.com/vv v1.11.0 => ./v11
   480  	example.com/vv v1.12.0 => ./v12
   481  	example.com/vv => ./vv
   482  )
   483  
   484  require (
   485  	example.com/a/b v0.0.0
   486  	example.com/x/v3 v3.0.0
   487  	example.com/y v0.0.0
   488  	example.com/y/z/w v0.0.0
   489  	example.com/vv v1.12.0
   490  )
   491  -- m.go --
   492  package main
   493  import (
   494  	_ "example.com/a/b"
   495  	_ "example.com/x/v3"
   496  	_ "example.com/y/z/w"
   497  	_ "example.com/vv"
   498  )
   499  func main() {}
   500  
   501  -- a/go.mod --
   502  module a.localhost
   503  -- a/a.go --
   504  package a
   505  -- a/b/b.go--
   506  package b
   507  
   508  -- b/go.mod --
   509  module a.localhost/b
   510  -- b/b.go --
   511  package b
   512  
   513  -- x/go.mod --
   514  module x.localhost
   515  -- x/x.go --
   516  package x
   517  -- x/v3.go --
   518  package v3
   519  import _ "x.localhost/v3"
   520  
   521  -- v3/go.mod --
   522  module x.localhost/v3
   523  -- v3/x.go --
   524  package x
   525  
   526  -- w/go.mod --
   527  module w.localhost
   528  -- w/skip/skip.go --
   529  // Package skip is nested below nonexistent package w.
   530  package skip
   531  
   532  -- y/go.mod --
   533  module y.localhost
   534  -- y/z/w/w.go --
   535  package w
   536  
   537  -- v12/go.mod --
   538  module v.localhost
   539  -- v12/v.go --
   540  package v
   541  
   542  -- v11/go.mod --
   543  module v.localhost
   544  -- v11/v.go --
   545  package v
   546  
   547  -- vv/go.mod --
   548  module v.localhost
   549  -- vv/v.go --
   550  package v
   551  `, "")
   552  	defer mt.cleanup()
   553  
   554  	mt.assertModuleFoundInDir("example.com/a/b", "b", `main/b$`)
   555  	mt.assertModuleFoundInDir("example.com/x/v3", "x", `main/v3$`)
   556  	mt.assertModuleFoundInDir("example.com/y/z/w", "w", `main/y/z/w$`)
   557  	mt.assertModuleFoundInDir("example.com/vv", "v", `main/v12$`)
   558  }
   559  
   560  // Tests that go.work files are respected.
   561  func TestModWorkspace(t *testing.T) {
   562  	mt := setup(t, nil, `
   563  -- go.work --
   564  go 1.18
   565  
   566  use (
   567  	./a
   568  	./b
   569  )
   570  -- a/go.mod --
   571  module example.com/a
   572  
   573  go 1.18
   574  -- a/a.go --
   575  package a
   576  -- b/go.mod --
   577  module example.com/b
   578  
   579  go 1.18
   580  -- b/b.go --
   581  package b
   582  `, "")
   583  	defer mt.cleanup()
   584  
   585  	mt.assertModuleFoundInDir("example.com/a", "a", `main/a$`)
   586  	mt.assertModuleFoundInDir("example.com/b", "b", `main/b$`)
   587  	mt.assertScanFinds("example.com/a", "a")
   588  	mt.assertScanFinds("example.com/b", "b")
   589  }
   590  
   591  // Tests replaces in workspaces. Uses the directory layout in the cmd/go
   592  // work_replace test. It tests both that replaces in go.work files are
   593  // respected and that a wildcard replace in go.work overrides a versioned replace
   594  // in go.mod.
   595  func TestModWorkspaceReplace(t *testing.T) {
   596  	mt := setup(t, nil, `
   597  -- go.work --
   598  use m
   599  
   600  replace example.com/dep => ./dep
   601  replace example.com/other => ./other2
   602  
   603  -- m/go.mod --
   604  module example.com/m
   605  
   606  require example.com/dep v1.0.0
   607  require example.com/other v1.0.0
   608  
   609  replace example.com/other v1.0.0 => ./other
   610  -- m/m.go --
   611  package m
   612  
   613  import "example.com/dep"
   614  import "example.com/other"
   615  
   616  func F() {
   617  	dep.G()
   618  	other.H()
   619  }
   620  -- dep/go.mod --
   621  module example.com/dep
   622  -- dep/dep.go --
   623  package dep
   624  
   625  func G() {
   626  }
   627  -- other/go.mod --
   628  module example.com/other
   629  -- other/dep.go --
   630  package other
   631  
   632  func G() {
   633  }
   634  -- other2/go.mod --
   635  module example.com/other
   636  -- other2/dep.go --
   637  package other2
   638  
   639  func G() {
   640  }
   641  `, "")
   642  	defer mt.cleanup()
   643  
   644  	mt.assertScanFinds("example.com/m", "m")
   645  	mt.assertScanFinds("example.com/dep", "dep")
   646  	mt.assertModuleFoundInDir("example.com/other", "other2", "main/other2$")
   647  	mt.assertScanFinds("example.com/other", "other2")
   648  }
   649  
   650  // Tests a case where conflicting replaces are overridden by a replace
   651  // in the go.work file.
   652  func TestModWorkspaceReplaceOverride(t *testing.T) {
   653  	mt := setup(t, nil, `-- go.work --
   654  use m
   655  use n
   656  replace example.com/dep => ./dep3
   657  -- m/go.mod --
   658  module example.com/m
   659  
   660  require example.com/dep v1.0.0
   661  replace example.com/dep => ./dep1
   662  -- m/m.go --
   663  package m
   664  
   665  import "example.com/dep"
   666  
   667  func F() {
   668  	dep.G()
   669  }
   670  -- n/go.mod --
   671  module example.com/n
   672  
   673  require example.com/dep v1.0.0
   674  replace example.com/dep => ./dep2
   675  -- n/n.go --
   676  package n
   677  
   678  import "example.com/dep"
   679  
   680  func F() {
   681  	dep.G()
   682  }
   683  -- dep1/go.mod --
   684  module example.com/dep
   685  -- dep1/dep.go --
   686  package dep
   687  
   688  func G() {
   689  }
   690  -- dep2/go.mod --
   691  module example.com/dep
   692  -- dep2/dep.go --
   693  package dep
   694  
   695  func G() {
   696  }
   697  -- dep3/go.mod --
   698  module example.com/dep
   699  -- dep3/dep.go --
   700  package dep
   701  
   702  func G() {
   703  }
   704  `, "")
   705  
   706  	mt.assertScanFinds("example.com/m", "m")
   707  	mt.assertScanFinds("example.com/n", "n")
   708  	mt.assertScanFinds("example.com/dep", "dep")
   709  	mt.assertModuleFoundInDir("example.com/dep", "dep", "main/dep3$")
   710  }
   711  
   712  // Tests that the correct versions of modules are found in
   713  // workspaces with module pruning. This is based on the
   714  // cmd/go mod_prune_all script test.
   715  func TestModWorkspacePrune(t *testing.T) {
   716  	mt := setup(t, nil, `
   717  -- go.work --
   718  go 1.18
   719  
   720  use (
   721  	./a
   722  	./p
   723  )
   724  
   725  replace example.com/b v1.0.0 => ./b
   726  replace example.com/q v1.0.0 => ./q1_0_0
   727  replace example.com/q v1.0.5 => ./q1_0_5
   728  replace example.com/q v1.1.0 => ./q1_1_0
   729  replace example.com/r v1.0.0 => ./r
   730  replace example.com/w v1.0.0 => ./w
   731  replace example.com/x v1.0.0 => ./x
   732  replace example.com/y v1.0.0 => ./y
   733  replace example.com/z v1.0.0 => ./z1_0_0
   734  replace example.com/z v1.1.0 => ./z1_1_0
   735  
   736  -- a/go.mod --
   737  module example.com/a
   738  
   739  go 1.18
   740  
   741  require example.com/b v1.0.0
   742  require example.com/z v1.0.0
   743  -- a/foo.go --
   744  package main
   745  
   746  import "example.com/b"
   747  
   748  func main() {
   749  	b.B()
   750  }
   751  -- b/go.mod --
   752  module example.com/b
   753  
   754  go 1.18
   755  
   756  require example.com/q v1.1.0
   757  -- b/b.go --
   758  package b
   759  
   760  func B() {
   761  }
   762  -- p/go.mod --
   763  module example.com/p
   764  
   765  go 1.18
   766  
   767  require example.com/q v1.0.0
   768  
   769  replace example.com/q v1.0.0 => ../q1_0_0
   770  replace example.com/q v1.1.0 => ../q1_1_0
   771  -- p/main.go --
   772  package main
   773  
   774  import "example.com/q"
   775  
   776  func main() {
   777  	q.PrintVersion()
   778  }
   779  -- q1_0_0/go.mod --
   780  module example.com/q
   781  
   782  go 1.18
   783  -- q1_0_0/q.go --
   784  package q
   785  
   786  import "fmt"
   787  
   788  func PrintVersion() {
   789  	fmt.Println("version 1.0.0")
   790  }
   791  -- q1_0_5/go.mod --
   792  module example.com/q
   793  
   794  go 1.18
   795  
   796  require example.com/r v1.0.0
   797  -- q1_0_5/q.go --
   798  package q
   799  
   800  import _ "example.com/r"
   801  -- q1_1_0/go.mod --
   802  module example.com/q
   803  
   804  require example.com/w v1.0.0
   805  require example.com/z v1.1.0
   806  
   807  go 1.18
   808  -- q1_1_0/q.go --
   809  package q
   810  
   811  import _ "example.com/w"
   812  import _ "example.com/z"
   813  
   814  import "fmt"
   815  
   816  func PrintVersion() {
   817  	fmt.Println("version 1.1.0")
   818  }
   819  -- r/go.mod --
   820  module example.com/r
   821  
   822  go 1.18
   823  
   824  require example.com/r v1.0.0
   825  -- r/r.go --
   826  package r
   827  -- w/go.mod --
   828  module example.com/w
   829  
   830  go 1.18
   831  
   832  require example.com/x v1.0.0
   833  -- w/w.go --
   834  package w
   835  -- w/w_test.go --
   836  package w
   837  
   838  import _ "example.com/x"
   839  -- x/go.mod --
   840  module example.com/x
   841  
   842  go 1.18
   843  -- x/x.go --
   844  package x
   845  -- x/x_test.go --
   846  package x
   847  import _ "example.com/y"
   848  -- y/go.mod --
   849  module example.com/y
   850  
   851  go 1.18
   852  -- y/y.go --
   853  package y
   854  -- z1_0_0/go.mod --
   855  module example.com/z
   856  
   857  go 1.18
   858  
   859  require example.com/q v1.0.5
   860  -- z1_0_0/z.go --
   861  package z
   862  
   863  import _ "example.com/q"
   864  -- z1_1_0/go.mod --
   865  module example.com/z
   866  
   867  go 1.18
   868  -- z1_1_0/z.go --
   869  package z
   870  `, "")
   871  
   872  	mt.assertScanFinds("example.com/w", "w")
   873  	mt.assertScanFinds("example.com/q", "q")
   874  	mt.assertScanFinds("example.com/x", "x")
   875  	mt.assertScanFinds("example.com/z", "z")
   876  	mt.assertModuleFoundInDir("example.com/w", "w", "main/w$")
   877  	mt.assertModuleFoundInDir("example.com/q", "q", "main/q1_1_0$")
   878  	mt.assertModuleFoundInDir("example.com/x", "x", "main/x$")
   879  	mt.assertModuleFoundInDir("example.com/z", "z", "main/z1_1_0$")
   880  }
   881  
   882  // Tests that we handle GO111MODULE=on with no go.mod file. See #30855.
   883  func TestNoMainModule(t *testing.T) {
   884  	mt := setup(t, map[string]string{"GO111MODULE": "on"}, `
   885  -- x.go --
   886  package x
   887  `, "")
   888  	defer mt.cleanup()
   889  	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
   890  		t.Fatal(err)
   891  	}
   892  
   893  	mt.assertScanFinds("rsc.io/quote", "quote")
   894  }
   895  
   896  // assertFound asserts that the package at importPath is found to have pkgName,
   897  // and that scanning for pkgName finds it at importPath.
   898  func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) {
   899  	t.Helper()
   900  
   901  	names, err := t.env.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir)
   902  	if err != nil {
   903  		t.Errorf("loading package name for %v: %v", importPath, err)
   904  	}
   905  	if names[importPath] != pkgName {
   906  		t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName)
   907  	}
   908  	pkg := t.assertScanFinds(importPath, pkgName)
   909  
   910  	_, foundDir := t.env.resolver.(*ModuleResolver).findPackage(importPath)
   911  	return foundDir, pkg
   912  }
   913  
   914  func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg {
   915  	t.Helper()
   916  	scan, err := scanToSlice(t.env.resolver, nil)
   917  	if err != nil {
   918  		t.Errorf("scan failed: %v", err)
   919  	}
   920  	for _, pkg := range scan {
   921  		if pkg.importPathShort == importPath {
   922  			return pkg
   923  		}
   924  	}
   925  	t.Errorf("scanning for %v did not find %v", pkgName, importPath)
   926  	return nil
   927  }
   928  
   929  func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) {
   930  	var mu sync.Mutex
   931  	var result []*pkg
   932  	filter := &scanCallback{
   933  		rootFound: func(root gopathwalk.Root) bool {
   934  			for _, rt := range exclude {
   935  				if root.Type == rt {
   936  					return false
   937  				}
   938  			}
   939  			return true
   940  		},
   941  		dirFound: func(pkg *pkg) bool {
   942  			return true
   943  		},
   944  		packageNameLoaded: func(pkg *pkg) bool {
   945  			mu.Lock()
   946  			defer mu.Unlock()
   947  			result = append(result, pkg)
   948  			return false
   949  		},
   950  	}
   951  	err := resolver.scan(context.Background(), filter)
   952  	return result, err
   953  }
   954  
   955  // assertModuleFoundInDir is the same as assertFound, but also checks that the
   956  // package was found in an active module whose Dir matches dirRE.
   957  func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) {
   958  	t.Helper()
   959  	dir, pkg := t.assertFound(importPath, pkgName)
   960  	re, err := regexp.Compile(dirRE)
   961  	if err != nil {
   962  		t.Fatal(err)
   963  	}
   964  
   965  	if dir == "" {
   966  		t.Errorf("import path %v not found in active modules", importPath)
   967  	} else {
   968  		if !re.MatchString(filepath.ToSlash(dir)) {
   969  			t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE)
   970  		}
   971  	}
   972  	if pkg != nil {
   973  		if !re.MatchString(filepath.ToSlash(pkg.dir)) {
   974  			t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE)
   975  		}
   976  	}
   977  }
   978  
   979  var proxyOnce sync.Once
   980  var proxyDir string
   981  
   982  type modTest struct {
   983  	*testing.T
   984  	env     *ProcessEnv
   985  	gopath  string
   986  	cleanup func()
   987  }
   988  
   989  // setup builds a test environment from a txtar and supporting modules
   990  // in testdata/mod, along the lines of TestScript in cmd/go.
   991  //
   992  // extraEnv is applied on top of the default test env.
   993  func setup(t *testing.T, extraEnv map[string]string, main, wd string) *modTest {
   994  	t.Helper()
   995  	testenv.NeedsTool(t, "go")
   996  
   997  	proxyOnce.Do(func() {
   998  		var err error
   999  		proxyDir, err = os.MkdirTemp("", "proxy-")
  1000  		if err != nil {
  1001  			t.Fatal(err)
  1002  		}
  1003  		if err := writeProxy(proxyDir, "testdata/mod"); err != nil {
  1004  			t.Fatal(err)
  1005  		}
  1006  	})
  1007  
  1008  	dir, err := os.MkdirTemp("", t.Name())
  1009  	if err != nil {
  1010  		t.Fatal(err)
  1011  	}
  1012  
  1013  	mainDir := filepath.Join(dir, "main")
  1014  	if err := writeModule(mainDir, main); err != nil {
  1015  		t.Fatal(err)
  1016  	}
  1017  
  1018  	env := &ProcessEnv{
  1019  		Env: map[string]string{
  1020  			"GOPATH":      filepath.Join(dir, "gopath"),
  1021  			"GOMODCACHE":  "",
  1022  			"GO111MODULE": "auto",
  1023  			"GOSUMDB":     "off",
  1024  			"GOPROXY":     proxydir.ToURL(proxyDir),
  1025  		},
  1026  		WorkingDir:  filepath.Join(mainDir, wd),
  1027  		GocmdRunner: &gocommand.Runner{},
  1028  	}
  1029  	for k, v := range extraEnv {
  1030  		env.Env[k] = v
  1031  	}
  1032  	if *testDebug {
  1033  		env.Logf = log.Printf
  1034  	}
  1035  	// go mod download gets mad if we don't have a go.mod, so make sure we do.
  1036  	_, err = os.Stat(filepath.Join(mainDir, "go.mod"))
  1037  	if err != nil && !os.IsNotExist(err) {
  1038  		t.Fatalf("checking if go.mod exists: %v", err)
  1039  	}
  1040  	if err == nil {
  1041  		if _, err := env.invokeGo(context.Background(), "mod", "download", "all"); err != nil {
  1042  			t.Fatal(err)
  1043  		}
  1044  	}
  1045  
  1046  	// Ensure the resolver is set for tests that (unsafely) access env.resolver
  1047  	// directly.
  1048  	//
  1049  	// TODO(rfindley): fix this after addressing the TODO in the ProcessEnv
  1050  	// docstring.
  1051  	if _, err := env.GetResolver(); err != nil {
  1052  		t.Fatal(err)
  1053  	}
  1054  
  1055  	return &modTest{
  1056  		T:       t,
  1057  		gopath:  env.Env["GOPATH"],
  1058  		env:     env,
  1059  		cleanup: func() { removeDir(dir) },
  1060  	}
  1061  }
  1062  
  1063  // writeModule writes the module in the ar, a txtar, to dir.
  1064  func writeModule(dir, ar string) error {
  1065  	a := txtar.Parse([]byte(ar))
  1066  
  1067  	for _, f := range a.Files {
  1068  		fpath := filepath.Join(dir, f.Name)
  1069  		if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
  1070  			return err
  1071  		}
  1072  
  1073  		if err := os.WriteFile(fpath, f.Data, 0644); err != nil {
  1074  			return err
  1075  		}
  1076  	}
  1077  	return nil
  1078  }
  1079  
  1080  // writeProxy writes all the txtar-formatted modules in arDir to a proxy
  1081  // directory in dir.
  1082  func writeProxy(dir, arDir string) error {
  1083  	files, err := os.ReadDir(arDir)
  1084  	if err != nil {
  1085  		return err
  1086  	}
  1087  
  1088  	for _, fi := range files {
  1089  		if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil {
  1090  			return err
  1091  		}
  1092  	}
  1093  	return nil
  1094  }
  1095  
  1096  // writeProxyModule writes a txtar-formatted module at arPath to the module
  1097  // proxy in base.
  1098  func writeProxyModule(base, arPath string) error {
  1099  	arName := filepath.Base(arPath)
  1100  	i := strings.LastIndex(arName, "_v")
  1101  	ver := strings.TrimSuffix(arName[i+1:], ".txt")
  1102  	modDir := strings.ReplaceAll(arName[:i], "_", "/")
  1103  	modPath, err := module.UnescapePath(modDir)
  1104  	if err != nil {
  1105  		return err
  1106  	}
  1107  
  1108  	dir := filepath.Join(base, modDir, "@v")
  1109  	a, err := txtar.ParseFile(arPath)
  1110  
  1111  	if err != nil {
  1112  		return err
  1113  	}
  1114  
  1115  	if err := os.MkdirAll(dir, 0755); err != nil {
  1116  		return err
  1117  	}
  1118  
  1119  	f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
  1120  	if err != nil {
  1121  		return err
  1122  	}
  1123  	z := zip.NewWriter(f)
  1124  	for _, f := range a.Files {
  1125  		if f.Name[0] == '.' {
  1126  			if err := os.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil {
  1127  				return err
  1128  			}
  1129  		} else {
  1130  			zf, err := z.Create(modPath + "@" + ver + "/" + f.Name)
  1131  			if err != nil {
  1132  				return err
  1133  			}
  1134  			if _, err := zf.Write(f.Data); err != nil {
  1135  				return err
  1136  			}
  1137  		}
  1138  	}
  1139  	if err := z.Close(); err != nil {
  1140  		return err
  1141  	}
  1142  	if err := f.Close(); err != nil {
  1143  		return err
  1144  	}
  1145  
  1146  	list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
  1147  	if err != nil {
  1148  		return err
  1149  	}
  1150  	if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil {
  1151  		return err
  1152  	}
  1153  	if err := list.Close(); err != nil {
  1154  		return err
  1155  	}
  1156  	return nil
  1157  }
  1158  
  1159  func removeDir(dir string) {
  1160  	_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
  1161  		if err != nil {
  1162  			return nil
  1163  		}
  1164  		if info.IsDir() {
  1165  			_ = os.Chmod(path, 0777)
  1166  		}
  1167  		return nil
  1168  	})
  1169  	_ = os.RemoveAll(dir) // ignore errors
  1170  }
  1171  
  1172  // Tests that findModFile can find the mod files from a path in the module cache.
  1173  func TestFindModFileModCache(t *testing.T) {
  1174  	mt := setup(t, nil, `
  1175  -- go.mod --
  1176  module x
  1177  
  1178  require rsc.io/quote v1.5.2
  1179  -- x.go --
  1180  package x
  1181  import _ "rsc.io/quote"
  1182  `, "")
  1183  	defer mt.cleanup()
  1184  	want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2")
  1185  
  1186  	found := mt.assertScanFinds("rsc.io/quote", "quote")
  1187  	modDir, _ := mt.env.resolver.(*ModuleResolver).modInfo(found.dir)
  1188  	if modDir != want {
  1189  		t.Errorf("expected: %s, got: %s", want, modDir)
  1190  	}
  1191  }
  1192  
  1193  // Tests that crud in the module cache is ignored.
  1194  func TestInvalidModCache(t *testing.T) {
  1195  	testenv.NeedsTool(t, "go")
  1196  
  1197  	dir, err := os.MkdirTemp("", t.Name())
  1198  	if err != nil {
  1199  		t.Fatal(err)
  1200  	}
  1201  	defer removeDir(dir)
  1202  
  1203  	// This doesn't have module@version like it should.
  1204  	if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil {
  1205  		t.Fatal(err)
  1206  	}
  1207  	if err := os.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil {
  1208  		t.Fatal(err)
  1209  	}
  1210  	env := &ProcessEnv{
  1211  		Env: map[string]string{
  1212  			"GOPATH":      filepath.Join(dir, "gopath"),
  1213  			"GO111MODULE": "on",
  1214  			"GOSUMDB":     "off",
  1215  		},
  1216  		GocmdRunner: &gocommand.Runner{},
  1217  		WorkingDir:  dir,
  1218  	}
  1219  	resolver, err := env.GetResolver()
  1220  	if err != nil {
  1221  		t.Fatal(err)
  1222  	}
  1223  	scanToSlice(resolver, nil)
  1224  }
  1225  
  1226  func TestGetCandidatesRanking(t *testing.T) {
  1227  	mt := setup(t, nil, `
  1228  -- go.mod --
  1229  module example.com
  1230  
  1231  require rsc.io/quote v1.5.1
  1232  require rsc.io/quote/v3 v3.0.0
  1233  
  1234  -- rpackage/x.go --
  1235  package rpackage
  1236  import (
  1237  	_ "rsc.io/quote"
  1238  	_ "rsc.io/quote/v3"
  1239  )
  1240  `, "")
  1241  	defer mt.cleanup()
  1242  
  1243  	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
  1244  		t.Fatal(err)
  1245  	}
  1246  
  1247  	type res struct {
  1248  		relevance  float64
  1249  		name, path string
  1250  	}
  1251  	want := []res{
  1252  		// Stdlib
  1253  		{7, "bytes", "bytes"},
  1254  		{7, "http", "net/http"},
  1255  		// Main module
  1256  		{6, "rpackage", "example.com/rpackage"},
  1257  		// Direct module deps with v2+ major version
  1258  		{5.003, "quote", "rsc.io/quote/v3"},
  1259  		// Direct module deps
  1260  		{5, "quote", "rsc.io/quote"},
  1261  		// Indirect deps
  1262  		{4, "language", "golang.org/x/text/language"},
  1263  		// Out of scope modules
  1264  		{3, "quote", "rsc.io/quote/v2"},
  1265  	}
  1266  	var mu sync.Mutex
  1267  	var got []res
  1268  	add := func(c ImportFix) {
  1269  		mu.Lock()
  1270  		defer mu.Unlock()
  1271  		for _, w := range want {
  1272  			if c.StmtInfo.ImportPath == w.path {
  1273  				got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
  1274  			}
  1275  		}
  1276  	}
  1277  	if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil {
  1278  		t.Fatalf("getAllCandidates() = %v", err)
  1279  	}
  1280  	sort.Slice(got, func(i, j int) bool {
  1281  		ri, rj := got[i], got[j]
  1282  		if ri.relevance != rj.relevance {
  1283  			return ri.relevance > rj.relevance // Highest first.
  1284  		}
  1285  		return ri.name < rj.name
  1286  	})
  1287  	if !reflect.DeepEqual(want, got) {
  1288  		t.Errorf("wanted candidates in order %v, got %v", want, got)
  1289  	}
  1290  }
  1291  
  1292  func BenchmarkModuleResolver_RescanModCache(b *testing.B) {
  1293  	env := &ProcessEnv{
  1294  		GocmdRunner: &gocommand.Runner{},
  1295  		// Uncomment for verbose logging (too verbose to enable by default).
  1296  		// Logf:        b.Logf,
  1297  	}
  1298  	exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
  1299  	resolver, err := env.GetResolver()
  1300  	if err != nil {
  1301  		b.Fatal(err)
  1302  	}
  1303  	start := time.Now()
  1304  	scanToSlice(resolver, exclude)
  1305  	b.Logf("warming the mod cache took %v", time.Since(start))
  1306  	b.ResetTimer()
  1307  	for i := 0; i < b.N; i++ {
  1308  		scanToSlice(resolver, exclude)
  1309  		resolver = resolver.ClearForNewScan()
  1310  	}
  1311  }
  1312  
  1313  func BenchmarkModuleResolver_InitialScan(b *testing.B) {
  1314  	for i := 0; i < b.N; i++ {
  1315  		env := &ProcessEnv{
  1316  			GocmdRunner: &gocommand.Runner{},
  1317  		}
  1318  		exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
  1319  		resolver, err := env.GetResolver()
  1320  		if err != nil {
  1321  			b.Fatal(err)
  1322  		}
  1323  		scanToSlice(resolver, exclude)
  1324  	}
  1325  }