golang.org/x/tools/gopls@v0.15.3/internal/test/integration/diagnostics/diagnostics_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 diagnostics 6 7 import ( 8 "context" 9 "fmt" 10 "os/exec" 11 "testing" 12 13 "golang.org/x/tools/gopls/internal/hooks" 14 "golang.org/x/tools/gopls/internal/protocol" 15 "golang.org/x/tools/gopls/internal/server" 16 . "golang.org/x/tools/gopls/internal/test/integration" 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/testenv" 21 ) 22 23 func TestMain(m *testing.M) { 24 bug.PanicOnBugs = true 25 Main(m, hooks.Options) 26 } 27 28 // Use mod.com for all go.mod files due to golang/go#35230. 29 const exampleProgram = ` 30 -- go.mod -- 31 module mod.com 32 33 go 1.12 34 -- main.go -- 35 package main 36 37 import "fmt" 38 39 func main() { 40 fmt.Println("Hello World.") 41 }` 42 43 func TestDiagnosticErrorInEditedFile(t *testing.T) { 44 // This test is very basic: start with a clean Go program, make an error, and 45 // get a diagnostic for that error. However, it also demonstrates how to 46 // combine Expectations to await more complex state in the editor. 47 Run(t, exampleProgram, func(t *testing.T, env *Env) { 48 // Deleting the 'n' at the end of Println should generate a single error 49 // diagnostic. 50 env.OpenFile("main.go") 51 env.RegexpReplace("main.go", "Printl(n)", "") 52 env.AfterChange( 53 Diagnostics(env.AtRegexp("main.go", "Printl")), 54 // Assert that this test has sent no error logs to the client. This is not 55 // strictly necessary for testing this regression, but is included here 56 // as an example of using the NoErrorLogs() expectation. Feel free to 57 // delete. 58 NoErrorLogs(), 59 ) 60 }) 61 } 62 63 func TestMissingImportDiagsClearOnFirstFile(t *testing.T) { 64 const onlyMod = ` 65 -- go.mod -- 66 module mod.com 67 68 go 1.12 69 ` 70 Run(t, onlyMod, func(t *testing.T, env *Env) { 71 env.CreateBuffer("main.go", `package main 72 73 func m() { 74 log.Println() 75 } 76 `) 77 env.AfterChange(Diagnostics(env.AtRegexp("main.go", "log"))) 78 env.SaveBuffer("main.go") 79 env.AfterChange(NoDiagnostics(ForFile("main.go"))) 80 }) 81 } 82 83 func TestDiagnosticErrorInNewFile(t *testing.T) { 84 const brokenFile = `package main 85 86 const Foo = "abc 87 ` 88 Run(t, brokenFile, func(t *testing.T, env *Env) { 89 env.CreateBuffer("broken.go", brokenFile) 90 env.AfterChange(Diagnostics(env.AtRegexp("broken.go", "\"abc"))) 91 }) 92 } 93 94 // badPackage contains a duplicate definition of the 'a' const. 95 const badPackage = ` 96 -- go.mod -- 97 module mod.com 98 99 go 1.12 100 -- a.go -- 101 package consts 102 103 const a = 1 104 -- b.go -- 105 package consts 106 107 const a = 2 108 ` 109 110 func TestDiagnosticClearingOnEdit(t *testing.T) { 111 Run(t, badPackage, func(t *testing.T, env *Env) { 112 env.OpenFile("b.go") 113 env.AfterChange( 114 Diagnostics(env.AtRegexp("a.go", "a = 1")), 115 Diagnostics(env.AtRegexp("b.go", "a = 2")), 116 ) 117 118 // Fix the error by editing the const name in b.go to `b`. 119 env.RegexpReplace("b.go", "(a) = 2", "b") 120 env.AfterChange( 121 NoDiagnostics(ForFile("a.go")), 122 NoDiagnostics(ForFile("b.go")), 123 ) 124 }) 125 } 126 127 func TestDiagnosticClearingOnDelete_Issue37049(t *testing.T) { 128 Run(t, badPackage, func(t *testing.T, env *Env) { 129 env.OpenFile("a.go") 130 env.AfterChange( 131 Diagnostics(env.AtRegexp("a.go", "a = 1")), 132 Diagnostics(env.AtRegexp("b.go", "a = 2")), 133 ) 134 env.RemoveWorkspaceFile("b.go") 135 136 env.AfterChange( 137 NoDiagnostics(ForFile("a.go")), 138 NoDiagnostics(ForFile("b.go")), 139 ) 140 }) 141 } 142 143 func TestDiagnosticClearingOnClose(t *testing.T) { 144 Run(t, badPackage, func(t *testing.T, env *Env) { 145 env.CreateBuffer("c.go", `package consts 146 147 const a = 3`) 148 env.AfterChange( 149 Diagnostics(env.AtRegexp("a.go", "a = 1")), 150 Diagnostics(env.AtRegexp("b.go", "a = 2")), 151 Diagnostics(env.AtRegexp("c.go", "a = 3")), 152 ) 153 env.CloseBuffer("c.go") 154 env.AfterChange( 155 Diagnostics(env.AtRegexp("a.go", "a = 1")), 156 Diagnostics(env.AtRegexp("b.go", "a = 2")), 157 NoDiagnostics(ForFile("c.go")), 158 ) 159 }) 160 } 161 162 // Tests golang/go#37978. 163 func TestIssue37978(t *testing.T) { 164 Run(t, exampleProgram, func(t *testing.T, env *Env) { 165 // Create a new workspace-level directory and empty file. 166 env.CreateBuffer("c/c.go", "") 167 168 // Write the file contents with a missing import. 169 env.EditBuffer("c/c.go", protocol.TextEdit{ 170 NewText: `package c 171 172 const a = http.MethodGet 173 `, 174 }) 175 env.AfterChange( 176 Diagnostics(env.AtRegexp("c/c.go", "http.MethodGet")), 177 ) 178 // Save file, which will organize imports, adding the expected import. 179 // Expect the diagnostics to clear. 180 env.SaveBuffer("c/c.go") 181 env.AfterChange( 182 NoDiagnostics(ForFile("c/c.go")), 183 ) 184 }) 185 } 186 187 // Tests golang/go#38878: good a.go, bad a_test.go, remove a_test.go but its errors remain 188 // If the file is open in the editor, this is working as intended 189 // If the file is not open in the editor, the errors go away 190 const test38878 = ` 191 -- go.mod -- 192 module foo 193 194 go 1.12 195 -- a.go -- 196 package x 197 198 // import "fmt" 199 200 func f() {} 201 202 -- a_test.go -- 203 package x 204 205 import "testing" 206 207 func TestA(t *testing.T) { 208 f(3) 209 } 210 ` 211 212 // Tests golang/go#38878: deleting a test file should clear its errors, and 213 // not break the workspace. 214 func TestDeleteTestVariant(t *testing.T) { 215 Run(t, test38878, func(t *testing.T, env *Env) { 216 env.AfterChange(Diagnostics(env.AtRegexp("a_test.go", `f\((3)\)`))) 217 env.RemoveWorkspaceFile("a_test.go") 218 env.AfterChange(NoDiagnostics(ForFile("a_test.go"))) 219 220 // Make sure the test variant has been removed from the workspace by 221 // triggering a metadata load. 222 env.OpenFile("a.go") 223 env.RegexpReplace("a.go", `// import`, "import") 224 env.AfterChange(Diagnostics(env.AtRegexp("a.go", `"fmt"`))) 225 }) 226 } 227 228 // Tests golang/go#38878: deleting a test file on disk while it's still open 229 // should not clear its errors. 230 func TestDeleteTestVariant_DiskOnly(t *testing.T) { 231 Run(t, test38878, func(t *testing.T, env *Env) { 232 env.OpenFile("a_test.go") 233 env.AfterChange(Diagnostics(AtPosition("a_test.go", 5, 3))) 234 env.Sandbox.Workdir.RemoveFile(context.Background(), "a_test.go") 235 env.AfterChange(Diagnostics(AtPosition("a_test.go", 5, 3))) 236 }) 237 } 238 239 // TestNoMod confirms that gopls continues to work when a user adds a go.mod 240 // file to their workspace. 241 func TestNoMod(t *testing.T) { 242 const noMod = ` 243 -- main.go -- 244 package main 245 246 import "mod.com/bob" 247 248 func main() { 249 bob.Hello() 250 } 251 -- bob/bob.go -- 252 package bob 253 254 func Hello() { 255 var x int 256 } 257 ` 258 259 t.Run("manual", func(t *testing.T) { 260 Run(t, noMod, func(t *testing.T, env *Env) { 261 env.OnceMet( 262 InitialWorkspaceLoad, 263 Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)), 264 ) 265 env.CreateBuffer("go.mod", `module mod.com 266 267 go 1.12 268 `) 269 env.SaveBuffer("go.mod") 270 var d protocol.PublishDiagnosticsParams 271 env.AfterChange( 272 NoDiagnostics(ForFile("main.go")), 273 Diagnostics(env.AtRegexp("bob/bob.go", "x")), 274 ReadDiagnostics("bob/bob.go", &d), 275 ) 276 if len(d.Diagnostics) != 1 { 277 t.Fatalf("expected 1 diagnostic, got %v", len(d.Diagnostics)) 278 } 279 }) 280 }) 281 t.Run("initialized", func(t *testing.T) { 282 Run(t, noMod, func(t *testing.T, env *Env) { 283 env.OnceMet( 284 InitialWorkspaceLoad, 285 Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)), 286 ) 287 env.RunGoCommand("mod", "init", "mod.com") 288 env.AfterChange( 289 NoDiagnostics(ForFile("main.go")), 290 Diagnostics(env.AtRegexp("bob/bob.go", "x")), 291 ) 292 }) 293 }) 294 295 t.Run("without workspace module", func(t *testing.T) { 296 WithOptions( 297 Modes(Default), 298 ).Run(t, noMod, func(t *testing.T, env *Env) { 299 env.OnceMet( 300 InitialWorkspaceLoad, 301 Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)), 302 ) 303 if err := env.Sandbox.RunGoCommand(env.Ctx, "", "mod", []string{"init", "mod.com"}, nil, true); err != nil { 304 t.Fatal(err) 305 } 306 env.AfterChange( 307 NoDiagnostics(ForFile("main.go")), 308 Diagnostics(env.AtRegexp("bob/bob.go", "x")), 309 ) 310 }) 311 }) 312 } 313 314 // Tests golang/go#38267. 315 func TestIssue38267(t *testing.T) { 316 const testPackage = ` 317 -- go.mod -- 318 module mod.com 319 320 go 1.12 321 -- lib.go -- 322 package lib 323 324 func Hello(x string) { 325 _ = x 326 } 327 -- lib_test.go -- 328 package lib 329 330 import "testing" 331 332 type testStruct struct{ 333 name string 334 } 335 336 func TestHello(t *testing.T) { 337 testStructs := []*testStruct{ 338 &testStruct{"hello"}, 339 &testStruct{"goodbye"}, 340 } 341 for y := range testStructs { 342 _ = y 343 } 344 } 345 ` 346 347 Run(t, testPackage, func(t *testing.T, env *Env) { 348 env.OpenFile("lib_test.go") 349 env.AfterChange( 350 Diagnostics(AtPosition("lib_test.go", 10, 2)), 351 Diagnostics(AtPosition("lib_test.go", 11, 2)), 352 ) 353 env.OpenFile("lib.go") 354 env.RegexpReplace("lib.go", "_ = x", "var y int") 355 env.AfterChange( 356 Diagnostics(env.AtRegexp("lib.go", "y int")), 357 NoDiagnostics(ForFile("lib_test.go")), 358 ) 359 }) 360 } 361 362 // Tests golang/go#38328. 363 func TestPackageChange_Issue38328(t *testing.T) { 364 const packageChange = ` 365 -- go.mod -- 366 module fake 367 368 go 1.12 369 -- a.go -- 370 package foo 371 func main() {} 372 ` 373 Run(t, packageChange, func(t *testing.T, env *Env) { 374 env.OpenFile("a.go") 375 env.RegexpReplace("a.go", "foo", "foox") 376 env.AfterChange( 377 NoDiagnostics(ForFile("a.go")), 378 ) 379 }) 380 } 381 382 const testPackageWithRequire = ` 383 -- go.mod -- 384 module mod.com 385 386 go 1.12 387 388 require foo.test v1.2.3 389 -- go.sum -- 390 foo.test v1.2.3 h1:TMA+lyd1ck0TqjSFpNe4T6cf/K6TYkoHwOOcMBMjaEw= 391 foo.test v1.2.3/go.mod h1:Ij3kyLIe5lzjycjh13NL8I2gX0quZuTdW0MnmlwGBL4= 392 -- print.go -- 393 package lib 394 395 import ( 396 "fmt" 397 398 "foo.test/bar" 399 ) 400 401 func PrintAnswer() { 402 fmt.Printf("answer: %s", bar.Answer) 403 } 404 ` 405 406 const testPackageWithRequireProxy = ` 407 -- foo.test@v1.2.3/go.mod -- 408 module foo.test 409 410 go 1.12 411 -- foo.test@v1.2.3/bar/const.go -- 412 package bar 413 414 const Answer = 42 415 ` 416 417 func TestResolveDiagnosticWithDownload(t *testing.T) { 418 WithOptions( 419 ProxyFiles(testPackageWithRequireProxy), 420 ).Run(t, testPackageWithRequire, func(t *testing.T, env *Env) { 421 env.OpenFile("print.go") 422 // Check that gopackages correctly loaded this dependency. We should get a 423 // diagnostic for the wrong formatting type. 424 env.AfterChange( 425 Diagnostics( 426 env.AtRegexp("print.go", "fmt.Printf"), 427 WithMessage("wrong type int"), 428 ), 429 ) 430 }) 431 } 432 433 func TestMissingDependency(t *testing.T) { 434 Run(t, testPackageWithRequire, func(t *testing.T, env *Env) { 435 env.OpenFile("print.go") 436 env.Await( 437 // Log messages are asynchronous to other events on the LSP stream, so we 438 // can't use OnceMet or AfterChange here. 439 LogMatching(protocol.Error, "initial workspace load failed", 1, false), 440 ) 441 }) 442 } 443 444 // Tests golang/go#36951. 445 func TestAdHocPackages_Issue36951(t *testing.T) { 446 const adHoc = ` 447 -- b/b.go -- 448 package b 449 450 func Hello() { 451 var x int 452 } 453 ` 454 Run(t, adHoc, func(t *testing.T, env *Env) { 455 env.OpenFile("b/b.go") 456 env.AfterChange( 457 Diagnostics(env.AtRegexp("b/b.go", "x")), 458 ) 459 }) 460 } 461 462 // Tests golang/go#37984: GOPATH should be read from the go command. 463 func TestNoGOPATH_Issue37984(t *testing.T) { 464 const files = ` 465 -- main.go -- 466 package main 467 468 func _() { 469 fmt.Println("Hello World") 470 } 471 ` 472 WithOptions( 473 EnvVars{ 474 "GOPATH": "", 475 "GO111MODULE": "off", 476 }, 477 ).Run(t, files, func(t *testing.T, env *Env) { 478 env.OpenFile("main.go") 479 env.AfterChange(Diagnostics(env.AtRegexp("main.go", "fmt"))) 480 env.SaveBuffer("main.go") 481 env.AfterChange(NoDiagnostics(ForFile("main.go"))) 482 }) 483 } 484 485 // Tests golang/go#38669. 486 func TestEqualInEnv_Issue38669(t *testing.T) { 487 const files = ` 488 -- go.mod -- 489 module mod.com 490 491 go 1.12 492 -- main.go -- 493 package main 494 495 var _ = x.X 496 -- x/x.go -- 497 package x 498 499 var X = 0 500 ` 501 WithOptions( 502 EnvVars{"GOFLAGS": "-tags=foo"}, 503 ).Run(t, files, func(t *testing.T, env *Env) { 504 env.OpenFile("main.go") 505 env.OrganizeImports("main.go") 506 env.AfterChange(NoDiagnostics(ForFile("main.go"))) 507 }) 508 } 509 510 // Tests golang/go#38467. 511 func TestNoSuggestedFixesForGeneratedFiles_Issue38467(t *testing.T) { 512 const generated = ` 513 -- go.mod -- 514 module mod.com 515 516 go 1.12 517 -- main.go -- 518 package main 519 520 // Code generated by generator.go. DO NOT EDIT. 521 522 func _() { 523 for i, _ := range []string{} { 524 _ = i 525 } 526 } 527 ` 528 Run(t, generated, func(t *testing.T, env *Env) { 529 env.OpenFile("main.go") 530 var d protocol.PublishDiagnosticsParams 531 env.AfterChange( 532 Diagnostics(AtPosition("main.go", 5, 8)), 533 ReadDiagnostics("main.go", &d), 534 ) 535 if fixes := env.GetQuickFixes("main.go", d.Diagnostics); len(fixes) != 0 { 536 t.Errorf("got quick fixes %v, wanted none", fixes) 537 } 538 }) 539 } 540 541 // Expect a module/GOPATH error if there is an error in the file at startup. 542 // Tests golang/go#37279. 543 func TestBrokenWorkspace_OutsideModule(t *testing.T) { 544 const noModule = ` 545 -- a.go -- 546 package foo 547 548 import "mod.com/hello" 549 550 func f() { 551 hello.Goodbye() 552 } 553 ` 554 Run(t, noModule, func(t *testing.T, env *Env) { 555 env.OpenFile("a.go") 556 env.AfterChange( 557 // AdHoc views are not critical errors, but their missing import 558 // diagnostics should specifically mention GOROOT or GOPATH (and not 559 // modules). 560 NoOutstandingWork(nil), 561 Diagnostics( 562 env.AtRegexp("a.go", `"mod.com`), 563 WithMessage("GOROOT or GOPATH"), 564 ), 565 ) 566 // Deleting the import dismisses the warning. 567 env.RegexpReplace("a.go", `import "mod.com/hello"`, "") 568 env.AfterChange( 569 NoOutstandingWork(IgnoreTelemetryPromptWork), 570 ) 571 }) 572 } 573 574 func TestNonGoFolder(t *testing.T) { 575 const files = ` 576 -- hello.txt -- 577 hi mom 578 ` 579 for _, go111module := range []string{"on", "off", ""} { 580 t.Run(fmt.Sprintf("GO111MODULE_%v", go111module), func(t *testing.T) { 581 WithOptions( 582 EnvVars{"GO111MODULE": go111module}, 583 ).Run(t, files, func(t *testing.T, env *Env) { 584 env.OnceMet( 585 InitialWorkspaceLoad, 586 NoOutstandingWork(IgnoreTelemetryPromptWork), 587 ) 588 }) 589 }) 590 } 591 } 592 593 // Tests the repro case from golang/go#38602. Diagnostics are now handled properly, 594 // which blocks type checking. 595 func TestConflictingMainPackageErrors(t *testing.T) { 596 const collision = ` 597 -- x/x.go -- 598 package x 599 600 import "x/hello" 601 602 func Hello() { 603 hello.HiThere() 604 } 605 -- x/main.go -- 606 package main 607 608 func main() { 609 fmt.Println("") 610 } 611 ` 612 WithOptions( 613 InGOPATH(), 614 EnvVars{"GO111MODULE": "off"}, 615 ).Run(t, collision, func(t *testing.T, env *Env) { 616 env.OpenFile("x/x.go") 617 env.AfterChange( 618 Diagnostics(env.AtRegexp("x/x.go", `^`), WithMessage("found packages main (main.go) and x (x.go)")), 619 Diagnostics(env.AtRegexp("x/main.go", `^`), WithMessage("found packages main (main.go) and x (x.go)")), 620 ) 621 622 // We don't recover cleanly from the errors without good overlay support. 623 if testenv.Go1Point() >= 16 { 624 env.RegexpReplace("x/x.go", `package x`, `package main`) 625 env.AfterChange( 626 Diagnostics(env.AtRegexp("x/main.go", `fmt`)), 627 ) 628 } 629 }) 630 } 631 632 const ardanLabsProxy = ` 633 -- github.com/ardanlabs/conf@v1.2.3/go.mod -- 634 module github.com/ardanlabs/conf 635 636 go 1.12 637 -- github.com/ardanlabs/conf@v1.2.3/conf.go -- 638 package conf 639 640 var ErrHelpWanted error 641 ` 642 643 // Test for golang/go#38211. 644 func Test_Issue38211(t *testing.T) { 645 const ardanLabs = ` 646 -- go.mod -- 647 module mod.com 648 649 go 1.14 650 -- main.go -- 651 package main 652 653 import "github.com/ardanlabs/conf" 654 655 func main() { 656 _ = conf.ErrHelpWanted 657 } 658 ` 659 WithOptions( 660 ProxyFiles(ardanLabsProxy), 661 ).Run(t, ardanLabs, func(t *testing.T, env *Env) { 662 // Expect a diagnostic with a suggested fix to add 663 // "github.com/ardanlabs/conf" to the go.mod file. 664 env.OpenFile("go.mod") 665 env.OpenFile("main.go") 666 var d protocol.PublishDiagnosticsParams 667 env.AfterChange( 668 Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`)), 669 ReadDiagnostics("main.go", &d), 670 ) 671 env.ApplyQuickFixes("main.go", d.Diagnostics) 672 env.SaveBuffer("go.mod") 673 env.AfterChange( 674 NoDiagnostics(ForFile("main.go")), 675 ) 676 // Comment out the line that depends on conf and expect a 677 // diagnostic and a fix to remove the import. 678 env.RegexpReplace("main.go", "_ = conf.ErrHelpWanted", "//_ = conf.ErrHelpWanted") 679 env.AfterChange( 680 Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`)), 681 ) 682 env.SaveBuffer("main.go") 683 // Expect a diagnostic and fix to remove the dependency in the go.mod. 684 env.AfterChange( 685 NoDiagnostics(ForFile("main.go")), 686 Diagnostics(env.AtRegexp("go.mod", "require github.com/ardanlabs/conf"), WithMessage("not used in this module")), 687 ReadDiagnostics("go.mod", &d), 688 ) 689 env.ApplyQuickFixes("go.mod", d.Diagnostics) 690 env.SaveBuffer("go.mod") 691 env.AfterChange( 692 NoDiagnostics(ForFile("go.mod")), 693 ) 694 // Uncomment the lines and expect a new diagnostic for the import. 695 env.RegexpReplace("main.go", "//_ = conf.ErrHelpWanted", "_ = conf.ErrHelpWanted") 696 env.SaveBuffer("main.go") 697 env.AfterChange( 698 Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`)), 699 ) 700 }) 701 } 702 703 // Test for golang/go#38207. 704 func TestNewModule_Issue38207(t *testing.T) { 705 const emptyFile = ` 706 -- go.mod -- 707 module mod.com 708 709 go 1.12 710 -- main.go -- 711 ` 712 WithOptions( 713 ProxyFiles(ardanLabsProxy), 714 ).Run(t, emptyFile, func(t *testing.T, env *Env) { 715 env.CreateBuffer("main.go", `package main 716 717 import "github.com/ardanlabs/conf" 718 719 func main() { 720 _ = conf.ErrHelpWanted 721 } 722 `) 723 env.SaveBuffer("main.go") 724 var d protocol.PublishDiagnosticsParams 725 env.AfterChange( 726 Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`), WithMessage("no required module")), 727 ReadDiagnostics("main.go", &d), 728 ) 729 env.ApplyQuickFixes("main.go", d.Diagnostics) 730 env.AfterChange( 731 NoDiagnostics(ForFile("main.go")), 732 ) 733 }) 734 } 735 736 // Test for golang/go#36960. 737 func TestNewFileBadImports_Issue36960(t *testing.T) { 738 const simplePackage = ` 739 -- go.mod -- 740 module mod.com 741 742 go 1.14 743 -- a/a1.go -- 744 package a 745 746 import "fmt" 747 748 func _() { 749 fmt.Println("hi") 750 } 751 ` 752 Run(t, simplePackage, func(t *testing.T, env *Env) { 753 env.OpenFile("a/a1.go") 754 env.CreateBuffer("a/a2.go", ``) 755 env.SaveBufferWithoutActions("a/a2.go") 756 env.AfterChange( 757 NoDiagnostics(ForFile("a/a1.go")), 758 ) 759 env.EditBuffer("a/a2.go", fake.NewEdit(0, 0, 0, 0, `package a`)) 760 env.AfterChange( 761 NoDiagnostics(ForFile("a/a1.go")), 762 ) 763 }) 764 } 765 766 // This test tries to replicate the workflow of a user creating a new x test. 767 // It also tests golang/go#39315. 768 func TestManuallyCreatingXTest(t *testing.T) { 769 // Create a package that already has a test variant (in-package test). 770 const testVariant = ` 771 -- go.mod -- 772 module mod.com 773 774 go 1.15 775 -- hello/hello.go -- 776 package hello 777 778 func Hello() { 779 var x int 780 } 781 -- hello/hello_test.go -- 782 package hello 783 784 import "testing" 785 786 func TestHello(t *testing.T) { 787 var x int 788 Hello() 789 } 790 ` 791 Run(t, testVariant, func(t *testing.T, env *Env) { 792 // Open the file, triggering the workspace load. 793 // There are errors in the code to ensure all is working as expected. 794 env.OpenFile("hello/hello.go") 795 env.AfterChange( 796 Diagnostics(env.AtRegexp("hello/hello.go", "x")), 797 Diagnostics(env.AtRegexp("hello/hello_test.go", "x")), 798 ) 799 800 // Create an empty file with the intention of making it an x test. 801 // This resembles a typical flow in an editor like VS Code, in which 802 // a user would create an empty file and add content, saving 803 // intermittently. 804 // TODO(rstambler): There might be more edge cases here, as file 805 // content can be added incrementally. 806 env.CreateBuffer("hello/hello_x_test.go", ``) 807 808 // Save the empty file (no actions since formatting will fail). 809 env.SaveBufferWithoutActions("hello/hello_x_test.go") 810 811 // Add the content. The missing import is for the package under test. 812 env.EditBuffer("hello/hello_x_test.go", fake.NewEdit(0, 0, 0, 0, `package hello_test 813 814 import ( 815 "testing" 816 ) 817 818 func TestHello(t *testing.T) { 819 hello.Hello() 820 } 821 `)) 822 // Expect a diagnostic for the missing import. Save, which should 823 // trigger import organization. The diagnostic should clear. 824 env.AfterChange( 825 Diagnostics(env.AtRegexp("hello/hello_x_test.go", "hello.Hello")), 826 ) 827 env.SaveBuffer("hello/hello_x_test.go") 828 env.AfterChange( 829 NoDiagnostics(ForFile("hello/hello_x_test.go")), 830 ) 831 }) 832 } 833 834 // Reproduce golang/go#40690. 835 func TestCreateOnlyXTest(t *testing.T) { 836 const mod = ` 837 -- go.mod -- 838 module mod.com 839 840 go 1.12 841 -- foo/foo.go -- 842 package foo 843 -- foo/bar_test.go -- 844 ` 845 Run(t, mod, func(t *testing.T, env *Env) { 846 env.OpenFile("foo/bar_test.go") 847 env.EditBuffer("foo/bar_test.go", fake.NewEdit(0, 0, 0, 0, "package foo")) 848 env.Await(env.DoneWithChange()) 849 env.RegexpReplace("foo/bar_test.go", "package foo", `package foo_test 850 851 import "testing" 852 853 func TestX(t *testing.T) { 854 var x int 855 } 856 `) 857 env.AfterChange( 858 Diagnostics(env.AtRegexp("foo/bar_test.go", "x")), 859 ) 860 }) 861 } 862 863 func TestChangePackageName(t *testing.T) { 864 const mod = ` 865 -- go.mod -- 866 module mod.com 867 868 go 1.12 869 -- foo/foo.go -- 870 package foo 871 -- foo/bar_test.go -- 872 package foo_ 873 ` 874 Run(t, mod, func(t *testing.T, env *Env) { 875 env.OpenFile("foo/bar_test.go") 876 env.AfterChange() 877 env.RegexpReplace("foo/bar_test.go", "package foo_", "package foo_test") 878 env.AfterChange( 879 NoDiagnostics(ForFile("foo/bar_test.go")), 880 NoDiagnostics(ForFile("foo/foo.go")), 881 ) 882 }) 883 } 884 885 func TestIgnoredFiles(t *testing.T) { 886 const ws = ` 887 -- go.mod -- 888 module mod.com 889 890 go 1.12 891 -- _foo/x.go -- 892 package x 893 894 var _ = foo.Bar 895 ` 896 Run(t, ws, func(t *testing.T, env *Env) { 897 env.OpenFile("_foo/x.go") 898 env.AfterChange( 899 NoDiagnostics(ForFile("_foo/x.go")), 900 ) 901 }) 902 } 903 904 // Partially reproduces golang/go#38977, moving a file between packages. 905 // It also gets hit by some go command bug fixed in 1.15, but we don't 906 // care about that so much here. 907 func TestDeletePackage(t *testing.T) { 908 const ws = ` 909 -- go.mod -- 910 module mod.com 911 912 go 1.15 913 -- a/a.go -- 914 package a 915 916 const A = 1 917 918 -- b/b.go -- 919 package b 920 921 import "mod.com/a" 922 923 const B = a.A 924 925 -- c/c.go -- 926 package c 927 928 import "mod.com/a" 929 930 const C = a.A 931 ` 932 Run(t, ws, func(t *testing.T, env *Env) { 933 env.OpenFile("b/b.go") 934 env.Await(env.DoneWithOpen()) 935 // Delete c/c.go, the only file in package c. 936 env.RemoveWorkspaceFile("c/c.go") 937 938 // We should still get diagnostics for files that exist. 939 env.RegexpReplace("b/b.go", `a.A`, "a.Nonexistant") 940 env.AfterChange( 941 Diagnostics(env.AtRegexp("b/b.go", `Nonexistant`)), 942 ) 943 }) 944 } 945 946 // This is a copy of the scenario_default/quickfix_empty_files.txt test from 947 // govim. Reproduces golang/go#39646. 948 func TestQuickFixEmptyFiles(t *testing.T) { 949 const mod = ` 950 -- go.mod -- 951 module mod.com 952 953 go 1.12 954 ` 955 // To fully recreate the govim tests, we create files by inserting 956 // a newline, adding to the file, and then deleting the newline. 957 // Wait for each event to process to avoid cancellations and force 958 // package loads. 959 writeGoVim := func(env *Env, name, content string) { 960 env.WriteWorkspaceFile(name, "") 961 env.Await(env.DoneWithChangeWatchedFiles()) 962 963 env.CreateBuffer(name, "\n") 964 env.Await(env.DoneWithOpen()) 965 966 env.EditBuffer(name, fake.NewEdit(1, 0, 1, 0, content)) 967 env.Await(env.DoneWithChange()) 968 969 env.EditBuffer(name, fake.NewEdit(0, 0, 1, 0, "")) 970 env.Await(env.DoneWithChange()) 971 } 972 973 const p = `package p; func DoIt(s string) {};` 974 const main = `package main 975 976 import "mod.com/p" 977 978 func main() { 979 p.DoIt(5) 980 } 981 ` 982 // A simple version of the test that reproduces most of the problems it 983 // exposes. 984 t.Run("short", func(t *testing.T) { 985 Run(t, mod, func(t *testing.T, env *Env) { 986 writeGoVim(env, "p/p.go", p) 987 writeGoVim(env, "main.go", main) 988 env.AfterChange( 989 Diagnostics(env.AtRegexp("main.go", "5")), 990 ) 991 }) 992 }) 993 994 // A full version that replicates the whole flow of the test. 995 t.Run("full", func(t *testing.T) { 996 Run(t, mod, func(t *testing.T, env *Env) { 997 writeGoVim(env, "p/p.go", p) 998 writeGoVim(env, "main.go", main) 999 writeGoVim(env, "p/p_test.go", `package p 1000 1001 import "testing" 1002 1003 func TestDoIt(t *testing.T) { 1004 DoIt(5) 1005 } 1006 `) 1007 writeGoVim(env, "p/x_test.go", `package p_test 1008 1009 import ( 1010 "testing" 1011 1012 "mod.com/p" 1013 ) 1014 1015 func TestDoIt(t *testing.T) { 1016 p.DoIt(5) 1017 } 1018 `) 1019 env.AfterChange( 1020 Diagnostics(env.AtRegexp("main.go", "5")), 1021 Diagnostics(env.AtRegexp("p/p_test.go", "5")), 1022 Diagnostics(env.AtRegexp("p/x_test.go", "5")), 1023 ) 1024 env.RegexpReplace("p/p.go", "s string", "i int") 1025 env.AfterChange( 1026 NoDiagnostics(ForFile("main.go")), 1027 NoDiagnostics(ForFile("p/p_test.go")), 1028 NoDiagnostics(ForFile("p/x_test.go")), 1029 ) 1030 }) 1031 }) 1032 } 1033 1034 func TestSingleFile(t *testing.T) { 1035 const mod = ` 1036 -- go.mod -- 1037 module mod.com 1038 1039 go 1.13 1040 -- a/a.go -- 1041 package a 1042 1043 func _() { 1044 var x int 1045 } 1046 ` 1047 WithOptions( 1048 // Empty workspace folders. 1049 WorkspaceFolders(), 1050 ).Run(t, mod, func(t *testing.T, env *Env) { 1051 env.OpenFile("a/a.go") 1052 env.AfterChange( 1053 Diagnostics(env.AtRegexp("a/a.go", "x")), 1054 ) 1055 }) 1056 } 1057 1058 // Reproduces the case described in 1059 // https://github.com/golang/go/issues/39296#issuecomment-652058883. 1060 func TestPkgm(t *testing.T) { 1061 const basic = ` 1062 -- go.mod -- 1063 module mod.com 1064 1065 go 1.15 1066 -- foo/foo.go -- 1067 package foo 1068 1069 import "fmt" 1070 1071 func Foo() { 1072 fmt.Println("") 1073 } 1074 ` 1075 Run(t, basic, func(t *testing.T, env *Env) { 1076 env.WriteWorkspaceFile("foo/foo_test.go", `package main 1077 1078 func main() { 1079 1080 }`) 1081 env.OpenFile("foo/foo_test.go") 1082 env.RegexpReplace("foo/foo_test.go", `package main`, `package foo`) 1083 env.AfterChange(NoDiagnostics(ForFile("foo/foo.go"))) 1084 }) 1085 } 1086 1087 func TestClosingBuffer(t *testing.T) { 1088 const basic = ` 1089 -- go.mod -- 1090 module mod.com 1091 1092 go 1.14 1093 -- main.go -- 1094 package main 1095 1096 func main() {} 1097 ` 1098 Run(t, basic, func(t *testing.T, env *Env) { 1099 env.Editor.CreateBuffer(env.Ctx, "foo.go", `package main`) 1100 env.AfterChange() 1101 env.CloseBuffer("foo.go") 1102 env.AfterChange(NoLogMatching(protocol.Info, "packages=0")) 1103 }) 1104 } 1105 1106 // Reproduces golang/go#38424. 1107 func TestCutAndPaste(t *testing.T) { 1108 const basic = ` 1109 -- go.mod -- 1110 module mod.com 1111 1112 go 1.14 1113 -- main2.go -- 1114 package main 1115 ` 1116 Run(t, basic, func(t *testing.T, env *Env) { 1117 env.CreateBuffer("main.go", "") 1118 env.Await(env.DoneWithOpen()) 1119 1120 env.SaveBufferWithoutActions("main.go") 1121 env.Await(env.DoneWithSave(), env.DoneWithChangeWatchedFiles()) 1122 1123 env.EditBuffer("main.go", fake.NewEdit(0, 0, 0, 0, `package main 1124 1125 func main() { 1126 } 1127 `)) 1128 env.Await(env.DoneWithChange()) 1129 1130 env.SaveBuffer("main.go") 1131 env.Await(env.DoneWithSave(), env.DoneWithChangeWatchedFiles()) 1132 1133 env.EditBuffer("main.go", fake.NewEdit(0, 0, 4, 0, "")) 1134 env.Await(env.DoneWithChange()) 1135 1136 env.EditBuffer("main.go", fake.NewEdit(0, 0, 0, 0, `package main 1137 1138 func main() { 1139 var x int 1140 } 1141 `)) 1142 env.AfterChange( 1143 Diagnostics(env.AtRegexp("main.go", "x")), 1144 ) 1145 }) 1146 } 1147 1148 // Reproduces golang/go#39763. 1149 func TestInvalidPackageName(t *testing.T) { 1150 const pkgDefault = ` 1151 -- go.mod -- 1152 module mod.com 1153 1154 go 1.12 1155 -- main.go -- 1156 package default 1157 1158 func main() {} 1159 ` 1160 Run(t, pkgDefault, func(t *testing.T, env *Env) { 1161 env.OpenFile("main.go") 1162 env.AfterChange( 1163 Diagnostics( 1164 env.AtRegexp("main.go", "default"), 1165 WithMessage("expected 'IDENT'"), 1166 ), 1167 ) 1168 }) 1169 } 1170 1171 // This test verifies that the workspace scope is effectively limited to the 1172 // workspace folder, if expandWorkspaceToModule is set. 1173 func TestExpandWorkspaceToModule(t *testing.T) { 1174 const mod = ` 1175 -- go.mod -- 1176 module mod.com 1177 1178 go 1.12 1179 -- a/main.go -- 1180 package main 1181 1182 func main() {} 1183 -- main.go -- 1184 package main 1185 1186 func main() { 1187 var x int 1188 } 1189 ` 1190 WithOptions( 1191 WorkspaceFolders("a"), 1192 ).Run(t, mod, func(t *testing.T, env *Env) { 1193 env.OpenFile("a/main.go") 1194 env.AfterChange( 1195 Diagnostics(env.AtRegexp("main.go", "x")), 1196 ) 1197 }) 1198 WithOptions( 1199 WorkspaceFolders("a"), 1200 Settings{"expandWorkspaceToModule": false}, 1201 ).Run(t, mod, func(t *testing.T, env *Env) { 1202 env.OpenFile("a/main.go") 1203 env.AfterChange( 1204 NoDiagnostics(ForFile("main.go")), 1205 ) 1206 }) 1207 } 1208 1209 // This test verifies that the workspace scope is effectively limited to the 1210 // set of active modules. 1211 // 1212 // We should not get diagnostics or file watching patterns for paths outside of 1213 // the active workspace. 1214 func TestWorkspaceModules(t *testing.T) { 1215 const mod = ` 1216 -- go.work -- 1217 go 1.18 1218 1219 use a 1220 -- a/go.mod -- 1221 module mod.com/a 1222 1223 go 1.12 1224 -- a/a.go -- 1225 package a 1226 1227 func _() { 1228 var x int 1229 } 1230 -- b/go.mod -- 1231 module mod.com/b 1232 1233 go 1.18 1234 ` 1235 WithOptions( 1236 Settings{ 1237 "subdirWatchPatterns": "on", 1238 }, 1239 ).Run(t, mod, func(t *testing.T, env *Env) { 1240 env.OpenFile("a/a.go") 1241 // Writing this file may cause the snapshot to 'know' about the file b, but 1242 // that shouldn't cause it to watch the 'b' directory. 1243 env.WriteWorkspaceFile("b/b.go", `package b 1244 1245 func _() { 1246 var x int 1247 } 1248 `) 1249 env.AfterChange( 1250 Diagnostics(env.AtRegexp("a/a.go", "x")), 1251 NoDiagnostics(ForFile("b/b.go")), 1252 FileWatchMatching("a$"), 1253 NoFileWatchMatching("b$"), 1254 ) 1255 }) 1256 } 1257 1258 func TestSimplifyCompositeLitDiagnostic(t *testing.T) { 1259 const files = ` 1260 -- go.mod -- 1261 module mod.com 1262 1263 go 1.12 1264 -- main.go -- 1265 package main 1266 1267 import "fmt" 1268 1269 type t struct { 1270 msg string 1271 } 1272 1273 func main() { 1274 x := []t{t{"msg"}} 1275 fmt.Println(x) 1276 } 1277 ` 1278 1279 WithOptions( 1280 Settings{"staticcheck": true}, 1281 ).Run(t, files, func(t *testing.T, env *Env) { 1282 env.OpenFile("main.go") 1283 var d protocol.PublishDiagnosticsParams 1284 env.AfterChange( 1285 Diagnostics(env.AtRegexp("main.go", `t{"msg"}`), WithMessage("redundant type")), 1286 ReadDiagnostics("main.go", &d), 1287 ) 1288 if tags := d.Diagnostics[0].Tags; len(tags) == 0 || tags[0] != protocol.Unnecessary { 1289 t.Errorf("wanted Unnecessary tag on diagnostic, got %v", tags) 1290 } 1291 env.ApplyQuickFixes("main.go", d.Diagnostics) 1292 env.AfterChange(NoDiagnostics(ForFile("main.go"))) 1293 }) 1294 } 1295 1296 // Test some secondary diagnostics 1297 func TestSecondaryDiagnostics(t *testing.T) { 1298 const dir = ` 1299 -- go.mod -- 1300 module mod.com 1301 1302 go 1.12 1303 -- main.go -- 1304 package main 1305 func main() { 1306 panic("not here") 1307 } 1308 -- other.go -- 1309 package main 1310 func main() {} 1311 ` 1312 Run(t, dir, func(t *testing.T, env *Env) { 1313 env.OpenFile("main.go") 1314 env.OpenFile("other.go") 1315 var mainDiags, otherDiags protocol.PublishDiagnosticsParams 1316 env.AfterChange( 1317 ReadDiagnostics("main.go", &mainDiags), 1318 ReadDiagnostics("other.go", &otherDiags), 1319 ) 1320 if len(mainDiags.Diagnostics) != 1 { 1321 t.Fatalf("main.go, got %d diagnostics, expected 1", len(mainDiags.Diagnostics)) 1322 } 1323 keep := mainDiags.Diagnostics[0] 1324 if len(otherDiags.Diagnostics) != 1 { 1325 t.Fatalf("other.go: got %d diagnostics, expected 1", len(otherDiags.Diagnostics)) 1326 } 1327 if len(otherDiags.Diagnostics[0].RelatedInformation) != 1 { 1328 t.Fatalf("got %d RelatedInformations, expected 1", len(otherDiags.Diagnostics[0].RelatedInformation)) 1329 } 1330 // check that the RelatedInformation matches the error from main.go 1331 c := otherDiags.Diagnostics[0].RelatedInformation[0] 1332 if c.Location.Range != keep.Range { 1333 t.Errorf("locations don't match. Got %v expected %v", c.Location.Range, keep.Range) 1334 } 1335 }) 1336 } 1337 1338 func TestOrphanedFiles(t *testing.T) { 1339 const files = ` 1340 -- go.mod -- 1341 module mod.com 1342 1343 go 1.12 1344 -- a/a.go -- 1345 package a 1346 1347 func main() { 1348 var x int 1349 } 1350 -- a/a_exclude.go -- 1351 // +build exclude 1352 1353 package a 1354 1355 func _() { 1356 var x int 1357 } 1358 ` 1359 Run(t, files, func(t *testing.T, env *Env) { 1360 env.OpenFile("a/a.go") 1361 env.AfterChange( 1362 Diagnostics(env.AtRegexp("a/a.go", "x")), 1363 ) 1364 env.OpenFile("a/a_exclude.go") 1365 1366 loadOnce := LogMatching(protocol.Info, "query=.*file=.*a_exclude.go", 1, false) 1367 1368 // can't use OnceMet or AfterChange as logs are async 1369 env.Await(loadOnce) 1370 // ...but ensure that the change has been fully processed before editing. 1371 // Otherwise, there may be a race where the snapshot is cloned before all 1372 // state changes resulting from the load have been processed 1373 // (golang/go#61521). 1374 env.AfterChange() 1375 1376 // Check that orphaned files are not reloaded, by making a change in 1377 // a.go file and confirming that the workspace diagnosis did not reload 1378 // a_exclude.go. 1379 // 1380 // This is racy (but fails open) because logs are asynchronous to other LSP 1381 // operations. There's a chance gopls _did_ log, and we just haven't seen 1382 // it yet. 1383 env.RegexpReplace("a/a.go", "package a", "package a // arbitrary comment") 1384 env.AfterChange(loadOnce) 1385 }) 1386 } 1387 1388 func TestEnableAllExperiments(t *testing.T) { 1389 // Before the oldest supported Go version, gopls sends a warning to upgrade 1390 // Go, which fails the expectation below. 1391 testenv.NeedsGo1Point(t, goversion.OldestSupported()) 1392 1393 const mod = ` 1394 -- go.mod -- 1395 module mod.com 1396 1397 go 1.12 1398 -- main.go -- 1399 package main 1400 1401 import "bytes" 1402 1403 func b(c bytes.Buffer) { 1404 _ = 1 1405 } 1406 ` 1407 WithOptions( 1408 Settings{"allExperiments": true}, 1409 ).Run(t, mod, func(t *testing.T, env *Env) { 1410 // Confirm that the setting doesn't cause any warnings. 1411 env.OnceMet( 1412 InitialWorkspaceLoad, 1413 NoShownMessage(""), // empty substring to match any message 1414 ) 1415 }) 1416 } 1417 1418 func TestSwig(t *testing.T) { 1419 if _, err := exec.LookPath("swig"); err != nil { 1420 t.Skip("skipping test: swig not available") 1421 } 1422 if _, err := exec.LookPath("g++"); err != nil { 1423 t.Skip("skipping test: g++ not available") 1424 } 1425 1426 const mod = ` 1427 -- go.mod -- 1428 module mod.com 1429 1430 go 1.12 1431 -- pkg/simple/export_swig.go -- 1432 package simple 1433 1434 func ExportSimple(x, y int) int { 1435 return Gcd(x, y) 1436 } 1437 -- pkg/simple/simple.swigcxx -- 1438 %module simple 1439 1440 %inline %{ 1441 extern int gcd(int x, int y) 1442 { 1443 int g; 1444 g = y; 1445 while (x > 0) { 1446 g = x; 1447 x = y % x; 1448 y = g; 1449 } 1450 return g; 1451 } 1452 %} 1453 -- main.go -- 1454 package a 1455 1456 func main() { 1457 var x int 1458 } 1459 ` 1460 Run(t, mod, func(t *testing.T, env *Env) { 1461 env.OnceMet( 1462 InitialWorkspaceLoad, 1463 NoDiagnostics(WithMessage("illegal character U+0023 '#'")), 1464 ) 1465 }) 1466 } 1467 1468 // When foo_test.go is opened, gopls will object to the borked package name. 1469 // This test asserts that when the package name is fixed, gopls will soon after 1470 // have no more complaints about it. 1471 // https://github.com/golang/go/issues/41061 1472 func TestRenamePackage(t *testing.T) { 1473 const proxy = ` 1474 -- example.com@v1.2.3/go.mod -- 1475 module example.com 1476 1477 go 1.12 1478 -- example.com@v1.2.3/blah/blah.go -- 1479 package blah 1480 1481 const Name = "Blah" 1482 -- random.org@v1.2.3/go.mod -- 1483 module random.org 1484 1485 go 1.12 1486 -- random.org@v1.2.3/blah/blah.go -- 1487 package hello 1488 1489 const Name = "Hello" 1490 ` 1491 1492 const contents = ` 1493 -- go.mod -- 1494 module mod.com 1495 1496 go 1.12 1497 -- main.go -- 1498 package main 1499 1500 import "example.com/blah" 1501 1502 func main() { 1503 blah.Hello() 1504 } 1505 -- bob.go -- 1506 package main 1507 -- foo/foo.go -- 1508 package foo 1509 -- foo/foo_test.go -- 1510 package foo_ 1511 ` 1512 1513 WithOptions( 1514 ProxyFiles(proxy), 1515 InGOPATH(), 1516 EnvVars{"GO111MODULE": "off"}, 1517 ).Run(t, contents, func(t *testing.T, env *Env) { 1518 // Simulate typing character by character. 1519 env.OpenFile("foo/foo_test.go") 1520 env.Await(env.DoneWithOpen()) 1521 env.RegexpReplace("foo/foo_test.go", "_", "_t") 1522 env.Await(env.DoneWithChange()) 1523 env.RegexpReplace("foo/foo_test.go", "_t", "_test") 1524 env.AfterChange( 1525 NoDiagnostics(ForFile("foo/foo_test.go")), 1526 NoOutstandingWork(IgnoreTelemetryPromptWork), 1527 ) 1528 }) 1529 } 1530 1531 // TestProgressBarErrors confirms that critical workspace load errors are shown 1532 // and updated via progress reports. 1533 func TestProgressBarErrors(t *testing.T) { 1534 const pkg = ` 1535 -- go.mod -- 1536 modul mod.com 1537 1538 go 1.12 1539 -- main.go -- 1540 package main 1541 ` 1542 Run(t, pkg, func(t *testing.T, env *Env) { 1543 env.OpenFile("go.mod") 1544 env.AfterChange( 1545 OutstandingWork(server.WorkspaceLoadFailure, "unknown directive"), 1546 ) 1547 env.EditBuffer("go.mod", fake.NewEdit(0, 0, 3, 0, `module mod.com 1548 1549 go 1.hello 1550 `)) 1551 // As of golang/go#42529, go.mod changes do not reload the workspace until 1552 // they are saved. 1553 env.SaveBufferWithoutActions("go.mod") 1554 env.AfterChange( 1555 OutstandingWork(server.WorkspaceLoadFailure, "invalid go version"), 1556 ) 1557 env.RegexpReplace("go.mod", "go 1.hello", "go 1.12") 1558 env.SaveBufferWithoutActions("go.mod") 1559 env.AfterChange( 1560 NoOutstandingWork(IgnoreTelemetryPromptWork), 1561 ) 1562 }) 1563 } 1564 1565 func TestDeleteDirectory(t *testing.T) { 1566 const mod = ` 1567 -- bob/bob.go -- 1568 package bob 1569 1570 func Hello() { 1571 var x int 1572 } 1573 -- go.mod -- 1574 module mod.com 1575 -- cmd/main.go -- 1576 package main 1577 1578 import "mod.com/bob" 1579 1580 func main() { 1581 bob.Hello() 1582 } 1583 ` 1584 WithOptions( 1585 Settings{ 1586 // Now that we don't watch subdirs by default (except for VS Code), 1587 // we must explicitly ask gopls to requests subdir watch patterns. 1588 "subdirWatchPatterns": "on", 1589 }, 1590 ).Run(t, mod, func(t *testing.T, env *Env) { 1591 env.OnceMet( 1592 InitialWorkspaceLoad, 1593 FileWatchMatching("bob"), 1594 ) 1595 env.RemoveWorkspaceFile("bob") 1596 env.AfterChange( 1597 Diagnostics(env.AtRegexp("cmd/main.go", `"mod.com/bob"`)), 1598 NoDiagnostics(ForFile("bob/bob.go")), 1599 NoFileWatchMatching("bob"), 1600 ) 1601 }) 1602 } 1603 1604 // Confirms that circular imports are tested and reported. 1605 func TestCircularImports(t *testing.T) { 1606 const mod = ` 1607 -- go.mod -- 1608 module mod.com 1609 1610 go 1.12 1611 -- self/self.go -- 1612 package self 1613 1614 import _ "mod.com/self" 1615 func Hello() {} 1616 -- double/a/a.go -- 1617 package a 1618 1619 import _ "mod.com/double/b" 1620 -- double/b/b.go -- 1621 package b 1622 1623 import _ "mod.com/double/a" 1624 -- triple/a/a.go -- 1625 package a 1626 1627 import _ "mod.com/triple/b" 1628 -- triple/b/b.go -- 1629 package b 1630 1631 import _ "mod.com/triple/c" 1632 -- triple/c/c.go -- 1633 package c 1634 1635 import _ "mod.com/triple/a" 1636 ` 1637 Run(t, mod, func(t *testing.T, env *Env) { 1638 env.OnceMet( 1639 InitialWorkspaceLoad, 1640 Diagnostics(env.AtRegexp("self/self.go", `_ "mod.com/self"`), WithMessage("import cycle not allowed")), 1641 Diagnostics(env.AtRegexp("double/a/a.go", `_ "mod.com/double/b"`), WithMessage("import cycle not allowed")), 1642 Diagnostics(env.AtRegexp("triple/a/a.go", `_ "mod.com/triple/b"`), WithMessage("import cycle not allowed")), 1643 ) 1644 }) 1645 } 1646 1647 // Tests golang/go#46667: deleting a problematic import path should resolve 1648 // import cycle errors. 1649 func TestResolveImportCycle(t *testing.T) { 1650 const mod = ` 1651 -- go.mod -- 1652 module mod.test 1653 1654 go 1.16 1655 -- a/a.go -- 1656 package a 1657 1658 import "mod.test/b" 1659 1660 const A = b.A 1661 const B = 2 1662 -- b/b.go -- 1663 package b 1664 1665 import "mod.test/a" 1666 1667 const A = 1 1668 const B = a.B 1669 ` 1670 Run(t, mod, func(t *testing.T, env *Env) { 1671 env.OpenFile("a/a.go") 1672 env.OpenFile("b/b.go") 1673 env.AfterChange( 1674 // The Go command sometimes tells us about only one of the import cycle 1675 // errors below. Also, sometimes we get an error during type checking 1676 // instead of during list, due to missing metadata. This is likely due to 1677 // a race. 1678 // For robustness of this test, succeed if we get any reasonable error. 1679 // 1680 // TODO(golang/go#52904): we should get *both* of these errors. 1681 // TODO(golang/go#64899): we should always get an import cycle error 1682 // rather than a missing metadata error. 1683 AnyOf( 1684 Diagnostics(env.AtRegexp("a/a.go", `"mod.test/b"`)), 1685 Diagnostics(env.AtRegexp("b/b.go", `"mod.test/a"`)), 1686 ), 1687 ) 1688 env.RegexpReplace("b/b.go", `const B = a\.B`, "") 1689 env.SaveBuffer("b/b.go") 1690 env.AfterChange( 1691 NoDiagnostics(ForFile("a/a.go")), 1692 NoDiagnostics(ForFile("b/b.go")), 1693 ) 1694 }) 1695 } 1696 1697 func TestBadImport(t *testing.T) { 1698 const mod = ` 1699 -- go.mod -- 1700 module mod.com 1701 1702 go 1.12 1703 -- main.go -- 1704 package main 1705 1706 import ( 1707 _ "nosuchpkg" 1708 ) 1709 ` 1710 t.Run("module", func(t *testing.T) { 1711 Run(t, mod, func(t *testing.T, env *Env) { 1712 env.OnceMet( 1713 InitialWorkspaceLoad, 1714 Diagnostics(env.AtRegexp("main.go", `"nosuchpkg"`), WithMessage(`could not import nosuchpkg (no required module provides package "nosuchpkg"`)), 1715 ) 1716 }) 1717 }) 1718 t.Run("GOPATH", func(t *testing.T) { 1719 WithOptions( 1720 InGOPATH(), 1721 EnvVars{"GO111MODULE": "off"}, 1722 Modes(Default), 1723 ).Run(t, mod, func(t *testing.T, env *Env) { 1724 env.OnceMet( 1725 InitialWorkspaceLoad, 1726 Diagnostics(env.AtRegexp("main.go", `"nosuchpkg"`), WithMessage(`cannot find package "nosuchpkg"`)), 1727 ) 1728 }) 1729 }) 1730 } 1731 1732 func TestNestedModules(t *testing.T) { 1733 const proxy = ` 1734 -- nested.com@v1.0.0/go.mod -- 1735 module nested.com 1736 1737 go 1.12 1738 -- nested.com@v1.0.0/hello/hello.go -- 1739 package hello 1740 1741 func Hello() {} 1742 ` 1743 1744 const nested = ` 1745 -- go.mod -- 1746 module mod.com 1747 1748 go 1.12 1749 1750 require nested.com v1.0.0 1751 -- go.sum -- 1752 nested.com v1.0.0 h1:I6spLE4CgFqMdBPc+wTV2asDO2QJ3tU0YAT+jkLeN1I= 1753 nested.com v1.0.0/go.mod h1:ly53UzXQgVjSlV7wicdBB4p8BxfytuGT1Xcyv0ReJfI= 1754 -- main.go -- 1755 package main 1756 1757 import "nested.com/hello" 1758 1759 func main() { 1760 hello.Hello() 1761 } 1762 -- nested/go.mod -- 1763 module nested.com 1764 1765 -- nested/hello/hello.go -- 1766 package hello 1767 1768 func Hello() { 1769 helloHelper() 1770 } 1771 -- nested/hello/hello_helper.go -- 1772 package hello 1773 1774 func helloHelper() {} 1775 ` 1776 WithOptions( 1777 ProxyFiles(proxy), 1778 Modes(Default), 1779 ).Run(t, nested, func(t *testing.T, env *Env) { 1780 // Expect a diagnostic in a nested module. 1781 env.OpenFile("nested/hello/hello.go") 1782 env.AfterChange( 1783 NoDiagnostics(ForFile("nested/hello/hello.go")), 1784 ) 1785 loc := env.GoToDefinition(env.RegexpSearch("nested/hello/hello.go", "helloHelper")) 1786 want := "nested/hello/hello_helper.go" 1787 if got := env.Sandbox.Workdir.URIToPath(loc.URI); got != want { 1788 t.Errorf("Definition() returned %q, want %q", got, want) 1789 } 1790 }) 1791 } 1792 1793 func TestAdHocPackagesReloading(t *testing.T) { 1794 const nomod = ` 1795 -- main.go -- 1796 package main 1797 1798 func main() {} 1799 ` 1800 Run(t, nomod, func(t *testing.T, env *Env) { 1801 env.OpenFile("main.go") 1802 env.RegexpReplace("main.go", "{}", "{ var x int; }") // simulate typing 1803 env.AfterChange(NoLogMatching(protocol.Info, "packages=1")) 1804 }) 1805 } 1806 1807 func TestBuildTagChange(t *testing.T) { 1808 const files = ` 1809 -- go.mod -- 1810 module mod.com 1811 1812 go 1.12 1813 -- foo.go -- 1814 // decoy comment 1815 // +build hidden 1816 // decoy comment 1817 1818 package foo 1819 var Foo = 1 1820 -- bar.go -- 1821 package foo 1822 var Bar = Foo 1823 ` 1824 1825 Run(t, files, func(t *testing.T, env *Env) { 1826 env.OpenFile("foo.go") 1827 env.AfterChange(Diagnostics(env.AtRegexp("bar.go", `Foo`))) 1828 env.RegexpReplace("foo.go", `\+build`, "") 1829 env.AfterChange(NoDiagnostics(ForFile("bar.go"))) 1830 }) 1831 1832 } 1833 1834 func TestIssue44736(t *testing.T) { 1835 const files = ` 1836 -- go.mod -- 1837 module blah.com 1838 1839 go 1.16 1840 -- main.go -- 1841 package main 1842 1843 import "fmt" 1844 1845 func main() { 1846 asdf 1847 fmt.Printf("This is a test %v") 1848 fdas 1849 } 1850 -- other.go -- 1851 package main 1852 1853 ` 1854 Run(t, files, func(t *testing.T, env *Env) { 1855 env.OpenFile("main.go") 1856 env.OpenFile("other.go") 1857 env.AfterChange( 1858 Diagnostics(env.AtRegexp("main.go", "asdf")), 1859 Diagnostics(env.AtRegexp("main.go", "fdas")), 1860 ) 1861 env.SetBufferContent("other.go", "package main\n\nasdf") 1862 // The new diagnostic in other.go should not suppress diagnostics in main.go. 1863 env.AfterChange( 1864 Diagnostics(env.AtRegexp("other.go", "asdf"), WithMessage("expected declaration")), 1865 Diagnostics(env.AtRegexp("main.go", "asdf")), 1866 ) 1867 }) 1868 } 1869 1870 func TestInitialization(t *testing.T) { 1871 const files = ` 1872 -- go.mod -- 1873 module mod.com 1874 1875 go 1.16 1876 -- main.go -- 1877 package main 1878 ` 1879 Run(t, files, func(t *testing.T, env *Env) { 1880 env.OpenFile("go.mod") 1881 env.Await(env.DoneWithOpen()) 1882 env.RegexpReplace("go.mod", "module", "modul") 1883 env.SaveBufferWithoutActions("go.mod") 1884 env.AfterChange( 1885 NoLogMatching(protocol.Error, "initial workspace load failed"), 1886 ) 1887 }) 1888 } 1889 1890 // This test confirms that the view does not reinitialize when a go.mod file is 1891 // opened. 1892 func TestNoReinitialize(t *testing.T) { 1893 const files = ` 1894 -- go.mod -- 1895 module mod.com 1896 1897 go 1.12 1898 -- main.go -- 1899 package main 1900 1901 func main() {} 1902 ` 1903 Run(t, files, func(t *testing.T, env *Env) { 1904 env.OpenFile("go.mod") 1905 env.Await( 1906 // Check that we have only loaded "<dir>/..." once. 1907 // Log messages are asynchronous to other events on the LSP stream, so we 1908 // can't use OnceMet or AfterChange here. 1909 LogMatching(protocol.Info, `.*query=.*\.\.\..*`, 1, false), 1910 ) 1911 }) 1912 } 1913 1914 func TestLangVersion(t *testing.T) { 1915 const files = ` 1916 -- go.mod -- 1917 module mod.com 1918 1919 go 1.12 1920 -- main.go -- 1921 package main 1922 1923 const C = 0b10 1924 ` 1925 Run(t, files, func(t *testing.T, env *Env) { 1926 env.OnceMet( 1927 InitialWorkspaceLoad, 1928 Diagnostics(env.AtRegexp("main.go", `0b10`), WithMessage("go1.13 or later")), 1929 ) 1930 env.WriteWorkspaceFile("go.mod", "module mod.com \n\ngo 1.13\n") 1931 env.AfterChange( 1932 NoDiagnostics(ForFile("main.go")), 1933 ) 1934 }) 1935 } 1936 1937 func TestNoQuickFixForUndeclaredConstraint(t *testing.T) { 1938 const files = ` 1939 -- go.mod -- 1940 module mod.com 1941 1942 go 1.18 1943 -- main.go -- 1944 package main 1945 1946 func F[T C](_ T) { 1947 } 1948 ` 1949 1950 Run(t, files, func(t *testing.T, env *Env) { 1951 var d protocol.PublishDiagnosticsParams 1952 env.OnceMet( 1953 InitialWorkspaceLoad, 1954 Diagnostics(env.AtRegexp("main.go", `C`)), 1955 ReadDiagnostics("main.go", &d), 1956 ) 1957 if fixes := env.GetQuickFixes("main.go", d.Diagnostics); len(fixes) != 0 { 1958 t.Errorf("got quick fixes %v, wanted none", fixes) 1959 } 1960 }) 1961 } 1962 1963 func TestEditGoDirective(t *testing.T) { 1964 const files = ` 1965 -- go.mod -- 1966 module mod.com 1967 1968 go 1.16 1969 -- main.go -- 1970 package main 1971 1972 func F[T any](_ T) { 1973 } 1974 ` 1975 Run(t, files, func(_ *testing.T, env *Env) { // Create a new workspace-level directory and empty file. 1976 var d protocol.PublishDiagnosticsParams 1977 env.OnceMet( 1978 InitialWorkspaceLoad, 1979 Diagnostics(env.AtRegexp("main.go", `T any`), WithMessage("type parameter")), 1980 ReadDiagnostics("main.go", &d), 1981 ) 1982 1983 env.ApplyQuickFixes("main.go", d.Diagnostics) 1984 env.AfterChange( 1985 NoDiagnostics(ForFile("main.go")), 1986 ) 1987 }) 1988 } 1989 1990 func TestEditGoDirectiveWorkspace(t *testing.T) { 1991 const files = ` 1992 -- go.mod -- 1993 module mod.com 1994 1995 go 1.16 1996 -- go.work -- 1997 go 1.18 1998 1999 use . 2000 -- main.go -- 2001 package main 2002 2003 func F[T any](_ T) { 2004 } 2005 ` 2006 Run(t, files, func(_ *testing.T, env *Env) { // Create a new workspace-level directory and empty file. 2007 var d protocol.PublishDiagnosticsParams 2008 2009 // We should have a diagnostic because generics are not supported at 1.16. 2010 env.OnceMet( 2011 InitialWorkspaceLoad, 2012 Diagnostics(env.AtRegexp("main.go", `T any`), WithMessage("type parameter")), 2013 ReadDiagnostics("main.go", &d), 2014 ) 2015 2016 // This diagnostic should have a quick fix to edit the go version. 2017 env.ApplyQuickFixes("main.go", d.Diagnostics) 2018 2019 // Once the edit is applied, the problematic diagnostics should be 2020 // resolved. 2021 env.AfterChange( 2022 NoDiagnostics(ForFile("main.go")), 2023 ) 2024 }) 2025 } 2026 2027 // This test demonstrates that analysis facts are correctly propagated 2028 // across packages. 2029 func TestInterpackageAnalysis(t *testing.T) { 2030 const src = ` 2031 -- go.mod -- 2032 module example.com 2033 -- a/a.go -- 2034 package a 2035 2036 import "example.com/b" 2037 2038 func _() { 2039 new(b.B).Printf("%d", "s") // printf error 2040 } 2041 2042 -- b/b.go -- 2043 package b 2044 2045 import "example.com/c" 2046 2047 type B struct{} 2048 2049 func (B) Printf(format string, args ...interface{}) { 2050 c.MyPrintf(format, args...) 2051 } 2052 2053 -- c/c.go -- 2054 package c 2055 2056 import "fmt" 2057 2058 func MyPrintf(format string, args ...interface{}) { 2059 fmt.Printf(format, args...) 2060 } 2061 ` 2062 Run(t, src, func(t *testing.T, env *Env) { 2063 env.OpenFile("a/a.go") 2064 env.AfterChange( 2065 Diagnostics( 2066 env.AtRegexp("a/a.go", "new.*Printf"), 2067 WithMessage("format %d has arg \"s\" of wrong type string"), 2068 ), 2069 ) 2070 }) 2071 } 2072 2073 // This test ensures that only Analyzers with RunDespiteErrors=true 2074 // are invoked on a package that would not compile, even if the errors 2075 // are distant and localized. 2076 func TestErrorsThatPreventAnalysis(t *testing.T) { 2077 const src = ` 2078 -- go.mod -- 2079 module example.com 2080 -- a/a.go -- 2081 package a 2082 2083 import "fmt" 2084 import "sync" 2085 import _ "example.com/b" 2086 2087 func _() { 2088 // The copylocks analyzer (RunDespiteErrors, FactTypes={}) does run. 2089 var mu sync.Mutex 2090 mu2 := mu // copylocks error, reported 2091 _ = &mu2 2092 2093 // The printf analyzer (!RunDespiteErrors, FactTypes!={}) does not run: 2094 // (c, printf) failed because of type error in c 2095 // (b, printf) and (a, printf) do not run because of failed prerequisites. 2096 fmt.Printf("%d", "s") // printf error, unreported 2097 2098 // The bools analyzer (!RunDespiteErrors, FactTypes={}) does not run: 2099 var cond bool 2100 _ = cond != true && cond != true // bools error, unreported 2101 } 2102 2103 -- b/b.go -- 2104 package b 2105 2106 import _ "example.com/c" 2107 2108 -- c/c.go -- 2109 package c 2110 2111 var _ = 1 / "" // type error 2112 2113 ` 2114 Run(t, src, func(t *testing.T, env *Env) { 2115 var diags protocol.PublishDiagnosticsParams 2116 env.OpenFile("a/a.go") 2117 env.AfterChange( 2118 Diagnostics(env.AtRegexp("a/a.go", "mu2 := (mu)"), WithMessage("assignment copies lock value")), 2119 ReadDiagnostics("a/a.go", &diags)) 2120 2121 // Assert that there were no other diagnostics. 2122 // In particular: 2123 // - "fmt.Printf" does not trigger a [printf] finding; 2124 // - "cond != true" does not trigger a [bools] finding. 2125 // 2126 // We use this check in preference to NoDiagnosticAtRegexp 2127 // as it is robust in case of minor mistakes in the position 2128 // regexp, and because it reports unexpected diagnostics. 2129 if got, want := len(diags.Diagnostics), 1; got != want { 2130 t.Errorf("got %d diagnostics in a/a.go, want %d:", got, want) 2131 for i, diag := range diags.Diagnostics { 2132 t.Logf("Diagnostics[%d] = %+v", i, diag) 2133 } 2134 } 2135 }) 2136 } 2137 2138 // This test demonstrates the deprecated symbol analyzer 2139 // produces deprecation notices with expected severity and tags. 2140 func TestDeprecatedAnalysis(t *testing.T) { 2141 const src = ` 2142 -- go.mod -- 2143 module example.com 2144 -- a/a.go -- 2145 package a 2146 2147 import "example.com/b" 2148 2149 func _() { 2150 new(b.B).Obsolete() // deprecated 2151 } 2152 2153 -- b/b.go -- 2154 package b 2155 2156 type B struct{} 2157 2158 // Deprecated: use New instead. 2159 func (B) Obsolete() {} 2160 2161 func (B) New() {} 2162 ` 2163 Run(t, src, func(t *testing.T, env *Env) { 2164 env.OpenFile("a/a.go") 2165 env.AfterChange( 2166 Diagnostics( 2167 env.AtRegexp("a/a.go", "new.*Obsolete"), 2168 WithMessage("use New instead."), 2169 WithSeverityTags("deprecated", protocol.SeverityHint, []protocol.DiagnosticTag{protocol.Deprecated}), 2170 ), 2171 ) 2172 }) 2173 } 2174 2175 func TestDiagnosticsOnlyOnSaveFile(t *testing.T) { 2176 // This functionality is broken because the new orphaned file diagnostics 2177 // logic wants to publish diagnostics for changed files, independent of any 2178 // snapshot diagnostics pass, and this causes stale diagnostics to be 2179 // invalidated. 2180 // 2181 // We can fix this behavior more correctly by also honoring the 2182 // diagnosticsTrigger in DiagnoseOrphanedFiles, but that would require 2183 // resolving configuration that is independent of the snapshot. In other 2184 // words, we need to figure out which cache.Folder.Options applies to the 2185 // changed file, even if it does not have a snapshot. 2186 t.Skip("temporary skip for golang/go#57979: revisit after zero-config logic is in place") 2187 2188 const onlyMod = ` 2189 -- go.mod -- 2190 module mod.com 2191 2192 go 1.12 2193 -- main.go -- 2194 package main 2195 2196 func main() { 2197 Foo() 2198 } 2199 -- foo.go -- 2200 package main 2201 2202 func Foo() {} 2203 ` 2204 WithOptions( 2205 Settings{ 2206 "diagnosticsTrigger": "Save", 2207 }, 2208 ).Run(t, onlyMod, func(t *testing.T, env *Env) { 2209 env.OpenFile("foo.go") 2210 env.RegexpReplace("foo.go", "(Foo)", "Bar") // Makes reference to Foo undefined/undeclared. 2211 env.AfterChange(NoDiagnostics()) // No diagnostics update until file save. 2212 2213 env.SaveBuffer("foo.go") 2214 // Compiler's error message about undeclared names vary depending on the version, 2215 // but must be explicit about the problematic name. 2216 env.AfterChange(Diagnostics(env.AtRegexp("main.go", "Foo"), WithMessage("Foo"))) 2217 2218 env.OpenFile("main.go") 2219 env.RegexpReplace("main.go", "(Foo)", "Bar") 2220 // No diagnostics update until file save. That results in outdated diagnostic. 2221 env.AfterChange(Diagnostics(env.AtRegexp("main.go", "Bar"), WithMessage("Foo"))) 2222 2223 env.SaveBuffer("main.go") 2224 env.AfterChange(NoDiagnostics()) 2225 }) 2226 }