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.
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"
30 // Tests that we can find packages in the stdlib.
31 func TestScanStdlib(t *testing.T) {
38 mt.assertScanFinds("fmt", "fmt")
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) {
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)
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
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) {
95 mt.assertFound("y", "y")
97 scan, err := scanToSlice(mt.resolver, nil)
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)
108 // Tests that path encoding is handled correctly. Adapted from mod_case.txt.
109 func TestModCase(t *testing.T) {
114 require rsc.io/QUOTE v1.5.2
119 import _ "rsc.io/QUOTE/QUOTE"
122 mt.assertFound("rsc.io/QUOTE/QUOTE", "QUOTE")
125 // Not obviously relevant to goimports. Adapted from mod_domain_root.txt anyway.
126 func TestModDomainRoot(t *testing.T) {
131 require example.com v1.0.0
135 import _ "example.com"
138 mt.assertFound("example.com", "x")
141 // Tests that scanning the module cache > 1 time is able to find the same module.
142 func TestModMultipleScans(t *testing.T) {
147 require example.com v1.0.0
151 import _ "example.com"
155 mt.assertScanFinds("example.com", "x")
156 mt.assertScanFinds("example.com", "x")
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) {
166 require rsc.io/quote v1.5.2
170 import _ "rsc.io/quote"
174 mt.assertScanFinds("rsc.io/quote", "quote")
175 mt.assertScanFinds("rsc.io/quote", "quote")
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) {
185 require rsc.io/quote v1.5.2
188 import _ "rsc.io/quote"
191 found := mt.assertScanFinds("rsc.io/quote", "quote")
193 t.Fatal("rsc.io/quote not found in initial scan.")
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 {
200 if err := ioutil.WriteFile(filepath.Join(found.dir, "go.mod"), []byte("module bad.com\n"), 0644); err != nil {
204 // Test that with its cache of module packages it still finds the package.
205 mt.assertScanFinds("rsc.io/quote", "quote")
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 {
211 if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "x.go"), []byte("package x\n"), 0644); err != nil {
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")
220 // Tests that -mod=vendor works. Adapted from mod_vendor_build.txt.
221 func TestModVendorBuild(t *testing.T) {
226 require rsc.io/sampler v1.3.1
229 import _ "rsc.io/sampler"
233 // Sanity-check the setup.
234 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`)
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 {
240 if _, err := mt.env.invokeGo(context.Background(), "clean", "-modcache"); err != nil {
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/`)
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) {
257 require rsc.io/sampler v1.3.1
260 import _ "rsc.io/sampler"
265 if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
269 wantDir := `pkg.*mod.*/sampler@.*$`
270 if testenv.Go1Point() >= 14 {
273 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir)
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) {
282 require rsc.io/quote v1.5.1
283 replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1
287 import _ "rsc.io/quote"
291 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.mod.*/sampler@v1.3.1$`)
294 // Tests that a local replace works. Adapted from mod_local_replace.txt.
295 func TestModLocalReplace(t *testing.T) {
300 replace zz v1.0.0 => ../z
314 mt.assertFound("zz", "z")
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) {
329 mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
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) {
340 require rsc.io/quote/v2 v2.0.1
344 import _ "rsc.io/quote/v2"
348 mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
349 mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
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) {
358 require rsc.io/quote/v2 v2.0.1
362 import _ "rsc.io/quote/v2"
365 package rsc.io/quote/v2
369 import _ "rsc.io/quote/v2"
373 mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
374 mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
377 // Tests a simple module dependency. Adapted from the first part of mod_replace.txt.
378 func TestModReplace1(t *testing.T) {
383 require rsc.io/quote/v3 v3.0.0
390 mt.assertFound("rsc.io/quote/v3", "quote")
393 // Tests a local replace. Adapted from the second part of mod_replace.txt.
394 func TestModReplace2(t *testing.T) {
399 require rsc.io/quote/v3 v3.0.0
400 replace rsc.io/quote/v3 => ./local/rsc.io/quote/v3
404 -- local/rsc.io/quote/v3/go.mod --
405 module rsc.io/quote/v3
407 require rsc.io/sampler v1.3.0
409 -- local/rsc.io/quote/v3/quote.go --
412 import "rsc.io/sampler"
415 mt.assertModuleFoundInDir("rsc.io/quote/v3", "quote", `/local/rsc.io/quote/v3`)
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) {
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
428 -- usenewmodule/main.go --
431 -- local/rsc.io/quote/v3/go.mod --
432 module rsc.io/quote/v3
434 require rsc.io/sampler v1.3.0
436 -- local/rsc.io/quote/v3/quote.go --
439 -- local/not-rsc.io/quote/v3/go.mod --
440 module not-rsc.io/quote/v3
442 -- local/not-rsc.io/quote/v3/quote.go --
446 mt.assertModuleFoundInDir("not-rsc.io/quote/v3", "quote", "local/rsc.io/quote/v3")
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) {
460 example.com/a/b => ./b
465 example.com/x/v3 => ./v3
469 example.com/y/z/w => ./w
474 example.com/vv v1.11.0 => ./v11
475 example.com/vv v1.12.0 => ./v12
476 example.com/vv => ./vv
480 example.com/a/b v0.0.0
481 example.com/x/v3 v3.0.0
483 example.com/y/z/w v0.0.0
484 example.com/vv v1.12.0
491 _ "example.com/y/z/w"
514 import _ "x.localhost/v3"
517 module x.localhost/v3
524 // Package skip is nested below nonexistent package w.
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$`)
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)
563 if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
567 mt.assertScanFinds("rsc.io/quote", "quote")
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) {
575 names, err := t.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir)
577 t.Errorf("loading package name for %v: %v", importPath, err)
579 if names[importPath] != pkgName {
580 t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName)
582 pkg := t.assertScanFinds(importPath, pkgName)
584 _, foundDir := t.resolver.findPackage(importPath)
588 func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg {
590 scan, err := scanToSlice(t.resolver, nil)
592 t.Errorf("scan failed: %v", err)
594 for _, pkg := range scan {
595 if pkg.importPathShort == importPath {
599 t.Errorf("scanning for %v did not find %v", pkgName, importPath)
603 func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) {
606 filter := &scanCallback{
607 rootFound: func(root gopathwalk.Root) bool {
608 for _, rt := range exclude {
615 dirFound: func(pkg *pkg) bool {
618 packageNameLoaded: func(pkg *pkg) bool {
621 result = append(result, pkg)
625 err := resolver.scan(context.Background(), filter)
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) {
633 dir, pkg := t.assertFound(importPath, pkgName)
634 re, err := regexp.Compile(dirRE)
640 t.Errorf("import path %v not found in active modules", importPath)
642 if !re.MatchString(filepath.ToSlash(dir)) {
643 t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE)
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)
653 var proxyOnce sync.Once
656 type modTest struct {
660 resolver *ModuleResolver
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 {
668 testenv.NeedsGo1Point(t, 11)
669 testenv.NeedsTool(t, "go")
671 proxyOnce.Do(func() {
673 proxyDir, err = ioutil.TempDir("", "proxy-")
677 if err := writeProxy(proxyDir, "testdata/mod"); err != nil {
682 dir, err := ioutil.TempDir("", t.Name())
687 mainDir := filepath.Join(dir, "main")
688 if err := writeModule(mainDir, main); err != nil {
693 Env: map[string]string{
694 "GOPATH": filepath.Join(dir, "gopath"),
698 "GOPROXY": proxydir.ToURL(proxyDir),
700 WorkingDir: filepath.Join(mainDir, wd),
701 GocmdRunner: &gocommand.Runner{},
704 env.Logf = log.Printf
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)
712 if _, err := env.invokeGo(context.Background(), "mod", "download"); err != nil {
717 resolver, err := env.GetResolver()
723 gopath: env.Env["GOPATH"],
725 resolver: resolver.(*ModuleResolver),
726 cleanup: func() { removeDir(dir) },
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))
734 for _, f := range a.Files {
735 fpath := filepath.Join(dir, f.Name)
736 if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
740 if err := ioutil.WriteFile(fpath, f.Data, 0644); err != nil {
747 // writeProxy writes all the txtar-formatted modules in arDir to a proxy
749 func writeProxy(dir, arDir string) error {
750 files, err := ioutil.ReadDir(arDir)
755 for _, fi := range files {
756 if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil {
763 // writeProxyModule writes a txtar-formatted module at arPath to the module
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)
775 dir := filepath.Join(base, modDir, "@v")
776 a, err := txtar.ParseFile(arPath)
782 if err := os.MkdirAll(dir, 0755); err != nil {
786 f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
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 {
797 zf, err := z.Create(modPath + "@" + ver + "/" + f.Name)
801 if _, err := zf.Write(f.Data); err != nil {
806 if err := z.Close(); err != nil {
809 if err := f.Close(); err != nil {
813 list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
817 if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil {
820 if err := list.Close(); err != nil {
826 func removeDir(dir string) {
827 _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
832 _ = os.Chmod(path, 0777)
836 _ = os.RemoveAll(dir) // ignore errors
839 // Tests that findModFile can find the mod files from a path in the module cache.
840 func TestFindModFileModCache(t *testing.T) {
845 require rsc.io/quote v1.5.2
848 import _ "rsc.io/quote"
851 want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2")
853 found := mt.assertScanFinds("rsc.io/quote", "quote")
854 modDir, _ := mt.resolver.modInfo(found.dir)
856 t.Errorf("expected: %s, got: %s", want, modDir)
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())
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 {
873 if err := ioutil.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil {
877 Env: map[string]string{
878 "GOPATH": filepath.Join(dir, "gopath"),
882 GocmdRunner: &gocommand.Runner{},
885 resolver, err := env.GetResolver()
889 scanToSlice(resolver, nil)
892 func TestGetCandidatesRanking(t *testing.T) {
897 require rsc.io/quote v1.5.1
898 require rsc.io/quote/v3 v3.0.0
909 if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
919 {7, "bytes", "bytes"},
920 {7, "http", "net/http"},
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"},
928 {4, "language", "golang.org/x/text/language"},
929 // Out of scope modules
930 {3, "quote", "rsc.io/quote/v2"},
934 add := func(c ImportFix) {
937 for _, w := range want {
938 if c.StmtInfo.ImportPath == w.path {
939 got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
943 if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil {
944 t.Fatalf("getAllCandidates() = %v", err)
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.
951 return ri.name < rj.name
953 if !reflect.DeepEqual(want, got) {
954 t.Errorf("wanted candidates in order %v, got %v", want, got)
958 func BenchmarkScanModCache(b *testing.B) {
959 testenv.NeedsGo1Point(b, 11)
961 GocmdRunner: &gocommand.Runner{},
964 exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
965 resolver, err := env.GetResolver()
969 scanToSlice(resolver, exclude)
971 for i := 0; i < b.N; i++ {
972 scanToSlice(resolver, exclude)
973 resolver.(*ModuleResolver).ClearForNewScan()