golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/imports/mod_test.go (about) 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 "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 // 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.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 // 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 := os.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 := 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 // Uninitialize the go.mod dependent cached information and make sure it still finds the package. 216 mt.env.ClearModuleInfo() 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.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 // Tests that -mod=vendor is auto-enabled only for go1.14 and higher. 252 // Vaguely inspired by mod_vendor_auto.txt. 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 // Populate vendor/. 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 // Clear out the resolver's module info, since we've changed the environment. 276 // (the presence of a /vendor directory affects `go list -m`). 277 mt.env.ClearModuleInfo() 278 mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir) 279 } 280 281 // Tests that a module replace works. Adapted from mod_list.txt. We start with 282 // go.mod2; the first part of the test is irrelevant. 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 // Tests that a local replace works. Adapted from mod_local_replace.txt. 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 // Tests that the package at the root of the main module can be found. 323 // Adapted from the first part of mod_multirepo.txt. 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 // Tests that a simple module dependency is found. Adapted from the third part 338 // of mod_multirepo.txt (We skip the case where it doesn't have a go.mod 339 // entry -- we just don't work in that case.) 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 // Tests that a nested module is found in the module cache, even though 358 // it's checked out. Adapted from the fourth part of mod_multirepo.txt. 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 // Tests a simple module dependency. Adapted from the first part of mod_replace.txt. 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 // Tests a local replace. Adapted from the second part of mod_replace.txt. 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 // Tests that a module can be replaced by a different module path. Adapted 424 // from the third part of mod_replace.txt. 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 // Tests more local replaces, notably the case where an outer module provides 455 // a package that could also be provided by an inner module. Adapted from 456 // mod_replace_import.txt, with example.com/v changed to /vv because Go 1.11 457 // thinks /v is an invalid major version. 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 // Tests that go.work files are respected. 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 // Tests replaces in workspaces. Uses the directory layout in the cmd/go 592 // work_replace test. It tests both that replaces in go.work files are 593 // respected and that a wildcard replace in go.work overrides a versioned replace 594 // in go.mod. 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 // Tests a case where conflicting replaces are overridden by a replace 651 // in the go.work file. 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 // Tests that the correct versions of modules are found in 713 // workspaces with module pruning. This is based on the 714 // cmd/go mod_prune_all script test. 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 // Tests that we handle GO111MODULE=on with no go.mod file. See #30855. 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 // assertFound asserts that the package at importPath is found to have pkgName, 897 // and that scanning for pkgName finds it at importPath. 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 // assertModuleFoundInDir is the same as assertFound, but also checks that the 956 // package was found in an active module whose Dir matches dirRE. 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 // setup builds a test environment from a txtar and supporting modules 990 // in testdata/mod, along the lines of TestScript in cmd/go. 991 // 992 // extraEnv is applied on top of the default test env. 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 // go mod download gets mad if we don't have a go.mod, so make sure we do. 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 // Ensure the resolver is set for tests that (unsafely) access env.resolver 1047 // directly. 1048 // 1049 // TODO(rfindley): fix this after addressing the TODO in the ProcessEnv 1050 // docstring. 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 // 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 := os.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 := 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 // 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.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) // 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.env.resolver.(*ModuleResolver).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 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 // This doesn't have module@version like it should. 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 // Stdlib 1253 {7, "bytes", "bytes"}, 1254 {7, "http", "net/http"}, 1255 // Main module 1256 {6, "rpackage", "example.com/rpackage"}, 1257 // Direct module deps with v2+ major version 1258 {5.003, "quote", "rsc.io/quote/v3"}, 1259 // Direct module deps 1260 {5, "quote", "rsc.io/quote"}, 1261 // Indirect deps 1262 {4, "language", "golang.org/x/text/language"}, 1263 // Out of scope modules 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 // Highest first. 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 // Uncomment for verbose logging (too verbose to enable by default). 1296 // Logf: b.Logf, 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 }