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