diff options
Diffstat (limited to 'gopls/internal/lsp/regtest/wrappers.go')
-rw-r--r-- | gopls/internal/lsp/regtest/wrappers.go | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/gopls/internal/lsp/regtest/wrappers.go b/gopls/internal/lsp/regtest/wrappers.go new file mode 100644 index 000000000..0315c6de3 --- /dev/null +++ b/gopls/internal/lsp/regtest/wrappers.go @@ -0,0 +1,489 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package regtest + +import ( + "encoding/json" + "path" + + "golang.org/x/tools/gopls/internal/lsp/command" + "golang.org/x/tools/gopls/internal/lsp/fake" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/internal/xcontext" +) + +// RemoveWorkspaceFile deletes a file on disk but does nothing in the +// editor. It calls t.Fatal on any error. +func (e *Env) RemoveWorkspaceFile(name string) { + e.T.Helper() + if err := e.Sandbox.Workdir.RemoveFile(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// ReadWorkspaceFile reads a file from the workspace, calling t.Fatal on any +// error. +func (e *Env) ReadWorkspaceFile(name string) string { + e.T.Helper() + content, err := e.Sandbox.Workdir.ReadFile(name) + if err != nil { + e.T.Fatal(err) + } + return string(content) +} + +// WriteWorkspaceFile writes a file to disk but does nothing in the editor. +// It calls t.Fatal on any error. +func (e *Env) WriteWorkspaceFile(name, content string) { + e.T.Helper() + if err := e.Sandbox.Workdir.WriteFile(e.Ctx, name, content); err != nil { + e.T.Fatal(err) + } +} + +// WriteWorkspaceFiles deletes a file on disk but does nothing in the +// editor. It calls t.Fatal on any error. +func (e *Env) WriteWorkspaceFiles(files map[string]string) { + e.T.Helper() + if err := e.Sandbox.Workdir.WriteFiles(e.Ctx, files); err != nil { + e.T.Fatal(err) + } +} + +// ListFiles lists relative paths to files in the given directory. +// It calls t.Fatal on any error. +func (e *Env) ListFiles(dir string) []string { + e.T.Helper() + paths, err := e.Sandbox.Workdir.ListFiles(dir) + if err != nil { + e.T.Fatal(err) + } + return paths +} + +// OpenFile opens a file in the editor, calling t.Fatal on any error. +func (e *Env) OpenFile(name string) { + e.T.Helper() + if err := e.Editor.OpenFile(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// CreateBuffer creates a buffer in the editor, calling t.Fatal on any error. +func (e *Env) CreateBuffer(name string, content string) { + e.T.Helper() + if err := e.Editor.CreateBuffer(e.Ctx, name, content); err != nil { + e.T.Fatal(err) + } +} + +// BufferText returns the current buffer contents for the file with the given +// relative path, calling t.Fatal if the file is not open in a buffer. +func (e *Env) BufferText(name string) string { + e.T.Helper() + text, ok := e.Editor.BufferText(name) + if !ok { + e.T.Fatalf("buffer %q is not open", name) + } + return text +} + +// CloseBuffer closes an editor buffer without saving, calling t.Fatal on any +// error. +func (e *Env) CloseBuffer(name string) { + e.T.Helper() + if err := e.Editor.CloseBuffer(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// EditBuffer applies edits to an editor buffer, calling t.Fatal on any error. +func (e *Env) EditBuffer(name string, edits ...protocol.TextEdit) { + e.T.Helper() + if err := e.Editor.EditBuffer(e.Ctx, name, edits); err != nil { + e.T.Fatal(err) + } +} + +func (e *Env) SetBufferContent(name string, content string) { + e.T.Helper() + if err := e.Editor.SetBufferContent(e.Ctx, name, content); err != nil { + e.T.Fatal(err) + } +} + +// RegexpSearch returns the starting position of the first match for re in the +// buffer specified by name, calling t.Fatal on any error. It first searches +// for the position in open buffers, then in workspace files. +func (e *Env) RegexpSearch(name, re string) protocol.Location { + e.T.Helper() + loc, err := e.Editor.RegexpSearch(name, re) + if err == fake.ErrUnknownBuffer { + loc, err = e.Sandbox.Workdir.RegexpSearch(name, re) + } + if err != nil { + e.T.Fatalf("RegexpSearch: %v, %v for %q", name, err, re) + } + return loc +} + +// RegexpReplace replaces the first group in the first match of regexpStr with +// the replace text, calling t.Fatal on any error. +func (e *Env) RegexpReplace(name, regexpStr, replace string) { + e.T.Helper() + if err := e.Editor.RegexpReplace(e.Ctx, name, regexpStr, replace); err != nil { + e.T.Fatalf("RegexpReplace: %v", err) + } +} + +// SaveBuffer saves an editor buffer, calling t.Fatal on any error. +func (e *Env) SaveBuffer(name string) { + e.T.Helper() + if err := e.Editor.SaveBuffer(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +func (e *Env) SaveBufferWithoutActions(name string) { + e.T.Helper() + if err := e.Editor.SaveBufferWithoutActions(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// GoToDefinition goes to definition in the editor, calling t.Fatal on any +// error. It returns the path and position of the resulting jump. +func (e *Env) GoToDefinition(loc protocol.Location) protocol.Location { + e.T.Helper() + loc, err := e.Editor.GoToDefinition(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return loc +} + +// FormatBuffer formats the editor buffer, calling t.Fatal on any error. +func (e *Env) FormatBuffer(name string) { + e.T.Helper() + if err := e.Editor.FormatBuffer(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// OrganizeImports processes the source.organizeImports codeAction, calling +// t.Fatal on any error. +func (e *Env) OrganizeImports(name string) { + e.T.Helper() + if err := e.Editor.OrganizeImports(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// ApplyQuickFixes processes the quickfix codeAction, calling t.Fatal on any error. +func (e *Env) ApplyQuickFixes(path string, diagnostics []protocol.Diagnostic) { + e.T.Helper() + loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file + if err := e.Editor.ApplyQuickFixes(e.Ctx, loc, diagnostics); err != nil { + e.T.Fatal(err) + } +} + +// ApplyCodeAction applies the given code action. +func (e *Env) ApplyCodeAction(action protocol.CodeAction) { + e.T.Helper() + if err := e.Editor.ApplyCodeAction(e.Ctx, action); err != nil { + e.T.Fatal(err) + } +} + +// GetQuickFixes returns the available quick fix code actions. +func (e *Env) GetQuickFixes(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction { + e.T.Helper() + loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // zero Range => whole file + actions, err := e.Editor.GetQuickFixes(e.Ctx, loc, diagnostics) + if err != nil { + e.T.Fatal(err) + } + return actions +} + +// Hover in the editor, calling t.Fatal on any error. +func (e *Env) Hover(loc protocol.Location) (*protocol.MarkupContent, protocol.Location) { + e.T.Helper() + c, loc, err := e.Editor.Hover(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return c, loc +} + +func (e *Env) DocumentLink(name string) []protocol.DocumentLink { + e.T.Helper() + links, err := e.Editor.DocumentLink(e.Ctx, name) + if err != nil { + e.T.Fatal(err) + } + return links +} + +func (e *Env) DocumentHighlight(loc protocol.Location) []protocol.DocumentHighlight { + e.T.Helper() + highlights, err := e.Editor.DocumentHighlight(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return highlights +} + +// RunGenerate runs "go generate" in the given dir, calling t.Fatal on any error. +// It waits for the generate command to complete and checks for file changes +// before returning. +func (e *Env) RunGenerate(dir string) { + e.T.Helper() + if err := e.Editor.RunGenerate(e.Ctx, dir); err != nil { + e.T.Fatal(err) + } + e.Await(NoOutstandingWork()) + // Ideally the fake.Workspace would handle all synthetic file watching, but + // we help it out here as we need to wait for the generate command to + // complete before checking the filesystem. + e.CheckForFileChanges() +} + +// RunGoCommand runs the given command in the sandbox's default working +// directory. +func (e *Env) RunGoCommand(verb string, args ...string) { + e.T.Helper() + if err := e.Sandbox.RunGoCommand(e.Ctx, "", verb, args, true); err != nil { + e.T.Fatal(err) + } +} + +// RunGoCommandInDir is like RunGoCommand, but executes in the given +// relative directory of the sandbox. +func (e *Env) RunGoCommandInDir(dir, verb string, args ...string) { + e.T.Helper() + if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, true); err != nil { + e.T.Fatal(err) + } +} + +// GoVersion checks the version of the go command. +// It returns the X in Go 1.X. +func (e *Env) GoVersion() int { + e.T.Helper() + v, err := e.Sandbox.GoVersion(e.Ctx) + if err != nil { + e.T.Fatal(err) + } + return v +} + +// DumpGoSum prints the correct go.sum contents for dir in txtar format, +// for use in creating regtests. +func (e *Env) DumpGoSum(dir string) { + e.T.Helper() + + if err := e.Sandbox.RunGoCommand(e.Ctx, dir, "list", []string{"-mod=mod", "..."}, true); err != nil { + e.T.Fatal(err) + } + sumFile := path.Join(dir, "/go.sum") + e.T.Log("\n\n-- " + sumFile + " --\n" + e.ReadWorkspaceFile(sumFile)) + e.T.Fatal("see contents above") +} + +// CheckForFileChanges triggers a manual poll of the workspace for any file +// changes since creation, or since last polling. It is a workaround for the +// lack of true file watching support in the fake workspace. +func (e *Env) CheckForFileChanges() { + e.T.Helper() + if err := e.Sandbox.Workdir.CheckForFileChanges(e.Ctx); err != nil { + e.T.Fatal(err) + } +} + +// CodeLens calls textDocument/codeLens for the given path, calling t.Fatal on +// any error. +func (e *Env) CodeLens(path string) []protocol.CodeLens { + e.T.Helper() + lens, err := e.Editor.CodeLens(e.Ctx, path) + if err != nil { + e.T.Fatal(err) + } + return lens +} + +// ExecuteCodeLensCommand executes the command for the code lens matching the +// given command name. +func (e *Env) ExecuteCodeLensCommand(path string, cmd command.Command, result interface{}) { + e.T.Helper() + lenses := e.CodeLens(path) + var lens protocol.CodeLens + var found bool + for _, l := range lenses { + if l.Command.Command == cmd.ID() { + lens = l + found = true + } + } + if !found { + e.T.Fatalf("found no command with the ID %s", cmd.ID()) + } + e.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: lens.Command.Command, + Arguments: lens.Command.Arguments, + }, result) +} + +func (e *Env) ExecuteCommand(params *protocol.ExecuteCommandParams, result interface{}) { + e.T.Helper() + response, err := e.Editor.ExecuteCommand(e.Ctx, params) + if err != nil { + e.T.Fatal(err) + } + if result == nil { + return + } + // Hack: The result of an executeCommand request will be unmarshaled into + // maps. Re-marshal and unmarshal into the type we expect. + // + // This could be improved by generating a jsonrpc2 command client from the + // command.Interface, but that should only be done if we're consolidating + // this part of the tsprotocol generation. + data, err := json.Marshal(response) + if err != nil { + e.T.Fatal(err) + } + if err := json.Unmarshal(data, result); err != nil { + e.T.Fatal(err) + } +} + +// InlayHints calls textDocument/inlayHints for the given path, calling t.Fatal on +// any error. +func (e *Env) InlayHints(path string) []protocol.InlayHint { + e.T.Helper() + hints, err := e.Editor.InlayHint(e.Ctx, path) + if err != nil { + e.T.Fatal(err) + } + return hints +} + +// Symbol calls workspace/symbol +func (e *Env) Symbol(query string) []protocol.SymbolInformation { + e.T.Helper() + ans, err := e.Editor.Symbols(e.Ctx, query) + if err != nil { + e.T.Fatal(err) + } + return ans +} + +// References wraps Editor.References, calling t.Fatal on any error. +func (e *Env) References(loc protocol.Location) []protocol.Location { + e.T.Helper() + locations, err := e.Editor.References(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return locations +} + +// Rename wraps Editor.Rename, calling t.Fatal on any error. +func (e *Env) Rename(loc protocol.Location, newName string) { + e.T.Helper() + if err := e.Editor.Rename(e.Ctx, loc, newName); err != nil { + e.T.Fatal(err) + } +} + +// Implementations wraps Editor.Implementations, calling t.Fatal on any error. +func (e *Env) Implementations(loc protocol.Location) []protocol.Location { + e.T.Helper() + locations, err := e.Editor.Implementations(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return locations +} + +// RenameFile wraps Editor.RenameFile, calling t.Fatal on any error. +func (e *Env) RenameFile(oldPath, newPath string) { + e.T.Helper() + if err := e.Editor.RenameFile(e.Ctx, oldPath, newPath); err != nil { + e.T.Fatal(err) + } +} + +// SignatureHelp wraps Editor.SignatureHelp, calling t.Fatal on error +func (e *Env) SignatureHelp(loc protocol.Location) *protocol.SignatureHelp { + e.T.Helper() + sighelp, err := e.Editor.SignatureHelp(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return sighelp +} + +// Completion executes a completion request on the server. +func (e *Env) Completion(loc protocol.Location) *protocol.CompletionList { + e.T.Helper() + completions, err := e.Editor.Completion(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return completions +} + +// AcceptCompletion accepts a completion for the given item at the given +// position. +func (e *Env) AcceptCompletion(loc protocol.Location, item protocol.CompletionItem) { + e.T.Helper() + if err := e.Editor.AcceptCompletion(e.Ctx, loc, item); err != nil { + e.T.Fatal(err) + } +} + +// CodeAction calls testDocument/codeAction for the given path, and calls +// t.Fatal if there are errors. +func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction { + e.T.Helper() + loc := protocol.Location{URI: e.Sandbox.Workdir.URI(path)} // no Range => whole file + actions, err := e.Editor.CodeAction(e.Ctx, loc, diagnostics) + if err != nil { + e.T.Fatal(err) + } + return actions +} + +// ChangeConfiguration updates the editor config, calling t.Fatal on any error. +func (e *Env) ChangeConfiguration(newConfig fake.EditorConfig) { + e.T.Helper() + if err := e.Editor.ChangeConfiguration(e.Ctx, newConfig); err != nil { + e.T.Fatal(err) + } +} + +// ChangeWorkspaceFolders updates the editor workspace folders, calling t.Fatal +// on any error. +func (e *Env) ChangeWorkspaceFolders(newFolders ...string) { + e.T.Helper() + if err := e.Editor.ChangeWorkspaceFolders(e.Ctx, newFolders); err != nil { + e.T.Fatal(err) + } +} + +// Close shuts down the editor session and cleans up the sandbox directory, +// calling t.Error on any error. +func (e *Env) Close() { + ctx := xcontext.Detach(e.Ctx) + if err := e.Editor.Close(ctx); err != nil { + e.T.Errorf("closing editor: %v", err) + } + if err := e.Sandbox.Close(); err != nil { + e.T.Errorf("cleaning up sandbox: %v", err) + } +} |