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