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 5package regtest 6 7import ( 8 "testing" 9 10 "golang.org/x/tools/gopls/internal/hooks" 11 . "golang.org/x/tools/internal/lsp/regtest" 12 13 "golang.org/x/tools/internal/lsp/fake" 14 "golang.org/x/tools/internal/lsp/protocol" 15 "golang.org/x/tools/internal/testenv" 16) 17 18func TestMain(m *testing.M) { 19 Main(m, hooks.Options) 20} 21 22func TestEditFile(t *testing.T) { 23 const pkg = ` 24-- go.mod -- 25module mod.com 26 27go 1.14 28-- a/a.go -- 29package a 30 31func _() { 32 var x int 33} 34` 35 // Edit the file when it's *not open* in the workspace, and check that 36 // diagnostics are updated. 37 t.Run("unopened", func(t *testing.T) { 38 Run(t, pkg, func(t *testing.T, env *Env) { 39 env.Await( 40 env.DiagnosticAtRegexp("a/a.go", "x"), 41 ) 42 env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`) 43 env.Await( 44 EmptyDiagnostics("a/a.go"), 45 ) 46 }) 47 }) 48 49 // Edit the file when it *is open* in the workspace, and check that 50 // diagnostics are *not* updated. 51 t.Run("opened", func(t *testing.T) { 52 Run(t, pkg, func(t *testing.T, env *Env) { 53 env.OpenFile("a/a.go") 54 // Insert a trivial edit so that we don't automatically update the buffer 55 // (see CL 267577). 56 env.EditBuffer("a/a.go", fake.NewEdit(0, 0, 0, 0, " ")) 57 env.Await(env.DoneWithOpen()) 58 env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`) 59 env.Await( 60 OnceMet( 61 env.DoneWithChangeWatchedFiles(), 62 env.DiagnosticAtRegexp("a/a.go", "x"), 63 )) 64 }) 65 }) 66} 67 68// Edit a dependency on disk and expect a new diagnostic. 69func TestEditDependency(t *testing.T) { 70 const pkg = ` 71-- go.mod -- 72module mod.com 73 74go 1.14 75-- b/b.go -- 76package b 77 78func B() int { return 0 } 79-- a/a.go -- 80package a 81 82import ( 83 "mod.com/b" 84) 85 86func _() { 87 _ = b.B() 88} 89` 90 Run(t, pkg, func(t *testing.T, env *Env) { 91 env.OpenFile("a/a.go") 92 env.Await(env.DoneWithOpen()) 93 env.WriteWorkspaceFile("b/b.go", `package b; func B() {};`) 94 env.Await( 95 env.DiagnosticAtRegexp("a/a.go", "b.B"), 96 ) 97 }) 98} 99 100// Edit both the current file and one of its dependencies on disk and 101// expect diagnostic changes. 102func TestEditFileAndDependency(t *testing.T) { 103 const pkg = ` 104-- go.mod -- 105module mod.com 106 107go 1.14 108-- b/b.go -- 109package b 110 111func B() int { return 0 } 112-- a/a.go -- 113package a 114 115import ( 116 "mod.com/b" 117) 118 119func _() { 120 var x int 121 _ = b.B() 122} 123` 124 Run(t, pkg, func(t *testing.T, env *Env) { 125 env.Await( 126 env.DiagnosticAtRegexp("a/a.go", "x"), 127 ) 128 env.WriteWorkspaceFiles(map[string]string{ 129 "b/b.go": `package b; func B() {};`, 130 "a/a.go": `package a 131 132import "mod.com/b" 133 134func _() { 135 b.B() 136}`, 137 }) 138 env.Await( 139 EmptyDiagnostics("a/a.go"), 140 NoDiagnostics("b/b.go"), 141 ) 142 }) 143} 144 145// Delete a dependency and expect a new diagnostic. 146func TestDeleteDependency(t *testing.T) { 147 const pkg = ` 148-- go.mod -- 149module mod.com 150 151go 1.14 152-- b/b.go -- 153package b 154 155func B() int { return 0 } 156-- a/a.go -- 157package a 158 159import ( 160 "mod.com/b" 161) 162 163func _() { 164 _ = b.B() 165} 166` 167 Run(t, pkg, func(t *testing.T, env *Env) { 168 env.OpenFile("a/a.go") 169 env.Await(env.DoneWithOpen()) 170 env.RemoveWorkspaceFile("b/b.go") 171 env.Await( 172 env.DiagnosticAtRegexp("a/a.go", "\"mod.com/b\""), 173 ) 174 }) 175} 176 177// Create a dependency on disk and expect the diagnostic to go away. 178func TestCreateDependency(t *testing.T) { 179 const missing = ` 180-- go.mod -- 181module mod.com 182 183go 1.14 184-- b/b.go -- 185package b 186 187func B() int { return 0 } 188-- a/a.go -- 189package a 190 191import ( 192 "mod.com/c" 193) 194 195func _() { 196 c.C() 197} 198` 199 Run(t, missing, func(t *testing.T, env *Env) { 200 t.Skip("the initial workspace load fails and never retries") 201 202 env.Await( 203 env.DiagnosticAtRegexp("a/a.go", "\"mod.com/c\""), 204 ) 205 env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`) 206 env.Await( 207 EmptyDiagnostics("c/c.go"), 208 ) 209 }) 210} 211 212// Create a new dependency and add it to the file on disk. 213// This is similar to what might happen if you switch branches. 214func TestCreateAndAddDependency(t *testing.T) { 215 const original = ` 216-- go.mod -- 217module mod.com 218 219go 1.14 220-- a/a.go -- 221package a 222 223func _() {} 224` 225 Run(t, original, func(t *testing.T, env *Env) { 226 env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`) 227 env.WriteWorkspaceFile("a/a.go", `package a; import "mod.com/c"; func _() { c.C() }`) 228 env.Await( 229 NoDiagnostics("a/a.go"), 230 ) 231 }) 232} 233 234// Create a new file that defines a new symbol, in the same package. 235func TestCreateFile(t *testing.T) { 236 const pkg = ` 237-- go.mod -- 238module mod.com 239 240go 1.14 241-- a/a.go -- 242package a 243 244func _() { 245 hello() 246} 247` 248 Run(t, pkg, func(t *testing.T, env *Env) { 249 env.Await( 250 env.DiagnosticAtRegexp("a/a.go", "hello"), 251 ) 252 env.WriteWorkspaceFile("a/a2.go", `package a; func hello() {};`) 253 env.Await( 254 EmptyDiagnostics("a/a.go"), 255 ) 256 }) 257} 258 259// Add a new method to an interface and implement it. 260// Inspired by the structure of internal/lsp/source and internal/lsp/cache. 261func TestCreateImplementation(t *testing.T) { 262 const pkg = ` 263-- go.mod -- 264module mod.com 265 266go 1.14 267-- b/b.go -- 268package b 269 270type B interface{ 271 Hello() string 272} 273 274func SayHello(bee B) { 275 println(bee.Hello()) 276} 277-- a/a.go -- 278package a 279 280import "mod.com/b" 281 282type X struct {} 283 284func (_ X) Hello() string { 285 return "" 286} 287 288func _() { 289 x := X{} 290 b.SayHello(x) 291} 292` 293 const newMethod = `package b 294type B interface{ 295 Hello() string 296 Bye() string 297} 298 299func SayHello(bee B) { 300 println(bee.Hello()) 301}` 302 const implementation = `package a 303 304import "mod.com/b" 305 306type X struct {} 307 308func (_ X) Hello() string { 309 return "" 310} 311 312func (_ X) Bye() string { 313 return "" 314} 315 316func _() { 317 x := X{} 318 b.SayHello(x) 319}` 320 321 // Add the new method before the implementation. Expect diagnostics. 322 t.Run("method before implementation", func(t *testing.T) { 323 Run(t, pkg, func(t *testing.T, env *Env) { 324 env.WriteWorkspaceFile("b/b.go", newMethod) 325 env.Await( 326 OnceMet( 327 env.DoneWithChangeWatchedFiles(), 328 DiagnosticAt("a/a.go", 12, 12), 329 ), 330 ) 331 env.WriteWorkspaceFile("a/a.go", implementation) 332 env.Await( 333 EmptyDiagnostics("a/a.go"), 334 ) 335 }) 336 }) 337 // Add the new implementation before the new method. Expect no diagnostics. 338 t.Run("implementation before method", func(t *testing.T) { 339 Run(t, pkg, func(t *testing.T, env *Env) { 340 env.WriteWorkspaceFile("a/a.go", implementation) 341 env.Await( 342 OnceMet( 343 env.DoneWithChangeWatchedFiles(), 344 NoDiagnostics("a/a.go"), 345 ), 346 ) 347 env.WriteWorkspaceFile("b/b.go", newMethod) 348 env.Await( 349 NoDiagnostics("a/a.go"), 350 ) 351 }) 352 }) 353 // Add both simultaneously. Expect no diagnostics. 354 t.Run("implementation and method simultaneously", func(t *testing.T) { 355 Run(t, pkg, func(t *testing.T, env *Env) { 356 env.WriteWorkspaceFiles(map[string]string{ 357 "a/a.go": implementation, 358 "b/b.go": newMethod, 359 }) 360 env.Await( 361 OnceMet( 362 env.DoneWithChangeWatchedFiles(), 363 NoDiagnostics("a/a.go"), 364 ), 365 NoDiagnostics("b/b.go"), 366 ) 367 }) 368 }) 369} 370 371// Tests golang/go#38498. Delete a file and then force a reload. 372// Assert that we no longer try to load the file. 373func TestDeleteFiles(t *testing.T) { 374 testenv.NeedsGo1Point(t, 13) // Poor overlay support causes problems on 1.12. 375 const pkg = ` 376-- go.mod -- 377module mod.com 378 379go 1.14 380-- a/a.go -- 381package a 382 383func _() { 384 var _ int 385} 386-- a/a_unneeded.go -- 387package a 388` 389 t.Run("close then delete", func(t *testing.T) { 390 WithOptions(EditorConfig{ 391 VerboseOutput: true, 392 }).Run(t, pkg, func(t *testing.T, env *Env) { 393 env.OpenFile("a/a.go") 394 env.OpenFile("a/a_unneeded.go") 395 env.Await( 396 OnceMet( 397 env.DoneWithOpen(), 398 LogMatching(protocol.Info, "a_unneeded.go", 1, false), 399 ), 400 ) 401 402 // Close and delete the open file, mimicking what an editor would do. 403 env.CloseBuffer("a/a_unneeded.go") 404 env.RemoveWorkspaceFile("a/a_unneeded.go") 405 env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")") 406 env.Await( 407 env.DiagnosticAtRegexp("a/a.go", "fmt"), 408 ) 409 env.SaveBuffer("a/a.go") 410 env.Await( 411 OnceMet( 412 env.DoneWithSave(), 413 // There should only be one log message containing 414 // a_unneeded.go, from the initial workspace load, which we 415 // check for earlier. If there are more, there's a bug. 416 LogMatching(protocol.Info, "a_unneeded.go", 1, false), 417 ), 418 EmptyDiagnostics("a/a.go"), 419 ) 420 }) 421 }) 422 423 t.Run("delete then close", func(t *testing.T) { 424 WithOptions( 425 EditorConfig{VerboseOutput: true}, 426 ).Run(t, pkg, func(t *testing.T, env *Env) { 427 env.OpenFile("a/a.go") 428 env.OpenFile("a/a_unneeded.go") 429 env.Await( 430 OnceMet( 431 env.DoneWithOpen(), 432 LogMatching(protocol.Info, "a_unneeded.go", 1, false), 433 ), 434 ) 435 436 // Delete and then close the file. 437 env.RemoveWorkspaceFile("a/a_unneeded.go") 438 env.CloseBuffer("a/a_unneeded.go") 439 env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")") 440 env.Await( 441 env.DiagnosticAtRegexp("a/a.go", "fmt"), 442 ) 443 env.SaveBuffer("a/a.go") 444 env.Await( 445 OnceMet( 446 env.DoneWithSave(), 447 // There should only be one log message containing 448 // a_unneeded.go, from the initial workspace load, which we 449 // check for earlier. If there are more, there's a bug. 450 LogMatching(protocol.Info, "a_unneeded.go", 1, false), 451 ), 452 EmptyDiagnostics("a/a.go"), 453 ) 454 }) 455 }) 456} 457 458// This change reproduces the behavior of switching branches, with multiple 459// files being created and deleted. The key change here is the movement of a 460// symbol from one file to another in a given package through a deletion and 461// creation. To reproduce an issue with metadata invalidation in batched 462// changes, the last change in the batch is an on-disk file change that doesn't 463// require metadata invalidation. 464func TestMoveSymbol(t *testing.T) { 465 const pkg = ` 466-- go.mod -- 467module mod.com 468 469go 1.14 470-- main.go -- 471package main 472 473import "mod.com/a" 474 475func main() { 476 var x int 477 x = a.Hello 478 println(x) 479} 480-- a/a1.go -- 481package a 482 483var Hello int 484-- a/a2.go -- 485package a 486 487func _() {} 488` 489 Run(t, pkg, func(t *testing.T, env *Env) { 490 env.ChangeFilesOnDisk([]fake.FileEvent{ 491 { 492 Path: "a/a3.go", 493 Content: `package a 494 495var Hello int 496`, 497 ProtocolEvent: protocol.FileEvent{ 498 URI: env.Sandbox.Workdir.URI("a/a3.go"), 499 Type: protocol.Created, 500 }, 501 }, 502 { 503 Path: "a/a1.go", 504 ProtocolEvent: protocol.FileEvent{ 505 URI: env.Sandbox.Workdir.URI("a/a1.go"), 506 Type: protocol.Deleted, 507 }, 508 }, 509 { 510 Path: "a/a2.go", 511 Content: `package a; func _() {};`, 512 ProtocolEvent: protocol.FileEvent{ 513 URI: env.Sandbox.Workdir.URI("a/a2.go"), 514 Type: protocol.Changed, 515 }, 516 }, 517 }) 518 env.Await( 519 OnceMet( 520 env.DoneWithChangeWatchedFiles(), 521 NoDiagnostics("main.go"), 522 ), 523 ) 524 }) 525} 526 527// Reproduce golang/go#40456. 528func TestChangeVersion(t *testing.T) { 529 const proxy = ` 530-- example.com@v1.2.3/go.mod -- 531module example.com 532 533go 1.12 534-- example.com@v1.2.3/blah/blah.go -- 535package blah 536 537const Name = "Blah" 538 539func X(x int) {} 540-- example.com@v1.2.2/go.mod -- 541module example.com 542 543go 1.12 544-- example.com@v1.2.2/blah/blah.go -- 545package blah 546 547const Name = "Blah" 548 549func X() {} 550-- random.org@v1.2.3/go.mod -- 551module random.org 552 553go 1.12 554-- random.org@v1.2.3/blah/blah.go -- 555package hello 556 557const Name = "Hello" 558` 559 const mod = ` 560-- go.mod -- 561module mod.com 562 563go 1.12 564 565require example.com v1.2.2 566-- main.go -- 567package main 568 569import "example.com/blah" 570 571func main() { 572 blah.X() 573} 574` 575 WithOptions(ProxyFiles(proxy)).Run(t, mod, func(t *testing.T, env *Env) { 576 env.WriteWorkspaceFiles(map[string]string{ 577 "go.mod": `module mod.com 578 579go 1.12 580 581require example.com v1.2.3 582`, 583 "main.go": `package main 584 585import ( 586 "example.com/blah" 587) 588 589func main() { 590 blah.X(1) 591} 592`, 593 }) 594 env.Await( 595 env.DoneWithChangeWatchedFiles(), 596 NoDiagnostics("main.go"), 597 ) 598 }) 599} 600 601// Reproduces golang/go#40340. 602func TestSwitchFromGOPATHToModules(t *testing.T) { 603 testenv.NeedsGo1Point(t, 13) 604 605 const files = ` 606-- foo/blah/blah.go -- 607package blah 608 609const Name = "" 610-- foo/main.go -- 611package main 612 613import "blah" 614 615func main() { 616 _ = blah.Name 617} 618` 619 WithOptions( 620 InGOPATH(), 621 EditorConfig{ 622 Env: map[string]string{ 623 "GO111MODULE": "auto", 624 }, 625 }, 626 Modes(Experimental), // module is in a subdirectory 627 ).Run(t, files, func(t *testing.T, env *Env) { 628 env.OpenFile("foo/main.go") 629 env.Await(env.DiagnosticAtRegexp("foo/main.go", `"blah"`)) 630 if err := env.Sandbox.RunGoCommand(env.Ctx, "foo", "mod", []string{"init", "mod.com"}, true); err != nil { 631 t.Fatal(err) 632 } 633 env.RegexpReplace("foo/main.go", `"blah"`, `"mod.com/blah"`) 634 env.Await( 635 EmptyDiagnostics("foo/main.go"), 636 ) 637 }) 638} 639 640// Reproduces golang/go#40487. 641func TestSwitchFromModulesToGOPATH(t *testing.T) { 642 testenv.NeedsGo1Point(t, 13) 643 644 const files = ` 645-- foo/go.mod -- 646module mod.com 647 648go 1.14 649-- foo/blah/blah.go -- 650package blah 651 652const Name = "" 653-- foo/main.go -- 654package main 655 656import "mod.com/blah" 657 658func main() { 659 _ = blah.Name 660} 661` 662 WithOptions( 663 InGOPATH(), 664 EditorConfig{ 665 Env: map[string]string{ 666 "GO111MODULE": "auto", 667 }, 668 }, 669 ).Run(t, files, func(t *testing.T, env *Env) { 670 env.OpenFile("foo/main.go") 671 env.RemoveWorkspaceFile("foo/go.mod") 672 env.Await( 673 OnceMet( 674 env.DoneWithChangeWatchedFiles(), 675 env.DiagnosticAtRegexp("foo/main.go", `"mod.com/blah"`), 676 ), 677 ) 678 env.RegexpReplace("foo/main.go", `"mod.com/blah"`, `"foo/blah"`) 679 env.Await( 680 EmptyDiagnostics("foo/main.go"), 681 ) 682 }) 683} 684 685func TestNewSymbolInTestVariant(t *testing.T) { 686 const files = ` 687-- go.mod -- 688module mod.com 689 690go 1.12 691-- a/a.go -- 692package a 693 694func bob() {} 695-- a/a_test.go -- 696package a 697 698import "testing" 699 700func TestBob(t *testing.T) { 701 bob() 702} 703` 704 Run(t, files, func(t *testing.T, env *Env) { 705 // Add a new symbol to the package under test and use it in the test 706 // variant. Expect no diagnostics. 707 env.WriteWorkspaceFiles(map[string]string{ 708 "a/a.go": `package a 709 710func bob() {} 711func george() {} 712`, 713 "a/a_test.go": `package a 714 715import "testing" 716 717func TestAll(t *testing.T) { 718 bob() 719 george() 720} 721`, 722 }) 723 env.Await( 724 OnceMet( 725 env.DoneWithChangeWatchedFiles(), 726 NoDiagnostics("a/a.go"), 727 ), 728 OnceMet( 729 env.DoneWithChangeWatchedFiles(), 730 NoDiagnostics("a/a_test.go"), 731 ), 732 ) 733 // Now, add a new file to the test variant and use its symbol in the 734 // original test file. Expect no diagnostics. 735 env.WriteWorkspaceFiles(map[string]string{ 736 "a/a_test.go": `package a 737 738import "testing" 739 740func TestAll(t *testing.T) { 741 bob() 742 george() 743 hi() 744} 745`, 746 "a/a2_test.go": `package a 747 748import "testing" 749 750func hi() {} 751 752func TestSomething(t *testing.T) {} 753`, 754 }) 755 env.Await( 756 OnceMet( 757 env.DoneWithChangeWatchedFiles(), 758 NoDiagnostics("a/a_test.go"), 759 ), 760 OnceMet( 761 env.DoneWithChangeWatchedFiles(), 762 NoDiagnostics("a/a2_test.go"), 763 ), 764 ) 765 }) 766} 767