github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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 "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 "github.com/powerman/golang-tools/internal/gocommand" 24 "github.com/powerman/golang-tools/internal/gopathwalk" 25 "github.com/powerman/golang-tools/internal/proxydir" 26 "github.com/powerman/golang-tools/internal/testenv" 27 "github.com/powerman/golang-tools/txtar" 28 ) 29 30 // Tests that we can find packages in the stdlib. 31 func TestScanStdlib(t *testing.T) { 32 mt := setup(t, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, ` 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, `-- 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, ` 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 testenv.NeedsGo1Point(t, 12) 888 mt := setup(t, ` 889 -- x.go -- 890 package x 891 `, "") 892 defer mt.cleanup() 893 if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil { 894 t.Fatal(err) 895 } 896 897 mt.assertScanFinds("rsc.io/quote", "quote") 898 } 899 900 // assertFound asserts that the package at importPath is found to have pkgName, 901 // and that scanning for pkgName finds it at importPath. 902 func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) { 903 t.Helper() 904 905 names, err := t.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir) 906 if err != nil { 907 t.Errorf("loading package name for %v: %v", importPath, err) 908 } 909 if names[importPath] != pkgName { 910 t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName) 911 } 912 pkg := t.assertScanFinds(importPath, pkgName) 913 914 _, foundDir := t.resolver.findPackage(importPath) 915 return foundDir, pkg 916 } 917 918 func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg { 919 t.Helper() 920 scan, err := scanToSlice(t.resolver, nil) 921 if err != nil { 922 t.Errorf("scan failed: %v", err) 923 } 924 for _, pkg := range scan { 925 if pkg.importPathShort == importPath { 926 return pkg 927 } 928 } 929 t.Errorf("scanning for %v did not find %v", pkgName, importPath) 930 return nil 931 } 932 933 func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) { 934 var mu sync.Mutex 935 var result []*pkg 936 filter := &scanCallback{ 937 rootFound: func(root gopathwalk.Root) bool { 938 for _, rt := range exclude { 939 if root.Type == rt { 940 return false 941 } 942 } 943 return true 944 }, 945 dirFound: func(pkg *pkg) bool { 946 return true 947 }, 948 packageNameLoaded: func(pkg *pkg) bool { 949 mu.Lock() 950 defer mu.Unlock() 951 result = append(result, pkg) 952 return false 953 }, 954 } 955 err := resolver.scan(context.Background(), filter) 956 return result, err 957 } 958 959 // assertModuleFoundInDir is the same as assertFound, but also checks that the 960 // package was found in an active module whose Dir matches dirRE. 961 func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) { 962 t.Helper() 963 dir, pkg := t.assertFound(importPath, pkgName) 964 re, err := regexp.Compile(dirRE) 965 if err != nil { 966 t.Fatal(err) 967 } 968 969 if dir == "" { 970 t.Errorf("import path %v not found in active modules", importPath) 971 } else { 972 if !re.MatchString(filepath.ToSlash(dir)) { 973 t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE) 974 } 975 } 976 if pkg != nil { 977 if !re.MatchString(filepath.ToSlash(pkg.dir)) { 978 t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE) 979 } 980 } 981 } 982 983 var proxyOnce sync.Once 984 var proxyDir string 985 986 type modTest struct { 987 *testing.T 988 env *ProcessEnv 989 gopath string 990 resolver *ModuleResolver 991 cleanup func() 992 } 993 994 // setup builds a test environment from a txtar and supporting modules 995 // in testdata/mod, along the lines of TestScript in cmd/go. 996 func setup(t *testing.T, main, wd string) *modTest { 997 t.Helper() 998 testenv.NeedsGo1Point(t, 11) 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": "on", 1027 "GOSUMDB": "off", 1028 "GOPROXY": proxydir.ToURL(proxyDir), 1029 }, 1030 WorkingDir: filepath.Join(mainDir, wd), 1031 GocmdRunner: &gocommand.Runner{}, 1032 } 1033 if *testDebug { 1034 env.Logf = log.Printf 1035 } 1036 // go mod download gets mad if we don't have a go.mod, so make sure we do. 1037 _, err = os.Stat(filepath.Join(mainDir, "go.mod")) 1038 if err != nil && !os.IsNotExist(err) { 1039 t.Fatalf("checking if go.mod exists: %v", err) 1040 } 1041 if err == nil { 1042 if _, err := env.invokeGo(context.Background(), "mod", "download", "all"); err != nil { 1043 t.Fatal(err) 1044 } 1045 } 1046 1047 resolver, err := env.GetResolver() 1048 if err != nil { 1049 t.Fatal(err) 1050 } 1051 return &modTest{ 1052 T: t, 1053 gopath: env.Env["GOPATH"], 1054 env: env, 1055 resolver: resolver.(*ModuleResolver), 1056 cleanup: func() { removeDir(dir) }, 1057 } 1058 } 1059 1060 // writeModule writes the module in the ar, a txtar, to dir. 1061 func writeModule(dir, ar string) error { 1062 a := txtar.Parse([]byte(ar)) 1063 1064 for _, f := range a.Files { 1065 fpath := filepath.Join(dir, f.Name) 1066 if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil { 1067 return err 1068 } 1069 1070 if err := ioutil.WriteFile(fpath, f.Data, 0644); err != nil { 1071 return err 1072 } 1073 } 1074 return nil 1075 } 1076 1077 // writeProxy writes all the txtar-formatted modules in arDir to a proxy 1078 // directory in dir. 1079 func writeProxy(dir, arDir string) error { 1080 files, err := ioutil.ReadDir(arDir) 1081 if err != nil { 1082 return err 1083 } 1084 1085 for _, fi := range files { 1086 if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil { 1087 return err 1088 } 1089 } 1090 return nil 1091 } 1092 1093 // writeProxyModule writes a txtar-formatted module at arPath to the module 1094 // proxy in base. 1095 func writeProxyModule(base, arPath string) error { 1096 arName := filepath.Base(arPath) 1097 i := strings.LastIndex(arName, "_v") 1098 ver := strings.TrimSuffix(arName[i+1:], ".txt") 1099 modDir := strings.Replace(arName[:i], "_", "/", -1) 1100 modPath, err := module.UnescapePath(modDir) 1101 if err != nil { 1102 return err 1103 } 1104 1105 dir := filepath.Join(base, modDir, "@v") 1106 a, err := txtar.ParseFile(arPath) 1107 1108 if err != nil { 1109 return err 1110 } 1111 1112 if err := os.MkdirAll(dir, 0755); err != nil { 1113 return err 1114 } 1115 1116 f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644) 1117 if err != nil { 1118 return err 1119 } 1120 z := zip.NewWriter(f) 1121 for _, f := range a.Files { 1122 if f.Name[0] == '.' { 1123 if err := ioutil.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil { 1124 return err 1125 } 1126 } else { 1127 zf, err := z.Create(modPath + "@" + ver + "/" + f.Name) 1128 if err != nil { 1129 return err 1130 } 1131 if _, err := zf.Write(f.Data); err != nil { 1132 return err 1133 } 1134 } 1135 } 1136 if err := z.Close(); err != nil { 1137 return err 1138 } 1139 if err := f.Close(); err != nil { 1140 return err 1141 } 1142 1143 list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 1144 if err != nil { 1145 return err 1146 } 1147 if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil { 1148 return err 1149 } 1150 if err := list.Close(); err != nil { 1151 return err 1152 } 1153 return nil 1154 } 1155 1156 func removeDir(dir string) { 1157 _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 1158 if err != nil { 1159 return nil 1160 } 1161 if info.IsDir() { 1162 _ = os.Chmod(path, 0777) 1163 } 1164 return nil 1165 }) 1166 _ = os.RemoveAll(dir) // ignore errors 1167 } 1168 1169 // Tests that findModFile can find the mod files from a path in the module cache. 1170 func TestFindModFileModCache(t *testing.T) { 1171 mt := setup(t, ` 1172 -- go.mod -- 1173 module x 1174 1175 require rsc.io/quote v1.5.2 1176 -- x.go -- 1177 package x 1178 import _ "rsc.io/quote" 1179 `, "") 1180 defer mt.cleanup() 1181 want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2") 1182 1183 found := mt.assertScanFinds("rsc.io/quote", "quote") 1184 modDir, _ := mt.resolver.modInfo(found.dir) 1185 if modDir != want { 1186 t.Errorf("expected: %s, got: %s", want, modDir) 1187 } 1188 } 1189 1190 // Tests that crud in the module cache is ignored. 1191 func TestInvalidModCache(t *testing.T) { 1192 testenv.NeedsGo1Point(t, 11) 1193 dir, err := ioutil.TempDir("", t.Name()) 1194 if err != nil { 1195 t.Fatal(err) 1196 } 1197 defer removeDir(dir) 1198 1199 // This doesn't have module@version like it should. 1200 if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil { 1201 t.Fatal(err) 1202 } 1203 if err := ioutil.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil { 1204 t.Fatal(err) 1205 } 1206 env := &ProcessEnv{ 1207 Env: map[string]string{ 1208 "GOPATH": filepath.Join(dir, "gopath"), 1209 "GO111MODULE": "on", 1210 "GOSUMDB": "off", 1211 }, 1212 GocmdRunner: &gocommand.Runner{}, 1213 WorkingDir: dir, 1214 } 1215 resolver, err := env.GetResolver() 1216 if err != nil { 1217 t.Fatal(err) 1218 } 1219 scanToSlice(resolver, nil) 1220 } 1221 1222 func TestGetCandidatesRanking(t *testing.T) { 1223 mt := setup(t, ` 1224 -- go.mod -- 1225 module example.com 1226 1227 require rsc.io/quote v1.5.1 1228 require rsc.io/quote/v3 v3.0.0 1229 1230 -- rpackage/x.go -- 1231 package rpackage 1232 import ( 1233 _ "rsc.io/quote" 1234 _ "rsc.io/quote/v3" 1235 ) 1236 `, "") 1237 defer mt.cleanup() 1238 1239 if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil { 1240 t.Fatal(err) 1241 } 1242 1243 type res struct { 1244 relevance float64 1245 name, path string 1246 } 1247 want := []res{ 1248 // Stdlib 1249 {7, "bytes", "bytes"}, 1250 {7, "http", "net/http"}, 1251 // Main module 1252 {6, "rpackage", "example.com/rpackage"}, 1253 // Direct module deps with v2+ major version 1254 {5.003, "quote", "rsc.io/quote/v3"}, 1255 // Direct module deps 1256 {5, "quote", "rsc.io/quote"}, 1257 // Indirect deps 1258 {4, "language", "golang.org/x/text/language"}, 1259 // Out of scope modules 1260 {3, "quote", "rsc.io/quote/v2"}, 1261 } 1262 var mu sync.Mutex 1263 var got []res 1264 add := func(c ImportFix) { 1265 mu.Lock() 1266 defer mu.Unlock() 1267 for _, w := range want { 1268 if c.StmtInfo.ImportPath == w.path { 1269 got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath}) 1270 } 1271 } 1272 } 1273 if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil { 1274 t.Fatalf("getAllCandidates() = %v", err) 1275 } 1276 sort.Slice(got, func(i, j int) bool { 1277 ri, rj := got[i], got[j] 1278 if ri.relevance != rj.relevance { 1279 return ri.relevance > rj.relevance // Highest first. 1280 } 1281 return ri.name < rj.name 1282 }) 1283 if !reflect.DeepEqual(want, got) { 1284 t.Errorf("wanted candidates in order %v, got %v", want, got) 1285 } 1286 } 1287 1288 func BenchmarkScanModCache(b *testing.B) { 1289 testenv.NeedsGo1Point(b, 11) 1290 env := &ProcessEnv{ 1291 GocmdRunner: &gocommand.Runner{}, 1292 Logf: log.Printf, 1293 } 1294 exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT} 1295 resolver, err := env.GetResolver() 1296 if err != nil { 1297 b.Fatal(err) 1298 } 1299 scanToSlice(resolver, exclude) 1300 b.ResetTimer() 1301 for i := 0; i < b.N; i++ { 1302 scanToSlice(resolver, exclude) 1303 resolver.(*ModuleResolver).ClearForNewScan() 1304 } 1305 }