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) 14 15func TestEditFile(t *testing.T) { 16 const pkg = ` 17-- go.mod -- 18module mod.com 19 20go 1.14 21-- a/a.go -- 22package a 23 24func _() { 25 var x int 26} 27` 28 // Edit the file when it's *not open* in the workspace, and check that 29 // diagnostics are updated. 30 t.Run("unopened", func(t *testing.T) { 31 runner.Run(t, pkg, func(t *testing.T, env *Env) { 32 env.Await( 33 env.DiagnosticAtRegexp("a/a.go", "x"), 34 ) 35 env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`) 36 env.Await( 37 EmptyDiagnostics("a/a.go"), 38 ) 39 }) 40 }) 41 42 // Edit the file when it *is open* in the workspace, and check that 43 // diagnostics are *not* updated. 44 t.Run("opened", func(t *testing.T) { 45 runner.Run(t, pkg, func(t *testing.T, env *Env) { 46 env.OpenFile("a/a.go") 47 env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1)) 48 env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`) 49 env.Await( 50 env.DiagnosticAtRegexp("a/a.go", "x"), 51 ) 52 }) 53 }) 54} 55 56// Edit a dependency on disk and expect a new diagnostic. 57func TestEditDependency(t *testing.T) { 58 const pkg = ` 59-- go.mod -- 60module mod.com 61 62go 1.14 63-- b/b.go -- 64package b 65 66func B() int { return 0 } 67-- a/a.go -- 68package a 69 70import ( 71 "mod.com/b" 72) 73 74func _() { 75 _ = b.B() 76} 77` 78 runner.Run(t, pkg, func(t *testing.T, env *Env) { 79 env.OpenFile("a/a.go") 80 env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1)) 81 env.WriteWorkspaceFile("b/b.go", `package b; func B() {};`) 82 env.Await( 83 env.DiagnosticAtRegexp("a/a.go", "b.B"), 84 ) 85 }) 86} 87 88// Edit both the current file and one of its dependencies on disk and 89// expect diagnostic changes. 90func TestEditFileAndDependency(t *testing.T) { 91 const pkg = ` 92-- go.mod -- 93module mod.com 94 95go 1.14 96-- b/b.go -- 97package b 98 99func B() int { return 0 } 100-- a/a.go -- 101package a 102 103import ( 104 "mod.com/b" 105) 106 107func _() { 108 var x int 109 _ = b.B() 110} 111` 112 runner.Run(t, pkg, func(t *testing.T, env *Env) { 113 env.Await( 114 env.DiagnosticAtRegexp("a/a.go", "x"), 115 ) 116 env.WriteWorkspaceFiles(map[string]string{ 117 "b/b.go": `package b; func B() {};`, 118 "a/a.go": `package a 119 120import "mod.com/b" 121 122func _() { 123 b.B() 124}`, 125 }) 126 env.Await( 127 EmptyDiagnostics("a/a.go"), 128 NoDiagnostics("b/b.go"), 129 ) 130 }) 131} 132 133// Delete a dependency and expect a new diagnostic. 134func TestDeleteDependency(t *testing.T) { 135 const pkg = ` 136-- go.mod -- 137module mod.com 138 139go 1.14 140-- b/b.go -- 141package b 142 143func B() int { return 0 } 144-- a/a.go -- 145package a 146 147import ( 148 "mod.com/b" 149) 150 151func _() { 152 _ = b.B() 153} 154` 155 runner.Run(t, pkg, func(t *testing.T, env *Env) { 156 env.OpenFile("a/a.go") 157 env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1)) 158 env.RemoveWorkspaceFile("b/b.go") 159 env.Await( 160 env.DiagnosticAtRegexp("a/a.go", "\"mod.com/b\""), 161 ) 162 }) 163} 164 165// Create a dependency on disk and expect the diagnostic to go away. 166func TestCreateDependency(t *testing.T) { 167 const missing = ` 168-- go.mod -- 169module mod.com 170 171go 1.14 172-- b/b.go -- 173package b 174 175func B() int { return 0 } 176-- a/a.go -- 177package a 178 179import ( 180 "mod.com/c" 181) 182 183func _() { 184 c.C() 185} 186` 187 runner.Run(t, missing, func(t *testing.T, env *Env) { 188 t.Skipf("the initial workspace load fails and never retries") 189 190 env.Await( 191 env.DiagnosticAtRegexp("a/a.go", "\"mod.com/c\""), 192 ) 193 env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`) 194 env.Await( 195 EmptyDiagnostics("c/c.go"), 196 ) 197 }) 198} 199 200// Create a new dependency and add it to the file on disk. 201// This is similar to what might happen if you switch branches. 202func TestCreateAndAddDependency(t *testing.T) { 203 const original = ` 204-- go.mod -- 205module mod.com 206 207go 1.14 208-- a/a.go -- 209package a 210 211func _() {} 212` 213 runner.Run(t, original, func(t *testing.T, env *Env) { 214 env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`) 215 env.WriteWorkspaceFile("a/a.go", `package a; import "mod.com/c"; func _() { c.C() }`) 216 env.Await( 217 NoDiagnostics("a/a.go"), 218 ) 219 }) 220} 221 222// Create a new file that defines a new symbol, in the same package. 223func TestCreateFile(t *testing.T) { 224 const pkg = ` 225-- go.mod -- 226module mod.com 227 228go 1.14 229-- a/a.go -- 230package a 231 232func _() { 233 hello() 234} 235` 236 runner.Run(t, pkg, func(t *testing.T, env *Env) { 237 env.Await( 238 env.DiagnosticAtRegexp("a/a.go", "hello"), 239 ) 240 env.WriteWorkspaceFile("a/a2.go", `package a; func hello() {};`) 241 env.Await( 242 EmptyDiagnostics("a/a.go"), 243 ) 244 }) 245} 246 247// Add a new method to an interface and implement it. 248// Inspired by the structure of internal/lsp/source and internal/lsp/cache. 249func TestCreateImplementation(t *testing.T) { 250 const pkg = ` 251-- go.mod -- 252module mod.com 253 254go 1.14 255-- b/b.go -- 256package b 257 258type B interface{ 259 Hello() string 260} 261 262func SayHello(bee B) { 263 println(bee.Hello()) 264} 265-- a/a.go -- 266package a 267 268import "mod.com/b" 269 270type X struct {} 271 272func (_ X) Hello() string { 273 return "" 274} 275 276func _() { 277 x := X{} 278 b.SayHello(x) 279} 280` 281 const newMethod = `package b 282type B interface{ 283 Hello() string 284 Bye() string 285} 286 287func SayHello(bee B) { 288 println(bee.Hello()) 289}` 290 const implementation = `package a 291 292import "mod.com/b" 293 294type X struct {} 295 296func (_ X) Hello() string { 297 return "" 298} 299 300func (_ X) Bye() string { 301 return "" 302} 303 304func _() { 305 x := X{} 306 b.SayHello(x) 307}` 308 309 // Add the new method before the implementation. Expect diagnostics. 310 t.Run("method before implementation", func(t *testing.T) { 311 runner.Run(t, pkg, func(t *testing.T, env *Env) { 312 env.Await( 313 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1), 314 ) 315 env.WriteWorkspaceFile("b/b.go", newMethod) 316 env.Await( 317 OnceMet( 318 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1), 319 DiagnosticAt("a/a.go", 12, 12), 320 ), 321 ) 322 env.WriteWorkspaceFile("a/a.go", implementation) 323 env.Await( 324 EmptyDiagnostics("a/a.go"), 325 ) 326 }) 327 }) 328 // Add the new implementation before the new method. Expect no diagnostics. 329 t.Run("implementation before method", func(t *testing.T) { 330 runner.Run(t, pkg, func(t *testing.T, env *Env) { 331 env.Await( 332 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1), 333 ) 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.Await( 351 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1), 352 ) 353 env.WriteWorkspaceFiles(map[string]string{ 354 "a/a.go": implementation, 355 "b/b.go": newMethod, 356 }) 357 env.Await( 358 OnceMet( 359 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1), 360 NoDiagnostics("a/a.go"), 361 ), 362 NoDiagnostics("b/b.go"), 363 ) 364 }) 365 }) 366} 367 368// Tests golang/go#38498. Delete a file and then force a reload. 369// Assert that we no longer try to load the file. 370func TestDeleteFiles(t *testing.T) { 371 const pkg = ` 372-- go.mod -- 373module mod.com 374 375go 1.14 376-- a/a.go -- 377package a 378 379func _() { 380 var _ int 381} 382-- a/a_unneeded.go -- 383package a 384` 385 t.Run("close then delete", func(t *testing.T) { 386 runner.Run(t, pkg, func(t *testing.T, env *Env) { 387 env.OpenFile("a/a.go") 388 env.OpenFile("a/a_unneeded.go") 389 env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2)) 390 391 // Close and delete the open file, mimicking what an editor would do. 392 env.CloseBuffer("a/a_unneeded.go") 393 env.RemoveWorkspaceFile("a/a_unneeded.go") 394 env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")") 395 env.Await( 396 env.DiagnosticAtRegexp("a/a.go", "fmt"), 397 ) 398 env.SaveBuffer("a/a.go") 399 env.Await( 400 NoLogMatching(protocol.Info, "a_unneeded.go"), 401 EmptyDiagnostics("a/a.go"), 402 ) 403 }) 404 }) 405 406 t.Run("delete then close", func(t *testing.T) { 407 runner.Run(t, pkg, func(t *testing.T, env *Env) { 408 env.OpenFile("a/a.go") 409 env.OpenFile("a/a_unneeded.go") 410 env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2)) 411 412 // Delete and then close the file. 413 env.CloseBuffer("a/a_unneeded.go") 414 env.RemoveWorkspaceFile("a/a_unneeded.go") 415 env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")") 416 env.Await( 417 env.DiagnosticAtRegexp("a/a.go", "fmt"), 418 ) 419 env.SaveBuffer("a/a.go") 420 env.Await( 421 NoLogMatching(protocol.Info, "a_unneeded.go"), 422 EmptyDiagnostics("a/a.go"), 423 ) 424 }) 425 }) 426} 427 428// This change reproduces the behavior of switching branches, with multiple 429// files being created and deleted. The key change here is the movement of a 430// symbol from one file to another in a given package through a deletion and 431// creation. To reproduce an issue with metadata invalidation in batched 432// changes, the last change in the batch is an on-disk file change that doesn't 433// require metadata invalidation. 434func TestMoveSymbol(t *testing.T) { 435 const pkg = ` 436-- go.mod -- 437module mod.com 438 439go 1.14 440-- main.go -- 441package main 442 443import "mod.com/a" 444 445func main() { 446 var x int 447 x = a.Hello 448 println(x) 449} 450-- a/a1.go -- 451package a 452 453var Hello int 454-- a/a2.go -- 455package a 456 457func _() {} 458` 459 runner.Run(t, pkg, func(t *testing.T, env *Env) { 460 env.Await( 461 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1), 462 ) 463 env.ChangeFilesOnDisk([]fake.FileEvent{ 464 { 465 Path: "a/a3.go", 466 Content: `package a 467 468var Hello int 469`, 470 ProtocolEvent: protocol.FileEvent{ 471 URI: env.Sandbox.Workdir.URI("a/a3.go"), 472 Type: protocol.Created, 473 }, 474 }, 475 { 476 Path: "a/a1.go", 477 ProtocolEvent: protocol.FileEvent{ 478 URI: env.Sandbox.Workdir.URI("a/a1.go"), 479 Type: protocol.Deleted, 480 }, 481 }, 482 { 483 Path: "a/a2.go", 484 Content: `package a; func _() {};`, 485 ProtocolEvent: protocol.FileEvent{ 486 URI: env.Sandbox.Workdir.URI("a/a2.go"), 487 Type: protocol.Changed, 488 }, 489 }, 490 }) 491 env.Await( 492 OnceMet( 493 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1), 494 NoDiagnostics("main.go"), 495 ), 496 ) 497 }) 498} 499 500// Reproduce golang/go#40456. 501func TestChangeVersion(t *testing.T) { 502 const proxy = ` 503-- example.com@v1.2.3/go.mod -- 504module example.com 505 506go 1.12 507-- example.com@v1.2.3/blah/blah.go -- 508package blah 509 510const Name = "Blah" 511 512func X(x int) {} 513-- example.com@v1.2.2/go.mod -- 514module example.com 515 516go 1.12 517-- example.com@v1.2.2/blah/blah.go -- 518package blah 519 520const Name = "Blah" 521 522func X() {} 523-- random.org@v1.2.3/go.mod -- 524module random.org 525 526go 1.12 527-- random.org@v1.2.3/blah/blah.go -- 528package hello 529 530const Name = "Hello" 531` 532 const mod = ` 533-- go.mod -- 534module mod.com 535 536go 1.12 537 538require example.com v1.2.2 539-- main.go -- 540package main 541 542import "example.com/blah" 543 544func main() { 545 blah.X() 546} 547` 548 withOptions(WithProxyFiles(proxy)).run(t, mod, func(t *testing.T, env *Env) { 549 env.Await( 550 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1), 551 ) 552 env.WriteWorkspaceFiles(map[string]string{ 553 "go.mod": `module mod.com 554 555go 1.12 556 557require example.com v1.2.3 558`, 559 "main.go": `package main 560 561import ( 562 "example.com/blah" 563) 564 565func main() { 566 blah.X(1) 567} 568`, 569 }) 570 env.Await( 571 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1), 572 NoDiagnostics("main.go"), 573 ) 574 }) 575} 576