1 | // Copyright 2019 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package imports |
6 | |
7 | import ( |
8 | "archive/zip" |
9 | "context" |
10 | "fmt" |
11 | "io/ioutil" |
12 | "log" |
13 | "os" |
14 | "path/filepath" |
15 | "reflect" |
16 | "regexp" |
17 | "sort" |
18 | "strings" |
19 | "sync" |
20 | "testing" |
21 | |
22 | "golang.org/x/mod/module" |
23 | "golang.org/x/tools/internal/gocommand" |
24 | "golang.org/x/tools/internal/gopathwalk" |
25 | "golang.org/x/tools/internal/proxydir" |
26 | "golang.org/x/tools/internal/testenv" |
27 | "golang.org/x/tools/txtar" |
28 | ) |
29 | |
30 | // Tests that we can find packages in the stdlib. |
31 | func TestScanStdlib(t *testing.T) { |
32 | mt := setup(t, nil, ` |
33 | -- go.mod -- |
34 | module x |
35 | `, "") |
36 | defer mt.cleanup() |
37 | |
38 | mt.assertScanFinds("fmt", "fmt") |
39 | } |
40 | |
41 | // Tests that we handle a nested module. This is different from other tests |
42 | // where the module is in scope -- here we have to figure out the import path |
43 | // without any help from go list. |
44 | func TestScanOutOfScopeNestedModule(t *testing.T) { |
45 | mt := setup(t, nil, ` |
46 | -- go.mod -- |
47 | module x |
48 | |
49 | -- x.go -- |
50 | package x |
51 | |
52 | -- v2/go.mod -- |
53 | module x |
54 | |
55 | -- v2/x.go -- |
56 | package x`, "") |
57 | defer mt.cleanup() |
58 | |
59 | pkg := mt.assertScanFinds("x/v2", "x") |
60 | if pkg != nil && !strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/v2") { |
61 | t.Errorf("x/v2 was found in %v, wanted .../main/v2", pkg.dir) |
62 | } |
63 | // We can't load the package name from the import path, but that should |
64 | // be okay -- if we end up adding this result, we'll add it with a name |
65 | // if necessary. |
66 | } |
67 | |
68 | // Tests that we don't find a nested module contained in a local replace target. |
69 | // The code for this case is too annoying to write, so it's just ignored. |
70 | func TestScanNestedModuleInLocalReplace(t *testing.T) { |
71 | mt := setup(t, nil, ` |
72 | -- go.mod -- |
73 | module x |
74 | |
75 | require y v0.0.0 |
76 | replace y => ./y |
77 | |
78 | -- x.go -- |
79 | package x |
80 | |
81 | -- y/go.mod -- |
82 | module y |
83 | |
84 | -- y/y.go -- |
85 | package y |
86 | |
87 | -- y/z/go.mod -- |
88 | module y/z |
89 | |
90 | -- y/z/z.go -- |
91 | package z |
92 | `, "") |
93 | defer mt.cleanup() |
94 | |
95 | mt.assertFound("y", "y") |
96 | |
97 | scan, err := scanToSlice(mt.resolver, nil) |
98 | if err != nil { |
99 | t.Fatal(err) |
100 | } |
101 | for _, pkg := range scan { |
102 | if strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/y/z") { |
103 | t.Errorf("scan found a package %v in dir main/y/z, wanted none", pkg.importPathShort) |
104 | } |
105 | } |
106 | } |
107 | |
108 | // Tests that path encoding is handled correctly. Adapted from mod_case.txt. |
109 | func TestModCase(t *testing.T) { |
110 | mt := setup(t, nil, ` |
111 | -- go.mod -- |
112 | module x |
113 | |
114 | require rsc.io/QUOTE v1.5.2 |
115 | |
116 | -- x.go -- |
117 | package x |
118 | |
119 | import _ "rsc.io/QUOTE/QUOTE" |
120 | `, "") |
121 | defer mt.cleanup() |
122 | mt.assertFound("rsc.io/QUOTE/QUOTE", "QUOTE") |
123 | } |
124 | |
125 | // Not obviously relevant to goimports. Adapted from mod_domain_root.txt anyway. |
126 | func TestModDomainRoot(t *testing.T) { |
127 | mt := setup(t, nil, ` |
128 | -- go.mod -- |
129 | module x |
130 | |
131 | require example.com v1.0.0 |
132 | |
133 | -- x.go -- |
134 | package x |
135 | import _ "example.com" |
136 | `, "") |
137 | defer mt.cleanup() |
138 | mt.assertFound("example.com", "x") |
139 | } |
140 | |
141 | // Tests that scanning the module cache > 1 time is able to find the same module. |
142 | func TestModMultipleScans(t *testing.T) { |
143 | mt := setup(t, nil, ` |
144 | -- go.mod -- |
145 | module x |
146 | |
147 | require example.com v1.0.0 |
148 | |
149 | -- x.go -- |
150 | package x |
151 | import _ "example.com" |
152 | `, "") |
153 | defer mt.cleanup() |
154 | |
155 | mt.assertScanFinds("example.com", "x") |
156 | mt.assertScanFinds("example.com", "x") |
157 | } |
158 | |
159 | // Tests that scanning the module cache > 1 time is able to find the same module |
160 | // in the module cache. |
161 | func TestModMultipleScansWithSubdirs(t *testing.T) { |
162 | mt := setup(t, nil, ` |
163 | -- go.mod -- |
164 | module x |
165 | |
166 | require rsc.io/quote v1.5.2 |
167 | |
168 | -- x.go -- |
169 | package x |
170 | import _ "rsc.io/quote" |
171 | `, "") |
172 | defer mt.cleanup() |
173 | |
174 | mt.assertScanFinds("rsc.io/quote", "quote") |
175 | mt.assertScanFinds("rsc.io/quote", "quote") |
176 | } |
177 | |
178 | // Tests that scanning the module cache > 1 after changing a package in module cache to make it unimportable |
179 | // is able to find the same module. |
180 | func TestModCacheEditModFile(t *testing.T) { |
181 | mt := setup(t, nil, ` |
182 | -- go.mod -- |
183 | module x |
184 | |
185 | require rsc.io/quote v1.5.2 |
186 | -- x.go -- |
187 | package x |
188 | import _ "rsc.io/quote" |
189 | `, "") |
190 | defer mt.cleanup() |
191 | found := mt.assertScanFinds("rsc.io/quote", "quote") |
192 | if found == nil { |
193 | t.Fatal("rsc.io/quote not found in initial scan.") |
194 | } |
195 | |
196 | // Update the go.mod file of example.com so that it changes its module path (not allowed). |
197 | if err := os.Chmod(filepath.Join(found.dir, "go.mod"), 0644); err != nil { |
198 | t.Fatal(err) |
199 | } |
200 | if err := ioutil.WriteFile(filepath.Join(found.dir, "go.mod"), []byte("module bad.com\n"), 0644); err != nil { |
201 | t.Fatal(err) |
202 | } |
203 | |
204 | // Test that with its cache of module packages it still finds the package. |
205 | mt.assertScanFinds("rsc.io/quote", "quote") |
206 | |
207 | // Rewrite the main package so that rsc.io/quote is not in scope. |
208 | if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "go.mod"), []byte("module x\n"), 0644); err != nil { |
209 | t.Fatal(err) |
210 | } |
211 | if err := ioutil.WriteFile(filepath.Join(mt.env.WorkingDir, "x.go"), []byte("package x\n"), 0644); err != nil { |
212 | t.Fatal(err) |
213 | } |
214 | |
215 | // Uninitialize the go.mod dependent cached information and make sure it still finds the package. |
216 | mt.resolver.ClearForNewMod() |
217 | mt.assertScanFinds("rsc.io/quote", "quote") |
218 | } |
219 | |
220 | // Tests that -mod=vendor works. Adapted from mod_vendor_build.txt. |
221 | func TestModVendorBuild(t *testing.T) { |
222 | mt := setup(t, nil, ` |
223 | -- go.mod -- |
224 | module m |
225 | go 1.12 |
226 | require rsc.io/sampler v1.3.1 |
227 | -- x.go -- |
228 | package x |
229 | import _ "rsc.io/sampler" |
230 | `, "") |
231 | defer mt.cleanup() |
232 | |
233 | // Sanity-check the setup. |
234 | mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`) |
235 | |
236 | // Populate vendor/ and clear out the mod cache so we can't cheat. |
237 | if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil { |
238 | t.Fatal(err) |
239 | } |
240 | if _, err := mt.env.invokeGo(context.Background(), "clean", "-modcache"); err != nil { |
241 | t.Fatal(err) |
242 | } |
243 | |
244 | // Clear out the resolver's cache, since we've changed the environment. |
245 | mt.resolver = newModuleResolver(mt.env) |
246 | mt.env.Env["GOFLAGS"] = "-mod=vendor" |
247 | mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`) |
248 | } |
249 | |
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) { |
253 | mt := setup(t, nil, ` |
254 | -- go.mod -- |
255 | module m |
256 | go 1.14 |
257 | require rsc.io/sampler v1.3.1 |
258 | -- x.go -- |
259 | package x |
260 | import _ "rsc.io/sampler" |
261 | `, "") |
262 | defer mt.cleanup() |
263 | |
264 | // Populate vendor/. |
265 | if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil { |
266 | t.Fatal(err) |
267 | } |
268 | |
269 | wantDir := `pkg.*mod.*/sampler@.*$` |
270 | if testenv.Go1Point() >= 14 { |
271 | wantDir = `/vendor/` |
272 | } |
273 | mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir) |
274 | } |
275 | |
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) { |
279 | mt := setup(t, nil, ` |
280 | -- go.mod -- |
281 | module x |
282 | require rsc.io/quote v1.5.1 |
283 | replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1 |
284 | |
285 | -- x.go -- |
286 | package x |
287 | import _ "rsc.io/quote" |
288 | `, "") |
289 | defer mt.cleanup() |
290 | |
291 | mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.mod.*/sampler@v1.3.1$`) |
292 | } |
293 | |
294 | // Tests that a local replace works. Adapted from mod_local_replace.txt. |
295 | func TestModLocalReplace(t *testing.T) { |
296 | mt := setup(t, nil, ` |
297 | -- x/y/go.mod -- |
298 | module x/y |
299 | require zz v1.0.0 |
300 | replace zz v1.0.0 => ../z |
301 | |
302 | -- x/y/y.go -- |
303 | package y |
304 | import _ "zz" |
305 | |
306 | -- x/z/go.mod -- |
307 | module x/z |
308 | |
309 | -- x/z/z.go -- |
310 | package z |
311 | `, "x/y") |
312 | defer mt.cleanup() |
313 | |
314 | mt.assertFound("zz", "z") |
315 | } |
316 | |
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) { |
320 | mt := setup(t, nil, ` |
321 | -- go.mod -- |
322 | module rsc.io/quote |
323 | |
324 | -- x.go -- |
325 | package quote |
326 | `, "") |
327 | defer mt.cleanup() |
328 | |
329 | mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`) |
330 | } |
331 | |
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) { |
336 | mt := setup(t, nil, ` |
337 | -- go.mod -- |
338 | module rsc.io/quote |
339 | |
340 | require rsc.io/quote/v2 v2.0.1 |
341 | -- x.go -- |
342 | package quote |
343 | |
344 | import _ "rsc.io/quote/v2" |
345 | `, "") |
346 | defer mt.cleanup() |
347 | |
348 | mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`) |
349 | mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`) |
350 | } |
351 | |
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) { |
355 | mt := setup(t, nil, ` |
356 | -- go.mod -- |
357 | module rsc.io/quote |
358 | require rsc.io/quote/v2 v2.0.1 |
359 | |
360 | -- x.go -- |
361 | package quote |
362 | import _ "rsc.io/quote/v2" |
363 | |
364 | -- v2/go.mod -- |
365 | package rsc.io/quote/v2 |
366 | |
367 | -- v2/x.go -- |
368 | package quote |
369 | import _ "rsc.io/quote/v2" |
370 | `, "") |
371 | defer mt.cleanup() |
372 | |
373 | mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`) |
374 | mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`) |
375 | } |
376 | |
377 | // Tests a simple module dependency. Adapted from the first part of mod_replace.txt. |
378 | func TestModReplace1(t *testing.T) { |
379 | mt := setup(t, nil, ` |
380 | -- go.mod -- |
381 | module quoter |
382 | |
383 | require rsc.io/quote/v3 v3.0.0 |
384 | |
385 | -- main.go -- |
386 | |
387 | package main |
388 | `, "") |
389 | defer mt.cleanup() |
390 | mt.assertFound("rsc.io/quote/v3", "quote") |
391 | } |
392 | |
393 | // Tests a local replace. Adapted from the second part of mod_replace.txt. |
394 | func TestModReplace2(t *testing.T) { |
395 | mt := setup(t, nil, ` |
396 | -- go.mod -- |
397 | module quoter |
398 | |
399 | require rsc.io/quote/v3 v3.0.0 |
400 | replace rsc.io/quote/v3 => ./local/rsc.io/quote/v3 |
401 | -- main.go -- |
402 | package main |
403 | |
404 | -- local/rsc.io/quote/v3/go.mod -- |
405 | module rsc.io/quote/v3 |
406 | |
407 | require rsc.io/sampler v1.3.0 |
408 | |
409 | -- local/rsc.io/quote/v3/quote.go -- |
410 | package quote |
411 | |
412 | import "rsc.io/sampler" |
413 | `, "") |
414 | defer mt.cleanup() |
415 | mt.assertModuleFoundInDir("rsc.io/quote/v3", "quote", `/local/rsc.io/quote/v3`) |
416 | } |
417 | |
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) { |
421 | mt := setup(t, nil, ` |
422 | -- go.mod -- |
423 | module quoter |
424 | |
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 |
427 | |
428 | -- usenewmodule/main.go -- |
429 | package main |
430 | |
431 | -- local/rsc.io/quote/v3/go.mod -- |
432 | module rsc.io/quote/v3 |
433 | |
434 | require rsc.io/sampler v1.3.0 |
435 | |
436 | -- local/rsc.io/quote/v3/quote.go -- |
437 | package quote |
438 | |
439 | -- local/not-rsc.io/quote/v3/go.mod -- |
440 | module not-rsc.io/quote/v3 |
441 | |
442 | -- local/not-rsc.io/quote/v3/quote.go -- |
443 | package quote |
444 | `, "") |
445 | defer mt.cleanup() |
446 | mt.assertModuleFoundInDir("not-rsc.io/quote/v3", "quote", "local/rsc.io/quote/v3") |
447 | } |
448 | |
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) { |
454 | mt := setup(t, nil, ` |
455 | -- go.mod -- |
456 | module example.com/m |
457 | |
458 | replace ( |
459 | example.com/a => ./a |
460 | example.com/a/b => ./b |
461 | ) |
462 | |
463 | replace ( |
464 | example.com/x => ./x |
465 | example.com/x/v3 => ./v3 |
466 | ) |
467 | |
468 | replace ( |
469 | example.com/y/z/w => ./w |
470 | example.com/y => ./y |
471 | ) |
472 | |
473 | replace ( |
474 | example.com/vv v1.11.0 => ./v11 |
475 | example.com/vv v1.12.0 => ./v12 |
476 | example.com/vv => ./vv |
477 | ) |
478 | |
479 | require ( |
480 | example.com/a/b v0.0.0 |
481 | example.com/x/v3 v3.0.0 |
482 | example.com/y v0.0.0 |
483 | example.com/y/z/w v0.0.0 |
484 | example.com/vv v1.12.0 |
485 | ) |
486 | -- m.go -- |
487 | package main |
488 | import ( |
489 | _ "example.com/a/b" |
490 | _ "example.com/x/v3" |
491 | _ "example.com/y/z/w" |
492 | _ "example.com/vv" |
493 | ) |
494 | func main() {} |
495 | |
496 | -- a/go.mod -- |
497 | module a.localhost |
498 | -- a/a.go -- |
499 | package a |
500 | -- a/b/b.go-- |
501 | package b |
502 | |
503 | -- b/go.mod -- |
504 | module a.localhost/b |
505 | -- b/b.go -- |
506 | package b |
507 | |
508 | -- x/go.mod -- |
509 | module x.localhost |
510 | -- x/x.go -- |
511 | package x |
512 | -- x/v3.go -- |
513 | package v3 |
514 | import _ "x.localhost/v3" |
515 | |
516 | -- v3/go.mod -- |
517 | module x.localhost/v3 |
518 | -- v3/x.go -- |
519 | package x |
520 | |
521 | -- w/go.mod -- |
522 | module w.localhost |
523 | -- w/skip/skip.go -- |
524 | // Package skip is nested below nonexistent package w. |
525 | package skip |
526 | |
527 | -- y/go.mod -- |
528 | module y.localhost |
529 | -- y/z/w/w.go -- |
530 | package w |
531 | |
532 | -- v12/go.mod -- |
533 | module v.localhost |
534 | -- v12/v.go -- |
535 | package v |
536 | |
537 | -- v11/go.mod -- |
538 | module v.localhost |
539 | -- v11/v.go -- |
540 | package v |
541 | |
542 | -- vv/go.mod -- |
543 | module v.localhost |
544 | -- vv/v.go -- |
545 | package v |
546 | `, "") |
547 | defer mt.cleanup() |
548 | |
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$`) |
553 | } |
554 | |
555 | // Tests that go.work files are respected. |
556 | func TestModWorkspace(t *testing.T) { |
557 | testenv.NeedsGo1Point(t, 18) |
558 | |
559 | mt := setup(t, nil, ` |
560 | -- go.work -- |
561 | go 1.18 |
562 | |
563 | use ( |
564 | ./a |
565 | ./b |
566 | ) |
567 | -- a/go.mod -- |
568 | module example.com/a |
569 | |
570 | go 1.18 |
571 | -- a/a.go -- |
572 | package a |
573 | -- b/go.mod -- |
574 | module example.com/b |
575 | |
576 | go 1.18 |
577 | -- b/b.go -- |
578 | package b |
579 | `, "") |
580 | defer mt.cleanup() |
581 | |
582 | mt.assertModuleFoundInDir("example.com/a", "a", `main/a$`) |
583 | mt.assertModuleFoundInDir("example.com/b", "b", `main/b$`) |
584 | mt.assertScanFinds("example.com/a", "a") |
585 | mt.assertScanFinds("example.com/b", "b") |
586 | } |
587 | |
588 | // Tests replaces in workspaces. Uses the directory layout in the cmd/go |
589 | // work_replace test. It tests both that replaces in go.work files are |
590 | // respected and that a wildcard replace in go.work overrides a versioned replace |
591 | // in go.mod. |
592 | func TestModWorkspaceReplace(t *testing.T) { |
593 | testenv.NeedsGo1Point(t, 18) |
594 | |
595 | mt := setup(t, nil, ` |
596 | -- go.work -- |
597 | use m |
598 | |
599 | replace example.com/dep => ./dep |
600 | replace example.com/other => ./other2 |
601 | |
602 | -- m/go.mod -- |
603 | module example.com/m |
604 | |
605 | require example.com/dep v1.0.0 |
606 | require example.com/other v1.0.0 |
607 | |
608 | replace example.com/other v1.0.0 => ./other |
609 | -- m/m.go -- |
610 | package m |
611 | |
612 | import "example.com/dep" |
613 | import "example.com/other" |
614 | |
615 | func F() { |
616 | dep.G() |
617 | other.H() |
618 | } |
619 | -- dep/go.mod -- |
620 | module example.com/dep |
621 | -- dep/dep.go -- |
622 | package dep |
623 | |
624 | func G() { |
625 | } |
626 | -- other/go.mod -- |
627 | module example.com/other |
628 | -- other/dep.go -- |
629 | package other |
630 | |
631 | func G() { |
632 | } |
633 | -- other2/go.mod -- |
634 | module example.com/other |
635 | -- other2/dep.go -- |
636 | package other2 |
637 | |
638 | func G() { |
639 | } |
640 | `, "") |
641 | defer mt.cleanup() |
642 | |
643 | mt.assertScanFinds("example.com/m", "m") |
644 | mt.assertScanFinds("example.com/dep", "dep") |
645 | mt.assertModuleFoundInDir("example.com/other", "other2", "main/other2$") |
646 | mt.assertScanFinds("example.com/other", "other2") |
647 | } |
648 | |
649 | // Tests a case where conflicting replaces are overridden by a replace |
650 | // in the go.work file. |
651 | func TestModWorkspaceReplaceOverride(t *testing.T) { |
652 | testenv.NeedsGo1Point(t, 18) |
653 | |
654 | mt := setup(t, nil, `-- go.work -- |
655 | use m |
656 | use n |
657 | replace example.com/dep => ./dep3 |
658 | -- m/go.mod -- |
659 | module example.com/m |
660 | |
661 | require example.com/dep v1.0.0 |
662 | replace example.com/dep => ./dep1 |
663 | -- m/m.go -- |
664 | package m |
665 | |
666 | import "example.com/dep" |
667 | |
668 | func F() { |
669 | dep.G() |
670 | } |
671 | -- n/go.mod -- |
672 | module example.com/n |
673 | |
674 | require example.com/dep v1.0.0 |
675 | replace example.com/dep => ./dep2 |
676 | -- n/n.go -- |
677 | package n |
678 | |
679 | import "example.com/dep" |
680 | |
681 | func F() { |
682 | dep.G() |
683 | } |
684 | -- dep1/go.mod -- |
685 | module example.com/dep |
686 | -- dep1/dep.go -- |
687 | package dep |
688 | |
689 | func G() { |
690 | } |
691 | -- dep2/go.mod -- |
692 | module example.com/dep |
693 | -- dep2/dep.go -- |
694 | package dep |
695 | |
696 | func G() { |
697 | } |
698 | -- dep3/go.mod -- |
699 | module example.com/dep |
700 | -- dep3/dep.go -- |
701 | package dep |
702 | |
703 | func G() { |
704 | } |
705 | `, "") |
706 | |
707 | mt.assertScanFinds("example.com/m", "m") |
708 | mt.assertScanFinds("example.com/n", "n") |
709 | mt.assertScanFinds("example.com/dep", "dep") |
710 | mt.assertModuleFoundInDir("example.com/dep", "dep", "main/dep3$") |
711 | } |
712 | |
713 | // Tests that the correct versions of modules are found in |
714 | // workspaces with module pruning. This is based on the |
715 | // cmd/go mod_prune_all script test. |
716 | func TestModWorkspacePrune(t *testing.T) { |
717 | testenv.NeedsGo1Point(t, 18) |
718 | |
719 | mt := setup(t, nil, ` |
720 | -- go.work -- |
721 | go 1.18 |
722 | |
723 | use ( |
724 | ./a |
725 | ./p |
726 | ) |
727 | |
728 | replace example.com/b v1.0.0 => ./b |
729 | replace example.com/q v1.0.0 => ./q1_0_0 |
730 | replace example.com/q v1.0.5 => ./q1_0_5 |
731 | replace example.com/q v1.1.0 => ./q1_1_0 |
732 | replace example.com/r v1.0.0 => ./r |
733 | replace example.com/w v1.0.0 => ./w |
734 | replace example.com/x v1.0.0 => ./x |
735 | replace example.com/y v1.0.0 => ./y |
736 | replace example.com/z v1.0.0 => ./z1_0_0 |
737 | replace example.com/z v1.1.0 => ./z1_1_0 |
738 | |
739 | -- a/go.mod -- |
740 | module example.com/a |
741 | |
742 | go 1.18 |
743 | |
744 | require example.com/b v1.0.0 |
745 | require example.com/z v1.0.0 |
746 | -- a/foo.go -- |
747 | package main |
748 | |
749 | import "example.com/b" |
750 | |
751 | func main() { |
752 | b.B() |
753 | } |
754 | -- b/go.mod -- |
755 | module example.com/b |
756 | |
757 | go 1.18 |
758 | |
759 | require example.com/q v1.1.0 |
760 | -- b/b.go -- |
761 | package b |
762 | |
763 | func B() { |
764 | } |
765 | -- p/go.mod -- |
766 | module example.com/p |
767 | |
768 | go 1.18 |
769 | |
770 | require example.com/q v1.0.0 |
771 | |
772 | replace example.com/q v1.0.0 => ../q1_0_0 |
773 | replace example.com/q v1.1.0 => ../q1_1_0 |
774 | -- p/main.go -- |
775 | package main |
776 | |
777 | import "example.com/q" |
778 | |
779 | func main() { |
780 | q.PrintVersion() |
781 | } |
782 | -- q1_0_0/go.mod -- |
783 | module example.com/q |
784 | |
785 | go 1.18 |
786 | -- q1_0_0/q.go -- |
787 | package q |
788 | |
789 | import "fmt" |
790 | |
791 | func PrintVersion() { |
792 | fmt.Println("version 1.0.0") |
793 | } |
794 | -- q1_0_5/go.mod -- |
795 | module example.com/q |
796 | |
797 | go 1.18 |
798 | |
799 | require example.com/r v1.0.0 |
800 | -- q1_0_5/q.go -- |
801 | package q |
802 | |
803 | import _ "example.com/r" |
804 | -- q1_1_0/go.mod -- |
805 | module example.com/q |
806 | |
807 | require example.com/w v1.0.0 |
808 | require example.com/z v1.1.0 |
809 | |
810 | go 1.18 |
811 | -- q1_1_0/q.go -- |
812 | package q |
813 | |
814 | import _ "example.com/w" |
815 | import _ "example.com/z" |
816 | |
817 | import "fmt" |
818 | |
819 | func PrintVersion() { |
820 | fmt.Println("version 1.1.0") |
821 | } |
822 | -- r/go.mod -- |
823 | module example.com/r |
824 | |
825 | go 1.18 |
826 | |
827 | require example.com/r v1.0.0 |
828 | -- r/r.go -- |
829 | package r |
830 | -- w/go.mod -- |
831 | module example.com/w |
832 | |
833 | go 1.18 |
834 | |
835 | require example.com/x v1.0.0 |
836 | -- w/w.go -- |
837 | package w |
838 | -- w/w_test.go -- |
839 | package w |
840 | |
841 | import _ "example.com/x" |
842 | -- x/go.mod -- |
843 | module example.com/x |
844 | |
845 | go 1.18 |
846 | -- x/x.go -- |
847 | package x |
848 | -- x/x_test.go -- |
849 | package x |
850 | import _ "example.com/y" |
851 | -- y/go.mod -- |
852 | module example.com/y |
853 | |
854 | go 1.18 |
855 | -- y/y.go -- |
856 | package y |
857 | -- z1_0_0/go.mod -- |
858 | module example.com/z |
859 | |
860 | go 1.18 |
861 | |
862 | require example.com/q v1.0.5 |
863 | -- z1_0_0/z.go -- |
864 | package z |
865 | |
866 | import _ "example.com/q" |
867 | -- z1_1_0/go.mod -- |
868 | module example.com/z |
869 | |
870 | go 1.18 |
871 | -- z1_1_0/z.go -- |
872 | package z |
873 | `, "") |
874 | |
875 | mt.assertScanFinds("example.com/w", "w") |
876 | mt.assertScanFinds("example.com/q", "q") |
877 | mt.assertScanFinds("example.com/x", "x") |
878 | mt.assertScanFinds("example.com/z", "z") |
879 | mt.assertModuleFoundInDir("example.com/w", "w", "main/w$") |
880 | mt.assertModuleFoundInDir("example.com/q", "q", "main/q1_1_0$") |
881 | mt.assertModuleFoundInDir("example.com/x", "x", "main/x$") |
882 | mt.assertModuleFoundInDir("example.com/z", "z", "main/z1_1_0$") |
883 | } |
884 | |
885 | // Tests that we handle GO111MODULE=on with no go.mod file. See #30855. |
886 | func TestNoMainModule(t *testing.T) { |
887 | mt := setup(t, map[string]string{"GO111MODULE": "on"}, ` |
888 | -- x.go -- |
889 | package x |
890 | `, "") |
891 | defer mt.cleanup() |
892 | if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil { |
893 | t.Fatal(err) |
894 | } |
895 | |
896 | mt.assertScanFinds("rsc.io/quote", "quote") |
897 | } |
898 | |
899 | // assertFound asserts that the package at importPath is found to have pkgName, |
900 | // and that scanning for pkgName finds it at importPath. |
901 | func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) { |
902 | t.Helper() |
903 | |
904 | names, err := t.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir) |
905 | if err != nil { |
906 | t.Errorf("loading package name for %v: %v", importPath, err) |
907 | } |
908 | if names[importPath] != pkgName { |
909 | t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName) |
910 | } |
911 | pkg := t.assertScanFinds(importPath, pkgName) |
912 | |
913 | _, foundDir := t.resolver.findPackage(importPath) |
914 | return foundDir, pkg |
915 | } |
916 | |
917 | func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg { |
918 | t.Helper() |
919 | scan, err := scanToSlice(t.resolver, nil) |
920 | if err != nil { |
921 | t.Errorf("scan failed: %v", err) |
922 | } |
923 | for _, pkg := range scan { |
924 | if pkg.importPathShort == importPath { |
925 | return pkg |
926 | } |
927 | } |
928 | t.Errorf("scanning for %v did not find %v", pkgName, importPath) |
929 | return nil |
930 | } |
931 | |
932 | func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) { |
933 | var mu sync.Mutex |
934 | var result []*pkg |
935 | filter := &scanCallback{ |
936 | rootFound: func(root gopathwalk.Root) bool { |
937 | for _, rt := range exclude { |
938 | if root.Type == rt { |
939 | return false |
940 | } |
941 | } |
942 | return true |
943 | }, |
944 | dirFound: func(pkg *pkg) bool { |
945 | return true |
946 | }, |
947 | packageNameLoaded: func(pkg *pkg) bool { |
948 | mu.Lock() |
949 | defer mu.Unlock() |
950 | result = append(result, pkg) |
951 | return false |
952 | }, |
953 | } |
954 | err := resolver.scan(context.Background(), filter) |
955 | return result, err |
956 | } |
957 | |
958 | // assertModuleFoundInDir is the same as assertFound, but also checks that the |
959 | // package was found in an active module whose Dir matches dirRE. |
960 | func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) { |
961 | t.Helper() |
962 | dir, pkg := t.assertFound(importPath, pkgName) |
963 | re, err := regexp.Compile(dirRE) |
964 | if err != nil { |
965 | t.Fatal(err) |
966 | } |
967 | |
968 | if dir == "" { |
969 | t.Errorf("import path %v not found in active modules", importPath) |
970 | } else { |
971 | if !re.MatchString(filepath.ToSlash(dir)) { |
972 | t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE) |
973 | } |
974 | } |
975 | if pkg != nil { |
976 | if !re.MatchString(filepath.ToSlash(pkg.dir)) { |
977 | t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE) |
978 | } |
979 | } |
980 | } |
981 | |
982 | var proxyOnce sync.Once |
983 | var proxyDir string |
984 | |
985 | type modTest struct { |
986 | *testing.T |
987 | env *ProcessEnv |
988 | gopath string |
989 | resolver *ModuleResolver |
990 | cleanup func() |
991 | } |
992 | |
993 | // setup builds a test environment from a txtar and supporting modules |
994 | // in testdata/mod, along the lines of TestScript in cmd/go. |
995 | // |
996 | // extraEnv is applied on top of the default test env. |
997 | func setup(t *testing.T, extraEnv map[string]string, main, wd string) *modTest { |
998 | t.Helper() |
999 | testenv.NeedsTool(t, "go") |
1000 | |
1001 | proxyOnce.Do(func() { |
1002 | var err error |
1003 | proxyDir, err = ioutil.TempDir("", "proxy-") |
1004 | if err != nil { |
1005 | t.Fatal(err) |
1006 | } |
1007 | if err := writeProxy(proxyDir, "testdata/mod"); err != nil { |
1008 | t.Fatal(err) |
1009 | } |
1010 | }) |
1011 | |
1012 | dir, err := ioutil.TempDir("", t.Name()) |
1013 | if err != nil { |
1014 | t.Fatal(err) |
1015 | } |
1016 | |
1017 | mainDir := filepath.Join(dir, "main") |
1018 | if err := writeModule(mainDir, main); err != nil { |
1019 | t.Fatal(err) |
1020 | } |
1021 | |
1022 | env := &ProcessEnv{ |
1023 | Env: map[string]string{ |
1024 | "GOPATH": filepath.Join(dir, "gopath"), |
1025 | "GOMODCACHE": "", |
1026 | "GO111MODULE": "auto", |
1027 | "GOSUMDB": "off", |
1028 | "GOPROXY": proxydir.ToURL(proxyDir), |
1029 | }, |
1030 | WorkingDir: filepath.Join(mainDir, wd), |
1031 | GocmdRunner: &gocommand.Runner{}, |
1032 | } |
1033 | for k, v := range extraEnv { |
1034 | env.Env[k] = v |
1035 | } |
1036 | if *testDebug { |
1037 | env.Logf = log.Printf |
1038 | } |
1039 | // go mod download gets mad if we don't have a go.mod, so make sure we do. |
1040 | _, err = os.Stat(filepath.Join(mainDir, "go.mod")) |
1041 | if err != nil && !os.IsNotExist(err) { |
1042 | t.Fatalf("checking if go.mod exists: %v", err) |
1043 | } |
1044 | if err == nil { |
1045 | if _, err := env.invokeGo(context.Background(), "mod", "download", "all"); err != nil { |
1046 | t.Fatal(err) |
1047 | } |
1048 | } |
1049 | |
1050 | resolver, err := env.GetResolver() |
1051 | if err != nil { |
1052 | t.Fatal(err) |
1053 | } |
1054 | return &modTest{ |
1055 | T: t, |
1056 | gopath: env.Env["GOPATH"], |
1057 | env: env, |
1058 | resolver: resolver.(*ModuleResolver), |
1059 | cleanup: func() { removeDir(dir) }, |
1060 | } |
1061 | } |
1062 | |
1063 | // writeModule writes the module in the ar, a txtar, to dir. |
1064 | func writeModule(dir, ar string) error { |
1065 | a := txtar.Parse([]byte(ar)) |
1066 | |
1067 | for _, f := range a.Files { |
1068 | fpath := filepath.Join(dir, f.Name) |
1069 | if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil { |
1070 | return err |
1071 | } |
1072 | |
1073 | if err := ioutil.WriteFile(fpath, f.Data, 0644); err != nil { |
1074 | return err |
1075 | } |
1076 | } |
1077 | return nil |
1078 | } |
1079 | |
1080 | // writeProxy writes all the txtar-formatted modules in arDir to a proxy |
1081 | // directory in dir. |
1082 | func writeProxy(dir, arDir string) error { |
1083 | files, err := ioutil.ReadDir(arDir) |
1084 | if err != nil { |
1085 | return err |
1086 | } |
1087 | |
1088 | for _, fi := range files { |
1089 | if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil { |
1090 | return err |
1091 | } |
1092 | } |
1093 | return nil |
1094 | } |
1095 | |
1096 | // writeProxyModule writes a txtar-formatted module at arPath to the module |
1097 | // proxy in base. |
1098 | func writeProxyModule(base, arPath string) error { |
1099 | arName := filepath.Base(arPath) |
1100 | i := strings.LastIndex(arName, "_v") |
1101 | ver := strings.TrimSuffix(arName[i+1:], ".txt") |
1102 | modDir := strings.Replace(arName[:i], "_", "/", -1) |
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 := ioutil.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil { |
1127 | return err |
1128 | } |
1129 | } else { |
1130 | zf, err := z.Create(modPath + "@" + ver + "/" + f.Name) |
1131 | if err != nil { |
1132 | return err |
1133 | } |
1134 | if _, err := zf.Write(f.Data); err != nil { |
1135 | return err |
1136 | } |
1137 | } |
1138 | } |
1139 | if err := z.Close(); err != nil { |
1140 | return err |
1141 | } |
1142 | if err := f.Close(); err != nil { |
1143 | return err |
1144 | } |
1145 | |
1146 | list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) |
1147 | if err != nil { |
1148 | return err |
1149 | } |
1150 | if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil { |
1151 | return err |
1152 | } |
1153 | if err := list.Close(); err != nil { |
1154 | return err |
1155 | } |
1156 | return nil |
1157 | } |
1158 | |
1159 | func removeDir(dir string) { |
1160 | _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { |
1161 | if err != nil { |
1162 | return nil |
1163 | } |
1164 | if info.IsDir() { |
1165 | _ = os.Chmod(path, 0777) |
1166 | } |
1167 | return nil |
1168 | }) |
1169 | _ = os.RemoveAll(dir) // ignore errors |
1170 | } |
1171 | |
1172 | // Tests that findModFile can find the mod files from a path in the module cache. |
1173 | func TestFindModFileModCache(t *testing.T) { |
1174 | mt := setup(t, nil, ` |
1175 | -- go.mod -- |
1176 | module x |
1177 | |
1178 | require rsc.io/quote v1.5.2 |
1179 | -- x.go -- |
1180 | package x |
1181 | import _ "rsc.io/quote" |
1182 | `, "") |
1183 | defer mt.cleanup() |
1184 | want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2") |
1185 | |
1186 | found := mt.assertScanFinds("rsc.io/quote", "quote") |
1187 | modDir, _ := mt.resolver.modInfo(found.dir) |
1188 | if modDir != want { |
1189 | t.Errorf("expected: %s, got: %s", want, modDir) |
1190 | } |
1191 | } |
1192 | |
1193 | // Tests that crud in the module cache is ignored. |
1194 | func TestInvalidModCache(t *testing.T) { |
1195 | dir, err := ioutil.TempDir("", t.Name()) |
1196 | if err != nil { |
1197 | t.Fatal(err) |
1198 | } |
1199 | defer removeDir(dir) |
1200 | |
1201 | // This doesn't have module@version like it should. |
1202 | if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil { |
1203 | t.Fatal(err) |
1204 | } |
1205 | if err := ioutil.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil { |
1206 | t.Fatal(err) |
1207 | } |
1208 | env := &ProcessEnv{ |
1209 | Env: map[string]string{ |
1210 | "GOPATH": filepath.Join(dir, "gopath"), |
1211 | "GO111MODULE": "on", |
1212 | "GOSUMDB": "off", |
1213 | }, |
1214 | GocmdRunner: &gocommand.Runner{}, |
1215 | WorkingDir: dir, |
1216 | } |
1217 | resolver, err := env.GetResolver() |
1218 | if err != nil { |
1219 | t.Fatal(err) |
1220 | } |
1221 | scanToSlice(resolver, nil) |
1222 | } |
1223 | |
1224 | func TestGetCandidatesRanking(t *testing.T) { |
1225 | mt := setup(t, nil, ` |
1226 | -- go.mod -- |
1227 | module example.com |
1228 | |
1229 | require rsc.io/quote v1.5.1 |
1230 | require rsc.io/quote/v3 v3.0.0 |
1231 | |
1232 | -- rpackage/x.go -- |
1233 | package rpackage |
1234 | import ( |
1235 | _ "rsc.io/quote" |
1236 | _ "rsc.io/quote/v3" |
1237 | ) |
1238 | `, "") |
1239 | defer mt.cleanup() |
1240 | |
1241 | if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil { |
1242 | t.Fatal(err) |
1243 | } |
1244 | |
1245 | type res struct { |
1246 | relevance float64 |
1247 | name, path string |
1248 | } |
1249 | want := []res{ |
1250 | // Stdlib |
1251 | {7, "bytes", "bytes"}, |
1252 | {7, "http", "net/http"}, |
1253 | // Main module |
1254 | {6, "rpackage", "example.com/rpackage"}, |
1255 | // Direct module deps with v2+ major version |
1256 | {5.003, "quote", "rsc.io/quote/v3"}, |
1257 | // Direct module deps |
1258 | {5, "quote", "rsc.io/quote"}, |
1259 | // Indirect deps |
1260 | {4, "language", "golang.org/x/text/language"}, |
1261 | // Out of scope modules |
1262 | {3, "quote", "rsc.io/quote/v2"}, |
1263 | } |
1264 | var mu sync.Mutex |
1265 | var got []res |
1266 | add := func(c ImportFix) { |
1267 | mu.Lock() |
1268 | defer mu.Unlock() |
1269 | for _, w := range want { |
1270 | if c.StmtInfo.ImportPath == w.path { |
1271 | got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath}) |
1272 | } |
1273 | } |
1274 | } |
1275 | if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil { |
1276 | t.Fatalf("getAllCandidates() = %v", err) |
1277 | } |
1278 | sort.Slice(got, func(i, j int) bool { |
1279 | ri, rj := got[i], got[j] |
1280 | if ri.relevance != rj.relevance { |
1281 | return ri.relevance > rj.relevance // Highest first. |
1282 | } |
1283 | return ri.name < rj.name |
1284 | }) |
1285 | if !reflect.DeepEqual(want, got) { |
1286 | t.Errorf("wanted candidates in order %v, got %v", want, got) |
1287 | } |
1288 | } |
1289 | |
1290 | func BenchmarkScanModCache(b *testing.B) { |
1291 | env := &ProcessEnv{ |
1292 | GocmdRunner: &gocommand.Runner{}, |
1293 | Logf: log.Printf, |
1294 | } |
1295 | exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT} |
1296 | resolver, err := env.GetResolver() |
1297 | if err != nil { |
1298 | b.Fatal(err) |
1299 | } |
1300 | scanToSlice(resolver, exclude) |
1301 | b.ResetTimer() |
1302 | for i := 0; i < b.N; i++ { |
1303 | scanToSlice(resolver, exclude) |
1304 | resolver.(*ModuleResolver).ClearForNewScan() |
1305 | } |
1306 | } |
1307 |
Members