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