aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/regtest/wrappers.go
diff options
context:
space:
mode:
Diffstat (limited to 'gopls/internal/lsp/regtest/wrappers.go')
-rw-r--r--gopls/internal/lsp/regtest/wrappers.go489
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)
+ }
+}