golang.org/x/tools/gopls@v0.15.3/internal/test/integration/workspace/workspace_test.go (about) 1 // Copyright 2020 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 workspace 6 7 import ( 8 "context" 9 "fmt" 10 "sort" 11 "strings" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 "golang.org/x/tools/gopls/internal/hooks" 16 "golang.org/x/tools/gopls/internal/protocol" 17 "golang.org/x/tools/gopls/internal/test/integration/fake" 18 "golang.org/x/tools/gopls/internal/util/bug" 19 "golang.org/x/tools/gopls/internal/util/goversion" 20 "golang.org/x/tools/internal/gocommand" 21 "golang.org/x/tools/internal/testenv" 22 23 . "golang.org/x/tools/gopls/internal/test/integration" 24 ) 25 26 func TestMain(m *testing.M) { 27 bug.PanicOnBugs = true 28 Main(m, hooks.Options) 29 } 30 31 const workspaceProxy = ` 32 -- example.com@v1.2.3/go.mod -- 33 module example.com 34 35 go 1.12 36 -- example.com@v1.2.3/blah/blah.go -- 37 package blah 38 39 import "fmt" 40 41 func SaySomething() { 42 fmt.Println("something") 43 } 44 -- random.org@v1.2.3/go.mod -- 45 module random.org 46 47 go 1.12 48 -- random.org@v1.2.3/bye/bye.go -- 49 package bye 50 51 func Goodbye() { 52 println("Bye") 53 } 54 ` 55 56 // TODO: Add a replace directive. 57 const workspaceModule = ` 58 -- pkg/go.mod -- 59 module mod.com 60 61 go 1.14 62 63 require ( 64 example.com v1.2.3 65 random.org v1.2.3 66 ) 67 -- pkg/go.sum -- 68 example.com v1.2.3 h1:veRD4tUnatQRgsULqULZPjeoBGFr2qBhevSCZllD2Ds= 69 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 70 random.org v1.2.3 h1:+JE2Fkp7gS0zsHXGEQJ7hraom3pNTlkxC4b2qPfA+/Q= 71 random.org v1.2.3/go.mod h1:E9KM6+bBX2g5ykHZ9H27w16sWo3QwgonyjM44Dnej3I= 72 -- pkg/main.go -- 73 package main 74 75 import ( 76 "example.com/blah" 77 "mod.com/inner" 78 "random.org/bye" 79 ) 80 81 func main() { 82 blah.SaySomething() 83 inner.Hi() 84 bye.Goodbye() 85 } 86 -- pkg/main2.go -- 87 package main 88 89 import "fmt" 90 91 func _() { 92 fmt.Print("%s") 93 } 94 -- pkg/inner/inner.go -- 95 package inner 96 97 import "example.com/blah" 98 99 func Hi() { 100 blah.SaySomething() 101 } 102 -- goodbye/bye/bye.go -- 103 package bye 104 105 func Bye() {} 106 -- goodbye/go.mod -- 107 module random.org 108 109 go 1.12 110 ` 111 112 // Confirm that find references returns all of the references in the module, 113 // regardless of what the workspace root is. 114 func TestReferences(t *testing.T) { 115 for _, tt := range []struct { 116 name, rootPath string 117 }{ 118 { 119 name: "module root", 120 rootPath: "pkg", 121 }, 122 { 123 name: "subdirectory", 124 rootPath: "pkg/inner", 125 }, 126 } { 127 t.Run(tt.name, func(t *testing.T) { 128 opts := []RunOption{ProxyFiles(workspaceProxy)} 129 if tt.rootPath != "" { 130 opts = append(opts, WorkspaceFolders(tt.rootPath)) 131 } 132 WithOptions(opts...).Run(t, workspaceModule, func(t *testing.T, env *Env) { 133 f := "pkg/inner/inner.go" 134 env.OpenFile(f) 135 locations := env.References(env.RegexpSearch(f, `SaySomething`)) 136 want := 3 137 if got := len(locations); got != want { 138 t.Fatalf("expected %v locations, got %v", want, got) 139 } 140 }) 141 }) 142 } 143 } 144 145 // Make sure that analysis diagnostics are cleared for the whole package when 146 // the only opened file is closed. This test was inspired by the experience in 147 // VS Code, where clicking on a reference result triggers a 148 // textDocument/didOpen without a corresponding textDocument/didClose. 149 func TestClearAnalysisDiagnostics(t *testing.T) { 150 WithOptions( 151 ProxyFiles(workspaceProxy), 152 WorkspaceFolders("pkg/inner"), 153 ).Run(t, workspaceModule, func(t *testing.T, env *Env) { 154 env.OpenFile("pkg/main.go") 155 env.AfterChange( 156 Diagnostics(env.AtRegexp("pkg/main2.go", "fmt.Print")), 157 ) 158 env.CloseBuffer("pkg/main.go") 159 env.AfterChange( 160 NoDiagnostics(ForFile("pkg/main2.go")), 161 ) 162 }) 163 } 164 165 // TestReloadOnlyOnce checks that changes to the go.mod file do not result in 166 // redundant package loads (golang/go#54473). 167 // 168 // Note that this test may be fragile, as it depends on specific structure to 169 // log messages around reinitialization. Nevertheless, it is important for 170 // guarding against accidentally duplicate reloading. 171 func TestReloadOnlyOnce(t *testing.T) { 172 WithOptions( 173 ProxyFiles(workspaceProxy), 174 WorkspaceFolders("pkg"), 175 ).Run(t, workspaceModule, func(t *testing.T, env *Env) { 176 dir := env.Sandbox.Workdir.URI("goodbye").Path() 177 goModWithReplace := fmt.Sprintf(`%s 178 replace random.org => %s 179 `, env.ReadWorkspaceFile("pkg/go.mod"), dir) 180 env.WriteWorkspaceFile("pkg/go.mod", goModWithReplace) 181 env.Await( 182 LogMatching(protocol.Info, `packages\.Load #\d+\n`, 2, false), 183 ) 184 }) 185 } 186 187 const workspaceModuleProxy = ` 188 -- example.com@v1.2.3/go.mod -- 189 module example.com 190 191 go 1.12 192 -- example.com@v1.2.3/blah/blah.go -- 193 package blah 194 195 import "fmt" 196 197 func SaySomething() { 198 fmt.Println("something") 199 } 200 -- b.com@v1.2.3/go.mod -- 201 module b.com 202 203 go 1.12 204 -- b.com@v1.2.3/b/b.go -- 205 package b 206 207 func Hello() {} 208 ` 209 210 const multiModule = ` 211 -- moda/a/go.mod -- 212 module a.com 213 214 require b.com v1.2.3 215 -- moda/a/go.sum -- 216 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= 217 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= 218 -- moda/a/a.go -- 219 package a 220 221 import ( 222 "b.com/b" 223 ) 224 225 func main() { 226 var x int 227 _ = b.Hello() 228 } 229 -- modb/go.mod -- 230 module b.com 231 232 -- modb/b/b.go -- 233 package b 234 235 func Hello() int { 236 var x int 237 } 238 ` 239 240 func TestAutomaticWorkspaceModule_Interdependent(t *testing.T) { 241 WithOptions( 242 ProxyFiles(workspaceModuleProxy), 243 ).Run(t, multiModule, func(t *testing.T, env *Env) { 244 env.RunGoCommand("work", "init") 245 env.RunGoCommand("work", "use", "-r", ".") 246 env.AfterChange( 247 Diagnostics(env.AtRegexp("moda/a/a.go", "x")), 248 Diagnostics(env.AtRegexp("modb/b/b.go", "x")), 249 NoDiagnostics(env.AtRegexp("moda/a/a.go", `"b.com/b"`)), 250 ) 251 }) 252 } 253 254 func TestWorkspaceVendoring(t *testing.T) { 255 testenv.NeedsGo1Point(t, 22) 256 WithOptions( 257 ProxyFiles(workspaceModuleProxy), 258 ).Run(t, multiModule, func(t *testing.T, env *Env) { 259 env.RunGoCommand("work", "init") 260 env.RunGoCommand("work", "use", "moda/a") 261 env.AfterChange() 262 env.OpenFile("moda/a/a.go") 263 env.RunGoCommand("work", "vendor") 264 env.AfterChange() 265 loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "b.(Hello)")) 266 const want = "vendor/b.com/b/b.go" 267 if got := env.Sandbox.Workdir.URIToPath(loc.URI); got != want { 268 t.Errorf("Definition: got location %q, want %q", got, want) 269 } 270 }) 271 } 272 273 func TestModuleWithExclude(t *testing.T) { 274 const proxy = ` 275 -- c.com@v1.2.3/go.mod -- 276 module c.com 277 278 go 1.12 279 280 require b.com v1.2.3 281 -- c.com@v1.2.3/blah/blah.go -- 282 package blah 283 284 import "fmt" 285 286 func SaySomething() { 287 fmt.Println("something") 288 } 289 -- b.com@v1.2.3/go.mod -- 290 module b.com 291 292 go 1.12 293 -- b.com@v1.2.4/b/b.go -- 294 package b 295 296 func Hello() {} 297 -- b.com@v1.2.4/go.mod -- 298 module b.com 299 300 go 1.12 301 ` 302 const files = ` 303 -- go.mod -- 304 module a.com 305 306 require c.com v1.2.3 307 308 exclude b.com v1.2.3 309 -- go.sum -- 310 c.com v1.2.3 h1:n07Dz9fYmpNqvZMwZi5NEqFcSHbvLa9lacMX+/g25tw= 311 c.com v1.2.3/go.mod h1:/4TyYgU9Nu5tA4NymP5xyqE8R2VMzGD3TbJCwCOvHAg= 312 -- main.go -- 313 package a 314 315 func main() { 316 var x int 317 } 318 ` 319 WithOptions( 320 ProxyFiles(proxy), 321 ).Run(t, files, func(t *testing.T, env *Env) { 322 env.OnceMet( 323 InitialWorkspaceLoad, 324 Diagnostics(env.AtRegexp("main.go", "x")), 325 ) 326 }) 327 } 328 329 // This change tests that the version of the module used changes after it has 330 // been deleted from the workspace. 331 // 332 // TODO(golang/go#55331): delete this placeholder along with experimental 333 // workspace module. 334 func TestDeleteModule_Interdependent(t *testing.T) { 335 const multiModule = ` 336 -- go.work -- 337 go 1.18 338 339 use ( 340 moda/a 341 modb 342 ) 343 -- moda/a/go.mod -- 344 module a.com 345 346 require b.com v1.2.3 347 -- moda/a/go.sum -- 348 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= 349 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= 350 -- moda/a/a.go -- 351 package a 352 353 import ( 354 "b.com/b" 355 ) 356 357 func main() { 358 var x int 359 _ = b.Hello() 360 } 361 -- modb/go.mod -- 362 module b.com 363 364 -- modb/b/b.go -- 365 package b 366 367 func Hello() int { 368 var x int 369 } 370 ` 371 WithOptions( 372 ProxyFiles(workspaceModuleProxy), 373 ).Run(t, multiModule, func(t *testing.T, env *Env) { 374 env.OpenFile("moda/a/a.go") 375 env.Await(env.DoneWithOpen()) 376 377 originalLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) 378 original := env.Sandbox.Workdir.URIToPath(originalLoc.URI) 379 if want := "modb/b/b.go"; !strings.HasSuffix(original, want) { 380 t.Errorf("expected %s, got %v", want, original) 381 } 382 env.CloseBuffer(original) 383 env.AfterChange() 384 385 env.RemoveWorkspaceFile("modb/b/b.go") 386 env.RemoveWorkspaceFile("modb/go.mod") 387 env.WriteWorkspaceFile("go.work", "go 1.18\nuse moda/a") 388 env.AfterChange() 389 390 gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) 391 got := env.Sandbox.Workdir.URIToPath(gotLoc.URI) 392 if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) { 393 t.Errorf("expected %s, got %v", want, got) 394 } 395 }) 396 } 397 398 // Tests that the version of the module used changes after it has been added 399 // to the workspace. 400 func TestCreateModule_Interdependent(t *testing.T) { 401 const multiModule = ` 402 -- go.work -- 403 go 1.18 404 405 use ( 406 moda/a 407 ) 408 -- moda/a/go.mod -- 409 module a.com 410 411 require b.com v1.2.3 412 -- moda/a/go.sum -- 413 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= 414 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= 415 -- moda/a/a.go -- 416 package a 417 418 import ( 419 "b.com/b" 420 ) 421 422 func main() { 423 var x int 424 _ = b.Hello() 425 } 426 ` 427 WithOptions( 428 ProxyFiles(workspaceModuleProxy), 429 ).Run(t, multiModule, func(t *testing.T, env *Env) { 430 env.OpenFile("moda/a/a.go") 431 loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) 432 original := env.Sandbox.Workdir.URIToPath(loc.URI) 433 if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) { 434 t.Errorf("expected %s, got %v", want, original) 435 } 436 env.CloseBuffer(original) 437 env.WriteWorkspaceFiles(map[string]string{ 438 "go.work": `go 1.18 439 440 use ( 441 moda/a 442 modb 443 ) 444 `, 445 "modb/go.mod": "module b.com", 446 "modb/b/b.go": `package b 447 448 func Hello() int { 449 var x int 450 } 451 `, 452 }) 453 env.AfterChange(Diagnostics(env.AtRegexp("modb/b/b.go", "x"))) 454 gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) 455 got := env.Sandbox.Workdir.URIToPath(gotLoc.URI) 456 if want := "modb/b/b.go"; !strings.HasSuffix(got, want) { 457 t.Errorf("expected %s, got %v", want, original) 458 } 459 }) 460 } 461 462 // This test confirms that a gopls workspace can recover from initialization 463 // with one invalid module. 464 func TestOneBrokenModule(t *testing.T) { 465 const multiModule = ` 466 -- go.work -- 467 go 1.18 468 469 use ( 470 moda/a 471 modb 472 ) 473 -- moda/a/go.mod -- 474 module a.com 475 476 require b.com v1.2.3 477 478 -- moda/a/a.go -- 479 package a 480 481 import ( 482 "b.com/b" 483 ) 484 485 func main() { 486 var x int 487 _ = b.Hello() 488 } 489 -- modb/go.mod -- 490 modul b.com // typo here 491 492 -- modb/b/b.go -- 493 package b 494 495 func Hello() int { 496 var x int 497 } 498 ` 499 WithOptions( 500 ProxyFiles(workspaceModuleProxy), 501 ).Run(t, multiModule, func(t *testing.T, env *Env) { 502 env.OpenFile("modb/go.mod") 503 env.AfterChange( 504 Diagnostics(AtPosition("modb/go.mod", 0, 0)), 505 ) 506 env.RegexpReplace("modb/go.mod", "modul", "module") 507 env.SaveBufferWithoutActions("modb/go.mod") 508 env.AfterChange( 509 Diagnostics(env.AtRegexp("modb/b/b.go", "x")), 510 ) 511 }) 512 } 513 514 // TestBadGoWork exercises the panic from golang/vscode-go#2121. 515 func TestBadGoWork(t *testing.T) { 516 const files = ` 517 -- go.work -- 518 use ./bar 519 -- bar/go.mod -- 520 module example.com/bar 521 ` 522 Run(t, files, func(t *testing.T, env *Env) { 523 env.OpenFile("go.work") 524 }) 525 } 526 527 func TestUseGoWork(t *testing.T) { 528 // This test validates certain functionality related to using a go.work 529 // file to specify workspace modules. 530 const multiModule = ` 531 -- moda/a/go.mod -- 532 module a.com 533 534 require b.com v1.2.3 535 -- moda/a/go.sum -- 536 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= 537 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= 538 -- moda/a/a.go -- 539 package a 540 541 import ( 542 "b.com/b" 543 ) 544 545 func main() { 546 var x int 547 _ = b.Hello() 548 } 549 -- modb/go.mod -- 550 module b.com 551 552 require example.com v1.2.3 553 -- modb/go.sum -- 554 example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= 555 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 556 -- modb/b/b.go -- 557 package b 558 559 func Hello() int { 560 var x int 561 } 562 -- go.work -- 563 go 1.17 564 565 use ( 566 ./moda/a 567 ) 568 ` 569 WithOptions( 570 ProxyFiles(workspaceModuleProxy), 571 Settings{ 572 "subdirWatchPatterns": "on", 573 }, 574 ).Run(t, multiModule, func(t *testing.T, env *Env) { 575 // Initially, the go.work should cause only the a.com module to be loaded, 576 // so we shouldn't get any file watches for modb. Further validate this by 577 // jumping to a definition in b.com and ensuring that we go to the module 578 // cache. 579 env.OnceMet( 580 InitialWorkspaceLoad, 581 NoFileWatchMatching("modb"), 582 ) 583 env.OpenFile("moda/a/a.go") 584 env.Await(env.DoneWithOpen()) 585 586 // To verify which modules are loaded, we'll jump to the definition of 587 // b.Hello. 588 checkHelloLocation := func(want string) error { 589 loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) 590 file := env.Sandbox.Workdir.URIToPath(loc.URI) 591 if !strings.HasSuffix(file, want) { 592 return fmt.Errorf("expected %s, got %v", want, file) 593 } 594 return nil 595 } 596 597 // Initially this should be in the module cache, as b.com is not replaced. 598 if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil { 599 t.Fatal(err) 600 } 601 602 // Now, modify the go.work file on disk to activate the b.com module in 603 // the workspace. 604 env.WriteWorkspaceFile("go.work", ` 605 go 1.17 606 607 use ( 608 ./moda/a 609 ./modb 610 ) 611 `) 612 613 // As of golang/go#54069, writing go.work to the workspace triggers a 614 // workspace reload, and new file watches. 615 env.AfterChange( 616 Diagnostics(env.AtRegexp("modb/b/b.go", "x")), 617 // TODO(golang/go#60340): we don't get a file watch yet, because 618 // updateWatchedDirectories runs before snapshot.load. Instead, we get it 619 // after the next change (the didOpen below). 620 // FileWatchMatching("modb"), 621 ) 622 623 // Jumping to definition should now go to b.com in the workspace. 624 if err := checkHelloLocation("modb/b/b.go"); err != nil { 625 t.Fatal(err) 626 } 627 628 // Now, let's modify the go.work *overlay* (not on disk), and verify that 629 // this change is only picked up once it is saved. 630 env.OpenFile("go.work") 631 env.AfterChange( 632 // TODO(golang/go#60340): delete this expectation in favor of 633 // the commented-out expectation above, once we fix the evaluation order 634 // of file watches. We should not have to wait for a second change to get 635 // the correct watches. 636 FileWatchMatching("modb"), 637 ) 638 env.SetBufferContent("go.work", `go 1.17 639 640 use ( 641 ./moda/a 642 )`) 643 644 // Simply modifying the go.work file does not cause a reload, so we should 645 // still jump within the workspace. 646 // 647 // TODO: should editing the go.work above cause modb diagnostics to be 648 // suppressed? 649 env.Await(env.DoneWithChange()) 650 if err := checkHelloLocation("modb/b/b.go"); err != nil { 651 t.Fatal(err) 652 } 653 654 // Saving should reload the workspace. 655 env.SaveBufferWithoutActions("go.work") 656 if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil { 657 t.Fatal(err) 658 } 659 660 // This fails if guarded with a OnceMet(DoneWithSave(), ...), because it is 661 // delayed (and therefore not synchronous with the change). 662 // 663 // Note: this check used to assert on NoDiagnostics, but with zero-config 664 // gopls we still have diagnostics. 665 env.Await(Diagnostics(ForFile("modb/go.mod"), WithMessage("example.com is not used"))) 666 667 // Test Formatting. 668 env.SetBufferContent("go.work", `go 1.18 669 use ( 670 671 672 673 ./moda/a 674 ) 675 `) // TODO(matloob): For some reason there's a "start position 7:0 is out of bounds" error when the ")" is on the last character/line in the file. Rob probably knows what's going on. 676 env.SaveBuffer("go.work") 677 env.Await(env.DoneWithSave()) 678 gotWorkContents := env.ReadWorkspaceFile("go.work") 679 wantWorkContents := `go 1.18 680 681 use ( 682 ./moda/a 683 ) 684 ` 685 if gotWorkContents != wantWorkContents { 686 t.Fatalf("formatted contents of workspace: got %q; want %q", gotWorkContents, wantWorkContents) 687 } 688 }) 689 } 690 691 func TestUseGoWorkDiagnosticMissingModule(t *testing.T) { 692 const files = ` 693 -- go.work -- 694 go 1.18 695 696 use ./foo 697 -- bar/go.mod -- 698 module example.com/bar 699 ` 700 Run(t, files, func(t *testing.T, env *Env) { 701 env.OpenFile("go.work") 702 env.AfterChange( 703 Diagnostics(env.AtRegexp("go.work", "use"), WithMessage("directory ./foo does not contain a module")), 704 ) 705 // The following tests is a regression test against an issue where we weren't 706 // copying the workFile struct field on workspace when a new one was created in 707 // (*workspace).invalidate. Set the buffer content to a working file so that 708 // invalidate recognizes the workspace to be change and copies over the workspace 709 // struct, and then set the content back to the old contents to make sure 710 // the diagnostic still shows up. 711 env.SetBufferContent("go.work", "go 1.18 \n\n use ./bar\n") 712 env.AfterChange( 713 NoDiagnostics(env.AtRegexp("go.work", "use")), 714 ) 715 env.SetBufferContent("go.work", "go 1.18 \n\n use ./foo\n") 716 env.AfterChange( 717 Diagnostics(env.AtRegexp("go.work", "use"), WithMessage("directory ./foo does not contain a module")), 718 ) 719 }) 720 } 721 722 func TestUseGoWorkDiagnosticSyntaxError(t *testing.T) { 723 const files = ` 724 -- go.work -- 725 go 1.18 726 727 usa ./foo 728 replace 729 ` 730 Run(t, files, func(t *testing.T, env *Env) { 731 env.OpenFile("go.work") 732 env.AfterChange( 733 Diagnostics(env.AtRegexp("go.work", "usa"), WithMessage("unknown directive: usa")), 734 Diagnostics(env.AtRegexp("go.work", "replace"), WithMessage("usage: replace")), 735 ) 736 }) 737 } 738 739 func TestUseGoWorkHover(t *testing.T) { 740 const files = ` 741 -- go.work -- 742 go 1.18 743 744 use ./foo 745 use ( 746 ./bar 747 ./bar/baz 748 ) 749 -- foo/go.mod -- 750 module example.com/foo 751 -- bar/go.mod -- 752 module example.com/bar 753 -- bar/baz/go.mod -- 754 module example.com/bar/baz 755 ` 756 Run(t, files, func(t *testing.T, env *Env) { 757 env.OpenFile("go.work") 758 759 tcs := map[string]string{ 760 `\./foo`: "example.com/foo", 761 `(?m)\./bar$`: "example.com/bar", 762 `\./bar/baz`: "example.com/bar/baz", 763 } 764 765 for hoverRE, want := range tcs { 766 got, _ := env.Hover(env.RegexpSearch("go.work", hoverRE)) 767 if got.Value != want { 768 t.Errorf(`hover on %q: got %q, want %q`, hoverRE, got, want) 769 } 770 } 771 }) 772 } 773 774 func TestExpandToGoWork(t *testing.T) { 775 const workspace = ` 776 -- moda/a/go.mod -- 777 module a.com 778 779 require b.com v1.2.3 780 -- moda/a/a.go -- 781 package a 782 783 import ( 784 "b.com/b" 785 ) 786 787 func main() { 788 var x int 789 _ = b.Hello() 790 } 791 -- modb/go.mod -- 792 module b.com 793 794 require example.com v1.2.3 795 -- modb/b/b.go -- 796 package b 797 798 func Hello() int { 799 var x int 800 } 801 -- go.work -- 802 go 1.17 803 804 use ( 805 ./moda/a 806 ./modb 807 ) 808 ` 809 WithOptions( 810 WorkspaceFolders("moda/a"), 811 ).Run(t, workspace, func(t *testing.T, env *Env) { 812 env.OpenFile("moda/a/a.go") 813 env.Await(env.DoneWithOpen()) 814 loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) 815 file := env.Sandbox.Workdir.URIToPath(loc.URI) 816 want := "modb/b/b.go" 817 if !strings.HasSuffix(file, want) { 818 t.Errorf("expected %s, got %v", want, file) 819 } 820 }) 821 } 822 823 func TestInnerGoWork(t *testing.T) { 824 // This test checks that gopls honors a go.work file defined 825 // inside a go module (golang/go#63917). 826 const workspace = ` 827 -- go.mod -- 828 module a.com 829 830 require b.com v1.2.3 831 -- a/go.work -- 832 go 1.18 833 834 use ( 835 .. 836 ../b 837 ) 838 -- a/a.go -- 839 package a 840 841 import "b.com/b" 842 843 var _ = b.B 844 -- b/go.mod -- 845 module b.com/b 846 847 -- b/b.go -- 848 package b 849 850 const B = 0 851 ` 852 WithOptions( 853 // This doesn't work if we open the outer module. I'm not sure it should, 854 // since the go.work file does not apply to the entire module, just a 855 // subdirectory. 856 WorkspaceFolders("a"), 857 ).Run(t, workspace, func(t *testing.T, env *Env) { 858 env.OpenFile("a/a.go") 859 loc := env.GoToDefinition(env.RegexpSearch("a/a.go", "b.(B)")) 860 got := env.Sandbox.Workdir.URIToPath(loc.URI) 861 want := "b/b.go" 862 if got != want { 863 t.Errorf("Definition(b.B): got %q, want %q", got, want) 864 } 865 }) 866 } 867 868 func TestNonWorkspaceFileCreation(t *testing.T) { 869 const files = ` 870 -- work/go.mod -- 871 module mod.com 872 873 go 1.12 874 -- work/x.go -- 875 package x 876 ` 877 878 const code = ` 879 package foo 880 import "fmt" 881 var _ = fmt.Printf 882 ` 883 WithOptions( 884 WorkspaceFolders("work"), // so that outside/... is outside the workspace 885 ).Run(t, files, func(t *testing.T, env *Env) { 886 env.CreateBuffer("outside/foo.go", "") 887 env.EditBuffer("outside/foo.go", fake.NewEdit(0, 0, 0, 0, code)) 888 env.GoToDefinition(env.RegexpSearch("outside/foo.go", `Printf`)) 889 }) 890 } 891 892 func TestGoWork_V2Module(t *testing.T) { 893 // When using a go.work, we must have proxy content even if it is replaced. 894 const proxy = ` 895 -- b.com/v2@v2.1.9/go.mod -- 896 module b.com/v2 897 898 go 1.12 899 -- b.com/v2@v2.1.9/b/b.go -- 900 package b 901 902 func Ciao()() int { 903 return 0 904 } 905 ` 906 907 const multiModule = ` 908 -- go.work -- 909 go 1.18 910 911 use ( 912 moda/a 913 modb 914 modb/v2 915 modc 916 ) 917 -- moda/a/go.mod -- 918 module a.com 919 920 require b.com/v2 v2.1.9 921 -- moda/a/a.go -- 922 package a 923 924 import ( 925 "b.com/v2/b" 926 ) 927 928 func main() { 929 var x int 930 _ = b.Hi() 931 } 932 -- modb/go.mod -- 933 module b.com 934 935 -- modb/b/b.go -- 936 package b 937 938 func Hello() int { 939 var x int 940 } 941 -- modb/v2/go.mod -- 942 module b.com/v2 943 944 -- modb/v2/b/b.go -- 945 package b 946 947 func Hi() int { 948 var x int 949 } 950 -- modc/go.mod -- 951 module gopkg.in/yaml.v1 // test gopkg.in versions 952 -- modc/main.go -- 953 package main 954 955 func main() { 956 var x int 957 } 958 ` 959 960 WithOptions( 961 ProxyFiles(proxy), 962 ).Run(t, multiModule, func(t *testing.T, env *Env) { 963 env.OnceMet( 964 InitialWorkspaceLoad, 965 // TODO(rfindley): assert on the full set of diagnostics here. We 966 // should ensure that we don't have a diagnostic at b.Hi in a.go. 967 Diagnostics(env.AtRegexp("moda/a/a.go", "x")), 968 Diagnostics(env.AtRegexp("modb/b/b.go", "x")), 969 Diagnostics(env.AtRegexp("modb/v2/b/b.go", "x")), 970 Diagnostics(env.AtRegexp("modc/main.go", "x")), 971 ) 972 }) 973 } 974 975 // Confirm that a fix for a tidy module will correct all modules in the 976 // workspace. 977 func TestMultiModule_OneBrokenModule(t *testing.T) { 978 // In the earlier 'experimental workspace mode', gopls would aggregate go.sum 979 // entries for the workspace module, allowing it to correctly associate 980 // missing go.sum with diagnostics. With go.work files, this doesn't work: 981 // the go.command will happily write go.work.sum. 982 t.Skip("golang/go#57509: go.mod diagnostics do not work in go.work mode") 983 const files = ` 984 -- go.work -- 985 go 1.18 986 987 use ( 988 a 989 b 990 ) 991 -- go.work.sum -- 992 -- a/go.mod -- 993 module a.com 994 995 go 1.12 996 -- a/main.go -- 997 package main 998 -- b/go.mod -- 999 module b.com 1000 1001 go 1.12 1002 1003 require ( 1004 example.com v1.2.3 1005 ) 1006 -- b/go.sum -- 1007 -- b/main.go -- 1008 package b 1009 1010 import "example.com/blah" 1011 1012 func main() { 1013 blah.Hello() 1014 } 1015 ` 1016 WithOptions( 1017 ProxyFiles(workspaceProxy), 1018 ).Run(t, files, func(t *testing.T, env *Env) { 1019 params := &protocol.PublishDiagnosticsParams{} 1020 env.OpenFile("b/go.mod") 1021 env.AfterChange( 1022 Diagnostics( 1023 env.AtRegexp("go.mod", `example.com v1.2.3`), 1024 WithMessage("go.sum is out of sync"), 1025 ), 1026 ReadDiagnostics("b/go.mod", params), 1027 ) 1028 for _, d := range params.Diagnostics { 1029 if !strings.Contains(d.Message, "go.sum is out of sync") { 1030 continue 1031 } 1032 actions := env.GetQuickFixes("b/go.mod", []protocol.Diagnostic{d}) 1033 if len(actions) != 2 { 1034 t.Fatalf("expected 2 code actions, got %v", len(actions)) 1035 } 1036 env.ApplyQuickFixes("b/go.mod", []protocol.Diagnostic{d}) 1037 } 1038 env.AfterChange( 1039 NoDiagnostics(ForFile("b/go.mod")), 1040 ) 1041 }) 1042 } 1043 1044 // Tests the fix for golang/go#52500. 1045 func TestChangeTestVariant_Issue52500(t *testing.T) { 1046 const src = ` 1047 -- go.mod -- 1048 module mod.test 1049 1050 go 1.12 1051 -- main_test.go -- 1052 package main_test 1053 1054 type Server struct{} 1055 1056 const mainConst = otherConst 1057 -- other_test.go -- 1058 package main_test 1059 1060 const otherConst = 0 1061 1062 func (Server) Foo() {} 1063 ` 1064 1065 Run(t, src, func(t *testing.T, env *Env) { 1066 env.OpenFile("other_test.go") 1067 env.RegexpReplace("other_test.go", "main_test", "main") 1068 1069 // For this test to function, it is necessary to wait on both of the 1070 // expectations below: the bug is that when switching the package name in 1071 // other_test.go from main->main_test, metadata for main_test is not marked 1072 // as invalid. So we need to wait for the metadata of main_test.go to be 1073 // updated before moving other_test.go back to the main_test package. 1074 env.Await( 1075 Diagnostics(env.AtRegexp("other_test.go", "Server")), 1076 Diagnostics(env.AtRegexp("main_test.go", "otherConst")), 1077 ) 1078 env.RegexpReplace("other_test.go", "main", "main_test") 1079 env.AfterChange( 1080 NoDiagnostics(ForFile("other_test.go")), 1081 NoDiagnostics(ForFile("main_test.go")), 1082 ) 1083 1084 // This will cause a test failure if other_test.go is not in any package. 1085 _ = env.GoToDefinition(env.RegexpSearch("other_test.go", "Server")) 1086 }) 1087 } 1088 1089 // Test for golang/go#48929. 1090 func TestClearNonWorkspaceDiagnostics(t *testing.T) { 1091 const ws = ` 1092 -- go.work -- 1093 go 1.18 1094 1095 use ( 1096 ./b 1097 ) 1098 -- a/go.mod -- 1099 module a 1100 1101 go 1.17 1102 -- a/main.go -- 1103 package main 1104 1105 func main() { 1106 var V string 1107 } 1108 -- b/go.mod -- 1109 module b 1110 1111 go 1.17 1112 -- b/main.go -- 1113 package b 1114 1115 import ( 1116 _ "fmt" 1117 ) 1118 ` 1119 Run(t, ws, func(t *testing.T, env *Env) { 1120 env.OpenFile("b/main.go") 1121 env.AfterChange( 1122 NoDiagnostics(ForFile("a/main.go")), 1123 ) 1124 env.OpenFile("a/main.go") 1125 env.AfterChange( 1126 Diagnostics(env.AtRegexp("a/main.go", "V"), WithMessage("not used")), 1127 ) 1128 // Here, diagnostics are added because of zero-config gopls. 1129 // In the past, they were added simply due to diagnosing changed files. 1130 // (see TestClearNonWorkspaceDiagnostics_NoView below for a 1131 // reimplementation of that test). 1132 if got, want := len(env.Views()), 2; got != want { 1133 t.Errorf("after opening a/main.go, got %d views, want %d", got, want) 1134 } 1135 env.CloseBuffer("a/main.go") 1136 env.AfterChange( 1137 NoDiagnostics(ForFile("a/main.go")), 1138 ) 1139 if got, want := len(env.Views()), 1; got != want { 1140 t.Errorf("after closing a/main.go, got %d views, want %d", got, want) 1141 } 1142 }) 1143 } 1144 1145 // This test is like TestClearNonWorkspaceDiagnostics, but bypasses the 1146 // zero-config algorithm by opening a nested workspace folder. 1147 // 1148 // We should still compute diagnostics correctly for open packages. 1149 func TestClearNonWorkspaceDiagnostics_NoView(t *testing.T) { 1150 const ws = ` 1151 -- a/go.mod -- 1152 module example.com/a 1153 1154 go 1.18 1155 1156 require example.com/b v1.2.3 1157 1158 replace example.com/b => ../b 1159 1160 -- a/a.go -- 1161 package a 1162 1163 import "example.com/b" 1164 1165 func _() { 1166 V := b.B // unused 1167 } 1168 1169 -- b/go.mod -- 1170 module b 1171 1172 go 1.18 1173 1174 -- b/b.go -- 1175 package b 1176 1177 const B = 2 1178 1179 func _() { 1180 var V int // unused 1181 } 1182 1183 -- b/b2.go -- 1184 package b 1185 1186 const B2 = B 1187 1188 -- c/c.go -- 1189 package main 1190 1191 func main() { 1192 var V int // unused 1193 } 1194 ` 1195 WithOptions( 1196 WorkspaceFolders("a"), 1197 ).Run(t, ws, func(t *testing.T, env *Env) { 1198 env.OpenFile("a/a.go") 1199 env.AfterChange( 1200 Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), 1201 NoDiagnostics(ForFile("b/b.go")), 1202 NoDiagnostics(ForFile("c/c.go")), 1203 ) 1204 env.OpenFile("b/b.go") 1205 env.AfterChange( 1206 Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), 1207 Diagnostics(env.AtRegexp("b/b.go", "V"), WithMessage("not used")), 1208 NoDiagnostics(ForFile("c/c.go")), 1209 ) 1210 1211 // Opening b/b.go should not result in a new view, because b is not 1212 // contained in a workspace folder. 1213 // 1214 // Yet we should get diagnostics for b, because it is open. 1215 if got, want := len(env.Views()), 1; got != want { 1216 t.Errorf("after opening b/b.go, got %d views, want %d", got, want) 1217 } 1218 env.CloseBuffer("b/b.go") 1219 env.AfterChange( 1220 Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), 1221 NoDiagnostics(ForFile("b/b.go")), 1222 NoDiagnostics(ForFile("c/c.go")), 1223 ) 1224 1225 // We should get references in the b package. 1226 bUse := env.RegexpSearch("a/a.go", `b\.(B)`) 1227 refs := env.References(bUse) 1228 wantRefs := []string{"a/a.go", "b/b.go", "b/b2.go"} 1229 var gotRefs []string 1230 for _, ref := range refs { 1231 gotRefs = append(gotRefs, env.Sandbox.Workdir.URIToPath(ref.URI)) 1232 } 1233 sort.Strings(gotRefs) 1234 if diff := cmp.Diff(wantRefs, gotRefs); diff != "" { 1235 t.Errorf("references(b.B) mismatch (-want +got)\n%s", diff) 1236 } 1237 1238 // Opening c/c.go should also not result in a new view, yet we should get 1239 // orphaned file diagnostics. 1240 env.OpenFile("c/c.go") 1241 env.AfterChange( 1242 Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), 1243 NoDiagnostics(ForFile("b/b.go")), 1244 Diagnostics(env.AtRegexp("c/c.go", "V"), WithMessage("not used")), 1245 ) 1246 if got, want := len(env.Views()), 1; got != want { 1247 t.Errorf("after opening b/b.go, got %d views, want %d", got, want) 1248 } 1249 1250 env.CloseBuffer("c/c.go") 1251 env.AfterChange( 1252 Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), 1253 NoDiagnostics(ForFile("b/b.go")), 1254 NoDiagnostics(ForFile("c/c.go")), 1255 ) 1256 env.CloseBuffer("a/a.go") 1257 env.AfterChange( 1258 Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), 1259 NoDiagnostics(ForFile("b/b.go")), 1260 NoDiagnostics(ForFile("c/c.go")), 1261 ) 1262 }) 1263 } 1264 1265 // Test that we don't get a version warning when the Go version in PATH is 1266 // supported. 1267 func TestOldGoNotification_SupportedVersion(t *testing.T) { 1268 v := goVersion(t) 1269 if v < goversion.OldestSupported() { 1270 t.Skipf("go version 1.%d is unsupported", v) 1271 } 1272 1273 Run(t, "", func(t *testing.T, env *Env) { 1274 env.OnceMet( 1275 InitialWorkspaceLoad, 1276 NoShownMessage("upgrade"), 1277 ) 1278 }) 1279 } 1280 1281 // Test that we do get a version warning when the Go version in PATH is 1282 // unsupported, though this test may never execute if we stop running CI at 1283 // legacy Go versions (see also TestOldGoNotification_Fake) 1284 func TestOldGoNotification_UnsupportedVersion(t *testing.T) { 1285 v := goVersion(t) 1286 if v >= goversion.OldestSupported() { 1287 t.Skipf("go version 1.%d is supported", v) 1288 } 1289 1290 Run(t, "", func(t *testing.T, env *Env) { 1291 env.Await( 1292 // Note: cannot use OnceMet(InitialWorkspaceLoad, ...) here, as the 1293 // upgrade message may race with the IWL. 1294 ShownMessage("Please upgrade"), 1295 ) 1296 }) 1297 } 1298 1299 func TestOldGoNotification_Fake(t *testing.T) { 1300 // Get the Go version from path, and make sure it's unsupported. 1301 // 1302 // In the future we'll stop running CI on legacy Go versions. By mutating the 1303 // oldest supported Go version here, we can at least ensure that the 1304 // ShowMessage pop-up works. 1305 ctx := context.Background() 1306 version, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{}) 1307 if err != nil { 1308 t.Fatal(err) 1309 } 1310 defer func(t []goversion.Support) { 1311 goversion.Supported = t 1312 }(goversion.Supported) 1313 goversion.Supported = []goversion.Support{ 1314 {GoVersion: version, InstallGoplsVersion: "v1.0.0"}, 1315 } 1316 1317 Run(t, "", func(t *testing.T, env *Env) { 1318 env.Await( 1319 // Note: cannot use OnceMet(InitialWorkspaceLoad, ...) here, as the 1320 // upgrade message may race with the IWL. 1321 ShownMessage("Please upgrade"), 1322 ) 1323 }) 1324 } 1325 1326 // goVersion returns the version of the Go command in PATH. 1327 func goVersion(t *testing.T) int { 1328 t.Helper() 1329 ctx := context.Background() 1330 goversion, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{}) 1331 if err != nil { 1332 t.Fatal(err) 1333 } 1334 return goversion 1335 } 1336 1337 func TestGoworkMutation(t *testing.T) { 1338 WithOptions( 1339 ProxyFiles(workspaceModuleProxy), 1340 ).Run(t, multiModule, func(t *testing.T, env *Env) { 1341 env.RunGoCommand("work", "init") 1342 env.RunGoCommand("work", "use", "-r", ".") 1343 env.AfterChange( 1344 Diagnostics(env.AtRegexp("moda/a/a.go", "x")), 1345 Diagnostics(env.AtRegexp("modb/b/b.go", "x")), 1346 NoDiagnostics(env.AtRegexp("moda/a/a.go", `b\.Hello`)), 1347 ) 1348 env.RunGoCommand("work", "edit", "-dropuse", "modb") 1349 env.Await( 1350 Diagnostics(env.AtRegexp("moda/a/a.go", "x")), 1351 NoDiagnostics(env.AtRegexp("modb/b/b.go", "x")), 1352 Diagnostics(env.AtRegexp("moda/a/a.go", `b\.Hello`)), 1353 ) 1354 }) 1355 }