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