1
2
3
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
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
42
43
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
64
65
66 }
67
68
69
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
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
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
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
160
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
179
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
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
205 mt.assertScanFinds("rsc.io/quote", "quote")
206
207
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
216 mt.env.ClearModuleInfo()
217 mt.assertScanFinds("rsc.io/quote", "quote")
218 }
219
220
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
234 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`)
235
236
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
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
252
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
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
276
277 mt.env.ClearModuleInfo()
278 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir)
279 }
280
281
282
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
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
323
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
338
339
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
358
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
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
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
424
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
455
456
457
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
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
592
593
594
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
651
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
713
714
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
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
897
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
956
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
990
991
992
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
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
1047
1048
1049
1050
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
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
1081
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
1097
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)
1170 }
1171
1172
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
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
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
1253 {7, "bytes", "bytes"},
1254 {7, "http", "net/http"},
1255
1256 {6, "rpackage", "example.com/rpackage"},
1257
1258 {5.003, "quote", "rsc.io/quote/v3"},
1259
1260 {5, "quote", "rsc.io/quote"},
1261
1262 {4, "language", "golang.org/x/text/language"},
1263
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
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
1296
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 }
1326
View as plain text