18 "golang.org/x/mod/module"
19 "golang.org/x/tools/internal/gocommand"
20 "golang.org/x/tools/internal/gopathwalk"
21 "golang.org/x/tools/internal/proxydir"
22 "golang.org/x/tools/internal/testenv"
23 "golang.org/x/tools/txtar"
26 // Tests that we can find packages in the stdlib.
27 func TestScanStdlib(t *testing.T) {
34 mt.assertScanFinds("fmt", "fmt")
37 // Tests that we handle a nested module. This is different from other tests
38 // where the module is in scope -- here we have to figure out the import path
39 // without any help from go list.
40 func TestScanOutOfScopeNestedModule(t *testing.T) {
55 pkg := mt.assertScanFinds("x/v2", "x")
56 if pkg != nil && !strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/v2") {
57 t.Errorf("x/v2 was found in %v, wanted .../main/v2", pkg.dir)
59 // We can't load the package name from the import path, but that should
60 // be okay -- if we end up adding this result, we'll add it with a name
64 // Tests that we don't find a nested module contained in a local replace target.
65 // The code for this case is too annoying to write, so it's just ignored.
66 func TestScanNestedModuleInLocalReplace(t *testing.T) {
91 mt.assertFound("y", "y")
93 scan, err := scanToSlice(mt.resolver, nil)
97 for _, pkg := range scan {
98 if strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/y/z") {
99 t.Errorf("scan found a package %v in dir main/y/z, wanted none", pkg.importPathShort)
104 // Tests that path encoding is handled correctly. Adapted from mod_case.txt.
105 func TestModCase(t *testing.T) {
110 require rsc.io/QUOTE v1.5.2
115 import _ "rsc.io/QUOTE/QUOTE"
118 mt.assertFound("rsc.io/QUOTE/QUOTE", "QUOTE")
121 // Not obviously relevant to goimports. Adapted from mod_domain_root.txt anyway.
122 func TestModDomainRoot(t *testing.T) {
127 require example.com v1.0.0
131 import _ "example.com"
134 mt.assertFound("example.com", "x")
137 // Tests that scanning the module cache > 1 time is able to find the same module.
138 func TestModMultipleScans(t *testing.T) {
143 require example.com v1.0.0
147 import _ "example.com"
151 mt.assertScanFinds("example.com", "x")
152 mt.assertScanFinds("example.com", "x")
155 // Tests that scanning the module cache > 1 time is able to find the same module
156 // in the module cache.
157 func TestModMultipleScansWithSubdirs(t *testing.T) {
162 require rsc.io/quote v1.5.2
166 import _ "rsc.io/quote"
170 mt.assertScanFinds("rsc.io/quote", "quote")
171 mt.assertScanFinds("rsc.io/quote", "quote")
174 // Tests that scanning the module cache > 1 after changing a package in module cache to make it unimportable
175 // is able to find the same module.
176 func TestModCacheEditModFile(t *testing.T) {
181 require rsc.io/quote v1.5.2
184 import _ "rsc.io/quote"
187 found := mt.assertScanFinds("rsc.io/quote", "quote")
189 t.Fatal("rsc.io/quote not found in initial scan.")
192 // Update the go.mod file of example.com so that it changes its module path (not allowed).
193 if err := os.Chmod(filepath.Join(found.dir, "go.mod"), 0644); err != nil {
196 if err := ioutil.WriteFile(filepath.Join(found.dir, "go.mod"), []byte("module bad.com\n"), 0644); err != nil {
200 // Test that with its cache of module packages it still finds the package.
201 mt.assertScanFinds("rsc.io/quote", "quote")
203 // Rewrite the main package so that rsc.io/quote is not in scope.
204 if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "go.mod"), []byte("module x\n"), 0644); err != nil {
207 if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "x.go"), []byte("package x\n"), 0644); err != nil {
211 // Uninitialize the go.mod dependent cached information and make sure it still finds the package.
212 mt.resolver.ClearForNewMod()
213 mt.assertScanFinds("rsc.io/quote", "quote")
216 // Tests that -mod=vendor works. Adapted from mod_vendor_build.txt.
217 func TestModVendorBuild(t *testing.T) {
222 require rsc.io/sampler v1.3.1
225 import _ "rsc.io/sampler"
229 // Sanity-check the setup.
230 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`)
232 // Populate vendor/ and clear out the mod cache so we can't cheat.
233 if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
236 if _, err := mt.env.invokeGo(context.Background(), "clean", "-modcache"); err != nil {
240 // Clear out the resolver's cache, since we've changed the environment.
241 mt.resolver = newModuleResolver(mt.env)
242 mt.env.Env["GOFLAGS"] = "-mod=vendor"
243 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`)
246 // Tests that -mod=vendor is auto-enabled only for go1.14 and higher.
247 // Vaguely inspired by mod_vendor_auto.txt.
248 func TestModVendorAuto(t *testing.T) {
253 require rsc.io/sampler v1.3.1
256 import _ "rsc.io/sampler"
261 if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
265 wantDir := `pkg.*mod.*/sampler@.*$`
266 if testenv.Go1Point() >= 14 {
269 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir)
272 // Tests that a module replace works. Adapted from mod_list.txt. We start with
273 // go.mod2; the first part of the test is irrelevant.
274 func TestModList(t *testing.T) {
278 require rsc.io/quote v1.5.1
279 replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1
283 import _ "rsc.io/quote"
287 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.mod.*/sampler@v1.3.1$`)
290 // Tests that a local replace works. Adapted from mod_local_replace.txt.
291 func TestModLocalReplace(t *testing.T) {
296 replace zz v1.0.0 => ../z
310 mt.assertFound("zz", "z")
313 // Tests that the package at the root of the main module can be found.
314 // Adapted from the first part of mod_multirepo.txt.
315 func TestModMultirepo1(t *testing.T) {
325 mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
328 // Tests that a simple module dependency is found. Adapted from the third part
329 // of mod_multirepo.txt (We skip the case where it doesn't have a go.mod
330 // entry -- we just don't work in that case.)
331 func TestModMultirepo3(t *testing.T) {
336 require rsc.io/quote/v2 v2.0.1
340 import _ "rsc.io/quote/v2"
344 mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
345 mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
348 // Tests that a nested module is found in the module cache, even though
349 // it's checked out. Adapted from the fourth part of mod_multirepo.txt.
350 func TestModMultirepo4(t *testing.T) {
354 require rsc.io/quote/v2 v2.0.1
358 import _ "rsc.io/quote/v2"
361 package rsc.io/quote/v2
365 import _ "rsc.io/quote/v2"
369 mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`)
370 mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`)
373 // Tests a simple module dependency. Adapted from the first part of mod_replace.txt.
374 func TestModReplace1(t *testing.T) {
379 require rsc.io/quote/v3 v3.0.0
386 mt.assertFound("rsc.io/quote/v3", "quote")
389 // Tests a local replace. Adapted from the second part of mod_replace.txt.
390 func TestModReplace2(t *testing.T) {
395 require rsc.io/quote/v3 v3.0.0
396 replace rsc.io/quote/v3 => ./local/rsc.io/quote/v3
400 -- local/rsc.io/quote/v3/go.mod --
401 module rsc.io/quote/v3
403 require rsc.io/sampler v1.3.0
405 -- local/rsc.io/quote/v3/quote.go --
408 import "rsc.io/sampler"
411 mt.assertModuleFoundInDir("rsc.io/quote/v3", "quote", `/local/rsc.io/quote/v3`)
414 // Tests that a module can be replaced by a different module path. Adapted
415 // from the third part of mod_replace.txt.
416 func TestModReplace3(t *testing.T) {
421 require not-rsc.io/quote/v3 v3.1.0
422 replace not-rsc.io/quote/v3 v3.1.0 => ./local/rsc.io/quote/v3
424 -- usenewmodule/main.go --
427 -- local/rsc.io/quote/v3/go.mod --
428 module rsc.io/quote/v3
430 require rsc.io/sampler v1.3.0
432 -- local/rsc.io/quote/v3/quote.go --
435 -- local/not-rsc.io/quote/v3/go.mod --
436 module not-rsc.io/quote/v3
438 -- local/not-rsc.io/quote/v3/quote.go --
442 mt.assertModuleFoundInDir("not-rsc.io/quote/v3", "quote", "local/rsc.io/quote/v3")
445 // Tests more local replaces, notably the case where an outer module provides
446 // a package that could also be provided by an inner module. Adapted from
447 // mod_replace_import.txt, with example.com/v changed to /vv because Go 1.11
448 // thinks /v is an invalid major version.
449 func TestModReplaceImport(t *testing.T) {
456 example.com/a/b => ./b
461 example.com/x/v3 => ./v3
465 example.com/y/z/w => ./w
470 example.com/vv v1.11.0 => ./v11
471 example.com/vv v1.12.0 => ./v12
472 example.com/vv => ./vv
476 example.com/a/b v0.0.0
477 example.com/x/v3 v3.0.0
479 example.com/y/z/w v0.0.0
480 example.com/vv v1.12.0
487 _ "example.com/y/z/w"
510 import _ "x.localhost/v3"
513 module x.localhost/v3
520 // Package skip is nested below nonexistent package w.
545 mt.assertModuleFoundInDir("example.com/a/b", "b", `main/b$`)
546 mt.assertModuleFoundInDir("example.com/x/v3", "x", `main/v3$`)
547 mt.assertModuleFoundInDir("example.com/y/z/w", "w", `main/y/z/w$`)
548 mt.assertModuleFoundInDir("example.com/vv", "v", `main/v12$`)
551 // Tests that we handle GO111MODULE=on with no go.mod file. See #30855.
552 func TestNoMainModule(t *testing.T) {
553 testenv.NeedsGo1Point(t, 12)
559 if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
563 mt.assertScanFinds("rsc.io/quote", "quote")
566 // assertFound asserts that the package at importPath is found to have pkgName,
567 // and that scanning for pkgName finds it at importPath.
568 func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) {
571 names, err := t.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir)
573 t.Errorf("loading package name for %v: %v", importPath, err)
575 if names[importPath] != pkgName {
576 t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName)
578 pkg := t.assertScanFinds(importPath, pkgName)
580 _, foundDir := t.resolver.findPackage(importPath)
584 func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg {
586 scan, err := scanToSlice(t.resolver, nil)
588 t.Errorf("scan failed: %v", err)
590 for _, pkg := range scan {
591 if pkg.importPathShort == importPath {
595 t.Errorf("scanning for %v did not find %v", pkgName, importPath)
599 func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) {
602 filter := &scanCallback{
603 rootFound: func(root gopathwalk.Root) bool {
604 for _, rt := range exclude {
611 dirFound: func(pkg *pkg) bool {
614 packageNameLoaded: func(pkg *pkg) bool {
617 result = append(result, pkg)
621 err := resolver.scan(context.Background(), filter)
625 // assertModuleFoundInDir is the same as assertFound, but also checks that the
626 // package was found in an active module whose Dir matches dirRE.
627 func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) {
629 dir, pkg := t.assertFound(importPath, pkgName)
630 re, err := regexp.Compile(dirRE)
636 t.Errorf("import path %v not found in active modules", importPath)
638 if !re.MatchString(filepath.ToSlash(dir)) {
639 t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE)
643 if !re.MatchString(filepath.ToSlash(pkg.dir)) {
644 t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE)
649 var proxyOnce sync.Once
652 type modTest struct {
656 resolver *ModuleResolver
660 // setup builds a test environment from a txtar and supporting modules
661 // in testdata/mod, along the lines of TestScript in cmd/go.
662 func setup(t *testing.T, main, wd string) *modTest {
664 testenv.NeedsGo1Point(t, 11)
665 testenv.NeedsTool(t, "go")
667 proxyOnce.Do(func() {
669 proxyDir, err = ioutil.TempDir("", "proxy-")
673 if err := writeProxy(proxyDir, "testdata/mod"); err != nil {
678 dir, err := ioutil.TempDir("", t.Name())
683 mainDir := filepath.Join(dir, "main")
684 if err := writeModule(mainDir, main); err != nil {
689 Env: map[string]string{
690 "GOPATH": filepath.Join(dir, "gopath"),
694 "GOPROXY": proxydir.ToURL(proxyDir),
696 WorkingDir: filepath.Join(mainDir, wd),
697 GocmdRunner: &gocommand.Runner{},
700 env.Logf = log.Printf
702 // go mod download gets mad if we don't have a go.mod, so make sure we do.
703 _, err = os.Stat(filepath.Join(mainDir, "go.mod"))
704 if err != nil && !os.IsNotExist(err) {
705 t.Fatalf("checking if go.mod exists: %v", err)
708 if _, err := env.invokeGo(context.Background(), "mod", "download"); err != nil {
713 resolver, err := env.GetResolver()
719 gopath: env.Env["GOPATH"],
721 resolver: resolver.(*ModuleResolver),
722 cleanup: func() { removeDir(dir) },
726 // writeModule writes the module in the ar, a txtar, to dir.
727 func writeModule(dir, ar string) error {
728 a := txtar.Parse([]byte(ar))
730 for _, f := range a.Files {
731 fpath := filepath.Join(dir, f.Name)
732 if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
736 if err := ioutil.WriteFile(fpath, f.Data, 0644); err != nil {
743 // writeProxy writes all the txtar-formatted modules in arDir to a proxy
745 func writeProxy(dir, arDir string) error {
746 files, err := ioutil.ReadDir(arDir)
751 for _, fi := range files {
752 if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil {
759 // writeProxyModule writes a txtar-formatted module at arPath to the module
761 func writeProxyModule(base, arPath string) error {
762 arName := filepath.Base(arPath)
763 i := strings.LastIndex(arName, "_v")
764 ver := strings.TrimSuffix(arName[i+1:], ".txt")
765 modDir := strings.Replace(arName[:i], "_", "/", -1)
766 modPath, err := module.UnescapePath(modDir)
771 dir := filepath.Join(base, modDir, "@v")
772 a, err := txtar.ParseFile(arPath)
778 if err := os.MkdirAll(dir, 0755); err != nil {
782 f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
786 z := zip.NewWriter(f)
787 for _, f := range a.Files {
788 if f.Name[0] == '.' {
789 if err := ioutil.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil {
793 zf, err := z.Create(modPath + "@" + ver + "/" + f.Name)
797 if _, err := zf.Write(f.Data); err != nil {
802 if err := z.Close(); err != nil {
805 if err := f.Close(); err != nil {
809 list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
813 if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil {
816 if err := list.Close(); err != nil {
822 func removeDir(dir string) {
823 _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
828 _ = os.Chmod(path, 0777)
832 _ = os.RemoveAll(dir) // ignore errors
835 // Tests that findModFile can find the mod files from a path in the module cache.
836 func TestFindModFileModCache(t *testing.T) {
841 require rsc.io/quote v1.5.2
844 import _ "rsc.io/quote"
847 want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2")
849 found := mt.assertScanFinds("rsc.io/quote", "quote")
850 modDir, _ := mt.resolver.modInfo(found.dir)
852 t.Errorf("expected: %s, got: %s", want, modDir)
856 // Tests that crud in the module cache is ignored.
857 func TestInvalidModCache(t *testing.T) {
858 testenv.NeedsGo1Point(t, 11)
859 dir, err := ioutil.TempDir("", t.Name())
865 // This doesn't have module@version like it should.
866 if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil {
869 if err := ioutil.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil {
873 Env: map[string]string{
874 "GOPATH": filepath.Join(dir, "gopath"),
878 GocmdRunner: &gocommand.Runner{},
881 resolver, err := env.GetResolver()
885 scanToSlice(resolver, nil)
888 func TestGetCandidatesRanking(t *testing.T) {
893 require rsc.io/quote v1.5.1
894 require rsc.io/quote/v3 v3.0.0
905 if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
915 {7, "bytes", "bytes"},
916 {7, "http", "net/http"},
918 {6, "rpackage", "example.com/rpackage"},
919 // Direct module deps with v2+ major version
920 {5.003, "quote", "rsc.io/quote/v3"},
921 // Direct module deps
922 {5, "quote", "rsc.io/quote"},
924 {4, "language", "golang.org/x/text/language"},
925 // Out of scope modules
926 {3, "quote", "rsc.io/quote/v2"},
930 add := func(c ImportFix) {
933 for _, w := range want {
934 if c.StmtInfo.ImportPath == w.path {
935 got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath})
939 if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil {
940 t.Fatalf("getAllCandidates() = %v", err)
942 sort.Slice(got, func(i, j int) bool {
943 ri, rj := got[i], got[j]
944 if ri.relevance != rj.relevance {
945 return ri.relevance > rj.relevance // Highest first.
947 return ri.name < rj.name
949 if !reflect.DeepEqual(want, got) {
950 t.Errorf("wanted candidates in order %v, got %v", want, got)
954 func BenchmarkScanModCache(b *testing.B) {
955 testenv.NeedsGo1Point(b, 11)
957 GocmdRunner: &gocommand.Runner{},
960 exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
961 resolver, err := env.GetResolver()
965 scanToSlice(resolver, exclude)
967 for i := 0; i < b.N; i++ {
968 scanToSlice(resolver, exclude)
969 resolver.(*ModuleResolver).ClearForNewScan()