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  }