golang.org/x/tools/gopls@v0.15.3/internal/test/integration/wrappers.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 integration 6 7 import ( 8 "encoding/json" 9 "path" 10 11 "golang.org/x/tools/gopls/internal/protocol" 12 "golang.org/x/tools/gopls/internal/protocol/command" 13 "golang.org/x/tools/gopls/internal/test/integration/fake" 14 "golang.org/x/tools/internal/xcontext" 15 ) 16 17 // RemoveWorkspaceFile deletes a file on disk but does nothing in the 18 // editor. It calls t.Fatal on any error. 19 func (e *Env) RemoveWorkspaceFile(name string) { 20 e.T.Helper() 21 if err := e.Sandbox.Workdir.RemoveFile(e.Ctx, name); err != nil { 22 e.T.Fatal(err) 23 } 24 } 25 26 // ReadWorkspaceFile reads a file from the workspace, calling t.Fatal on any 27 // error. 28 func (e *Env) ReadWorkspaceFile(name string) string { 29 e.T.Helper() 30 content, err := e.Sandbox.Workdir.ReadFile(name) 31 if err != nil { 32 e.T.Fatal(err) 33 } 34 return string(content) 35 } 36 37 // WriteWorkspaceFile writes a file to disk but does nothing in the editor. 38 // It calls t.Fatal on any error. 39 func (e *Env) WriteWorkspaceFile(name, content string) { 40 e.T.Helper() 41 if err := e.Sandbox.Workdir.WriteFile(e.Ctx, name, content); err != nil { 42 e.T.Fatal(err) 43 } 44 } 45 46 // WriteWorkspaceFiles deletes a file on disk but does nothing in the 47 // editor. It calls t.Fatal on any error. 48 func (e *Env) WriteWorkspaceFiles(files map[string]string) { 49 e.T.Helper() 50 if err := e.Sandbox.Workdir.WriteFiles(e.Ctx, files); err != nil { 51 e.T.Fatal(err) 52 } 53 } 54 55 // ListFiles lists relative paths to files in the given directory. 56 // It calls t.Fatal on any error. 57 func (e *Env) ListFiles(dir string) []string { 58 e.T.Helper() 59 paths, err := e.Sandbox.Workdir.ListFiles(dir) 60 if err != nil { 61 e.T.Fatal(err) 62 } 63 return paths 64 } 65 66 // OpenFile opens a file in the editor, calling t.Fatal on any error. 67 func (e *Env) OpenFile(name string) { 68 e.T.Helper() 69 if err := e.Editor.OpenFile(e.Ctx, name); err != nil { 70 e.T.Fatal(err) 71 } 72 } 73 74 // CreateBuffer creates a buffer in the editor, calling t.Fatal on any error. 75 func (e *Env) CreateBuffer(name string, content string) { 76 e.T.Helper() 77 if err := e.Editor.CreateBuffer(e.Ctx, name, content); err != nil { 78 e.T.Fatal(err) 79 } 80 } 81 82 // BufferText returns the current buffer contents for the file with the given 83 // relative path, calling t.Fatal if the file is not open in a buffer. 84 func (e *Env) BufferText(name string) string { 85 e.T.Helper() 86 text, ok := e.Editor.BufferText(name) 87 if !ok { 88 e.T.Fatalf("buffer %q is not open", name) 89 } 90 return text 91 } 92 93 // CloseBuffer closes an editor buffer without saving, calling t.Fatal on any 94 // error. 95 func (e *Env) CloseBuffer(name string) { 96 e.T.Helper() 97 if err := e.Editor.CloseBuffer(e.Ctx, name); err != nil { 98 e.T.Fatal(err) 99 } 100 } 101 102 // EditBuffer applies edits to an editor buffer, calling t.Fatal on any error. 103 func (e *Env) EditBuffer(name string, edits ...protocol.TextEdit) { 104 e.T.Helper() 105 if err := e.Editor.EditBuffer(e.Ctx, name, edits); err != nil { 106 e.T.Fatal(err) 107 } 108 } 109 110 func (e *Env) SetBufferContent(name string, content string) { 111 e.T.Helper() 112 if err := e.Editor.SetBufferContent(e.Ctx, name, content); err != nil { 113 e.T.Fatal(err) 114 } 115 } 116 117 // ReadFile returns the file content for name that applies to the current 118 // editing session: if the file is open, it returns its buffer content, 119 // otherwise it returns on disk content. 120 func (e *Env) FileContent(name string) string { 121 e.T.Helper() 122 text, ok := e.Editor.BufferText(name) 123 if ok { 124 return text 125 } 126 return e.ReadWorkspaceFile(name) 127 } 128 129 // RegexpSearch returns the starting position of the first match for re in the 130 // buffer specified by name, calling t.Fatal on any error. It first searches 131 // for the position in open buffers, then in workspace files. 132 func (e *Env) RegexpSearch(name, re string) protocol.Location { 133 e.T.Helper() 134 loc, err := e.Editor.RegexpSearch(name, re) 135 if err == fake.ErrUnknownBuffer { 136 loc, err = e.Sandbox.Workdir.RegexpSearch(name, re) 137 } 138 if err != nil { 139 e.T.Fatalf("RegexpSearch: %v, %v for %q", name, err, re) 140 } 141 return loc 142 } 143 144 // RegexpReplace replaces the first group in the first match of regexpStr with 145 // the replace text, calling t.Fatal on any error. 146 func (e *Env) RegexpReplace(name, regexpStr, replace string) { 147 e.T.Helper() 148 if err := e.Editor.RegexpReplace(e.Ctx, name, regexpStr, replace); err != nil { 149 e.T.Fatalf("RegexpReplace: %v", err) 150 } 151 } 152 153 // SaveBuffer saves an editor buffer, calling t.Fatal on any error. 154 func (e *Env) SaveBuffer(name string) { 155 e.T.Helper() 156 if err := e.Editor.SaveBuffer(e.Ctx, name); err != nil { 157 e.T.Fatal(err) 158 } 159 } 160 161 func (e *Env) SaveBufferWithoutActions(name string) { 162 e.T.Helper() 163 if err := e.Editor.SaveBufferWithoutActions(e.Ctx, name); err != nil { 164 e.T.Fatal(err) 165 } 166 } 167 168 // GoToDefinition goes to definition in the editor, calling t.Fatal on any 169 // error. It returns the path and position of the resulting jump. 170 // 171 // TODO(rfindley): rename this to just 'Definition'. 172 func (e *Env) GoToDefinition(loc protocol.Location) protocol.Location { 173 e.T.Helper() 174 loc, err := e.Editor.Definition(e.Ctx, loc) 175 if err != nil { 176 e.T.Fatal(err) 177 } 178 return loc 179 } 180 181 func (e *Env) TypeDefinition(loc protocol.Location) protocol.Location { 182 e.T.Helper() 183 loc, err := e.Editor.TypeDefinition(e.Ctx, loc) 184 if err != nil { 185 e.T.Fatal(err) 186 } 187 return loc 188 } 189 190 // FormatBuffer formats the editor buffer, calling t.Fatal on any error. 191 func (e *Env) FormatBuffer(name string) { 192 e.T.Helper() 193 if err := e.Editor.FormatBuffer(e.Ctx, name); err != nil { 194 e.T.Fatal(err) 195 } 196 } 197 198 // OrganizeImports processes the source.organizeImports codeAction, calling 199 // t.Fatal on any error. 200 func (e *Env) OrganizeImports(name string) { 201 e.T.Helper() 202 if err := e.Editor.OrganizeImports(e.Ctx, name); err != nil { 203 e.T.Fatal(err) 204 } 205 } 206 207 // ApplyQuickFixes processes the quickfix codeAction, calling t.Fatal on any error. 208 func (e *Env) ApplyQuickFixes(path string, diagnostics []protocol.Diagnostic) { 209 e.T.Helper() 210 loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file 211 if err := e.Editor.ApplyQuickFixes(e.Ctx, loc, diagnostics); err != nil { 212 e.T.Fatal(err) 213 } 214 } 215 216 // ApplyCodeAction applies the given code action. 217 func (e *Env) ApplyCodeAction(action protocol.CodeAction) { 218 e.T.Helper() 219 if err := e.Editor.ApplyCodeAction(e.Ctx, action); err != nil { 220 e.T.Fatal(err) 221 } 222 } 223 224 // GetQuickFixes returns the available quick fix code actions. 225 func (e *Env) GetQuickFixes(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction { 226 e.T.Helper() 227 loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file 228 actions, err := e.Editor.GetQuickFixes(e.Ctx, loc, diagnostics) 229 if err != nil { 230 e.T.Fatal(err) 231 } 232 return actions 233 } 234 235 // Hover in the editor, calling t.Fatal on any error. 236 func (e *Env) Hover(loc protocol.Location) (*protocol.MarkupContent, protocol.Location) { 237 e.T.Helper() 238 c, loc, err := e.Editor.Hover(e.Ctx, loc) 239 if err != nil { 240 e.T.Fatal(err) 241 } 242 return c, loc 243 } 244 245 func (e *Env) DocumentLink(name string) []protocol.DocumentLink { 246 e.T.Helper() 247 links, err := e.Editor.DocumentLink(e.Ctx, name) 248 if err != nil { 249 e.T.Fatal(err) 250 } 251 return links 252 } 253 254 func (e *Env) DocumentHighlight(loc protocol.Location) []protocol.DocumentHighlight { 255 e.T.Helper() 256 highlights, err := e.Editor.DocumentHighlight(e.Ctx, loc) 257 if err != nil { 258 e.T.Fatal(err) 259 } 260 return highlights 261 } 262 263 // RunGenerate runs "go generate" in the given dir, calling t.Fatal on any error. 264 // It waits for the generate command to complete and checks for file changes 265 // before returning. 266 func (e *Env) RunGenerate(dir string) { 267 e.T.Helper() 268 if err := e.Editor.RunGenerate(e.Ctx, dir); err != nil { 269 e.T.Fatal(err) 270 } 271 e.Await(NoOutstandingWork(IgnoreTelemetryPromptWork)) 272 // Ideally the editor.Workspace would handle all synthetic file watching, but 273 // we help it out here as we need to wait for the generate command to 274 // complete before checking the filesystem. 275 e.CheckForFileChanges() 276 } 277 278 // RunGoCommand runs the given command in the sandbox's default working 279 // directory. 280 func (e *Env) RunGoCommand(verb string, args ...string) { 281 e.T.Helper() 282 if err := e.Sandbox.RunGoCommand(e.Ctx, "", verb, args, nil, true); err != nil { 283 e.T.Fatal(err) 284 } 285 } 286 287 // RunGoCommandInDir is like RunGoCommand, but executes in the given 288 // relative directory of the sandbox. 289 func (e *Env) RunGoCommandInDir(dir, verb string, args ...string) { 290 e.T.Helper() 291 if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, nil, true); err != nil { 292 e.T.Fatal(err) 293 } 294 } 295 296 // RunGoCommandInDirWithEnv is like RunGoCommand, but executes in the given 297 // relative directory of the sandbox with the given additional environment variables. 298 func (e *Env) RunGoCommandInDirWithEnv(dir string, env []string, verb string, args ...string) { 299 e.T.Helper() 300 if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, env, true); err != nil { 301 e.T.Fatal(err) 302 } 303 } 304 305 // GoVersion checks the version of the go command. 306 // It returns the X in Go 1.X. 307 func (e *Env) GoVersion() int { 308 e.T.Helper() 309 v, err := e.Sandbox.GoVersion(e.Ctx) 310 if err != nil { 311 e.T.Fatal(err) 312 } 313 return v 314 } 315 316 // DumpGoSum prints the correct go.sum contents for dir in txtar format, 317 // for use in creating integration tests. 318 func (e *Env) DumpGoSum(dir string) { 319 e.T.Helper() 320 321 if err := e.Sandbox.RunGoCommand(e.Ctx, dir, "list", []string{"-mod=mod", "..."}, nil, true); err != nil { 322 e.T.Fatal(err) 323 } 324 sumFile := path.Join(dir, "/go.sum") 325 e.T.Log("\n\n-- " + sumFile + " --\n" + e.ReadWorkspaceFile(sumFile)) 326 e.T.Fatal("see contents above") 327 } 328 329 // CheckForFileChanges triggers a manual poll of the workspace for any file 330 // changes since creation, or since last polling. It is a workaround for the 331 // lack of true file watching support in the fake workspace. 332 func (e *Env) CheckForFileChanges() { 333 e.T.Helper() 334 if err := e.Sandbox.Workdir.CheckForFileChanges(e.Ctx); err != nil { 335 e.T.Fatal(err) 336 } 337 } 338 339 // CodeLens calls textDocument/codeLens for the given path, calling t.Fatal on 340 // any error. 341 func (e *Env) CodeLens(path string) []protocol.CodeLens { 342 e.T.Helper() 343 lens, err := e.Editor.CodeLens(e.Ctx, path) 344 if err != nil { 345 e.T.Fatal(err) 346 } 347 return lens 348 } 349 350 // ExecuteCodeLensCommand executes the command for the code lens matching the 351 // given command name. 352 func (e *Env) ExecuteCodeLensCommand(path string, cmd command.Command, result interface{}) { 353 e.T.Helper() 354 lenses := e.CodeLens(path) 355 var lens protocol.CodeLens 356 var found bool 357 for _, l := range lenses { 358 if l.Command.Command == cmd.ID() { 359 lens = l 360 found = true 361 } 362 } 363 if !found { 364 e.T.Fatalf("found no command with the ID %s", cmd.ID()) 365 } 366 e.ExecuteCommand(&protocol.ExecuteCommandParams{ 367 Command: lens.Command.Command, 368 Arguments: lens.Command.Arguments, 369 }, result) 370 } 371 372 func (e *Env) ExecuteCommand(params *protocol.ExecuteCommandParams, result interface{}) { 373 e.T.Helper() 374 response, err := e.Editor.ExecuteCommand(e.Ctx, params) 375 if err != nil { 376 e.T.Fatal(err) 377 } 378 if result == nil { 379 return 380 } 381 // Hack: The result of an executeCommand request will be unmarshaled into 382 // maps. Re-marshal and unmarshal into the type we expect. 383 // 384 // This could be improved by generating a jsonrpc2 command client from the 385 // command.Interface, but that should only be done if we're consolidating 386 // this part of the tsprotocol generation. 387 data, err := json.Marshal(response) 388 if err != nil { 389 e.T.Fatal(err) 390 } 391 if err := json.Unmarshal(data, result); err != nil { 392 e.T.Fatal(err) 393 } 394 } 395 396 // Views returns the server's views. 397 func (e *Env) Views() []command.View { 398 var summaries []command.View 399 cmd, err := command.NewViewsCommand("") 400 if err != nil { 401 e.T.Fatal(err) 402 } 403 e.ExecuteCommand(&protocol.ExecuteCommandParams{ 404 Command: cmd.Command, 405 Arguments: cmd.Arguments, 406 }, &summaries) 407 return summaries 408 } 409 410 // StartProfile starts a CPU profile with the given name, using the 411 // gopls.start_profile custom command. It calls t.Fatal on any error. 412 // 413 // The resulting stop function must be called to stop profiling (using the 414 // gopls.stop_profile custom command). 415 func (e *Env) StartProfile() (stop func() string) { 416 // TODO(golang/go#61217): revisit the ergonomics of these command APIs. 417 // 418 // This would be a lot simpler if we generated params constructors. 419 args, err := command.MarshalArgs(command.StartProfileArgs{}) 420 if err != nil { 421 e.T.Fatal(err) 422 } 423 params := &protocol.ExecuteCommandParams{ 424 Command: command.StartProfile.ID(), 425 Arguments: args, 426 } 427 var result command.StartProfileResult 428 e.ExecuteCommand(params, &result) 429 430 return func() string { 431 stopArgs, err := command.MarshalArgs(command.StopProfileArgs{}) 432 if err != nil { 433 e.T.Fatal(err) 434 } 435 stopParams := &protocol.ExecuteCommandParams{ 436 Command: command.StopProfile.ID(), 437 Arguments: stopArgs, 438 } 439 var result command.StopProfileResult 440 e.ExecuteCommand(stopParams, &result) 441 return result.File 442 } 443 } 444 445 // InlayHints calls textDocument/inlayHints for the given path, calling t.Fatal on 446 // any error. 447 func (e *Env) InlayHints(path string) []protocol.InlayHint { 448 e.T.Helper() 449 hints, err := e.Editor.InlayHint(e.Ctx, path) 450 if err != nil { 451 e.T.Fatal(err) 452 } 453 return hints 454 } 455 456 // Symbol calls workspace/symbol 457 func (e *Env) Symbol(query string) []protocol.SymbolInformation { 458 e.T.Helper() 459 ans, err := e.Editor.Symbols(e.Ctx, query) 460 if err != nil { 461 e.T.Fatal(err) 462 } 463 return ans 464 } 465 466 // References wraps Editor.References, calling t.Fatal on any error. 467 func (e *Env) References(loc protocol.Location) []protocol.Location { 468 e.T.Helper() 469 locations, err := e.Editor.References(e.Ctx, loc) 470 if err != nil { 471 e.T.Fatal(err) 472 } 473 return locations 474 } 475 476 // Rename wraps Editor.Rename, calling t.Fatal on any error. 477 func (e *Env) Rename(loc protocol.Location, newName string) { 478 e.T.Helper() 479 if err := e.Editor.Rename(e.Ctx, loc, newName); err != nil { 480 e.T.Fatal(err) 481 } 482 } 483 484 // Implementations wraps Editor.Implementations, calling t.Fatal on any error. 485 func (e *Env) Implementations(loc protocol.Location) []protocol.Location { 486 e.T.Helper() 487 locations, err := e.Editor.Implementations(e.Ctx, loc) 488 if err != nil { 489 e.T.Fatal(err) 490 } 491 return locations 492 } 493 494 // RenameFile wraps Editor.RenameFile, calling t.Fatal on any error. 495 func (e *Env) RenameFile(oldPath, newPath string) { 496 e.T.Helper() 497 if err := e.Editor.RenameFile(e.Ctx, oldPath, newPath); err != nil { 498 e.T.Fatal(err) 499 } 500 } 501 502 // SignatureHelp wraps Editor.SignatureHelp, calling t.Fatal on error 503 func (e *Env) SignatureHelp(loc protocol.Location) *protocol.SignatureHelp { 504 e.T.Helper() 505 sighelp, err := e.Editor.SignatureHelp(e.Ctx, loc) 506 if err != nil { 507 e.T.Fatal(err) 508 } 509 return sighelp 510 } 511 512 // Completion executes a completion request on the server. 513 func (e *Env) Completion(loc protocol.Location) *protocol.CompletionList { 514 e.T.Helper() 515 completions, err := e.Editor.Completion(e.Ctx, loc) 516 if err != nil { 517 e.T.Fatal(err) 518 } 519 return completions 520 } 521 522 // AcceptCompletion accepts a completion for the given item at the given 523 // position. 524 func (e *Env) AcceptCompletion(loc protocol.Location, item protocol.CompletionItem) { 525 e.T.Helper() 526 if err := e.Editor.AcceptCompletion(e.Ctx, loc, item); err != nil { 527 e.T.Fatal(err) 528 } 529 } 530 531 // CodeAction calls textDocument/codeAction for the given path, and calls 532 // t.Fatal if there are errors. 533 func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction { 534 e.T.Helper() 535 loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // no Range => whole file 536 actions, err := e.Editor.CodeAction(e.Ctx, loc, diagnostics) 537 if err != nil { 538 e.T.Fatal(err) 539 } 540 return actions 541 } 542 543 // ChangeConfiguration updates the editor config, calling t.Fatal on any error. 544 func (e *Env) ChangeConfiguration(newConfig fake.EditorConfig) { 545 e.T.Helper() 546 if err := e.Editor.ChangeConfiguration(e.Ctx, newConfig); err != nil { 547 e.T.Fatal(err) 548 } 549 } 550 551 // ChangeWorkspaceFolders updates the editor workspace folders, calling t.Fatal 552 // on any error. 553 func (e *Env) ChangeWorkspaceFolders(newFolders ...string) { 554 e.T.Helper() 555 if err := e.Editor.ChangeWorkspaceFolders(e.Ctx, newFolders); err != nil { 556 e.T.Fatal(err) 557 } 558 } 559 560 // SemanticTokensFull invokes textDocument/semanticTokens/full, calling t.Fatal 561 // on any error. 562 func (e *Env) SemanticTokensFull(path string) []fake.SemanticToken { 563 e.T.Helper() 564 toks, err := e.Editor.SemanticTokensFull(e.Ctx, path) 565 if err != nil { 566 e.T.Fatal(err) 567 } 568 return toks 569 } 570 571 // SemanticTokensRange invokes textDocument/semanticTokens/range, calling t.Fatal 572 // on any error. 573 func (e *Env) SemanticTokensRange(loc protocol.Location) []fake.SemanticToken { 574 e.T.Helper() 575 toks, err := e.Editor.SemanticTokensRange(e.Ctx, loc) 576 if err != nil { 577 e.T.Fatal(err) 578 } 579 return toks 580 } 581 582 // Close shuts down the editor session and cleans up the sandbox directory, 583 // calling t.Error on any error. 584 func (e *Env) Close() { 585 ctx := xcontext.Detach(e.Ctx) 586 if err := e.Editor.Close(ctx); err != nil { 587 e.T.Errorf("closing editor: %v", err) 588 } 589 if err := e.Sandbox.Close(); err != nil { 590 e.T.Errorf("cleaning up sandbox: %v", err) 591 } 592 }