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 "io" 9 "testing" 10 11 "golang.org/x/tools/internal/lsp/fake" 12 "golang.org/x/tools/internal/lsp/protocol" 13 "golang.org/x/tools/internal/lsp/source" 14 errors "golang.org/x/xerrors" 15) 16 17func (e *Env) ChangeFilesOnDisk(events []fake.FileEvent) { 18 e.T.Helper() 19 if err := e.Sandbox.Workdir.ChangeFilesOnDisk(e.Ctx, events); err != nil { 20 e.T.Fatal(err) 21 } 22} 23 24// RemoveWorkspaceFile deletes a file on disk but does nothing in the 25// editor. It calls t.Fatal on any error. 26func (e *Env) RemoveWorkspaceFile(name string) { 27 e.T.Helper() 28 if err := e.Sandbox.Workdir.RemoveFile(e.Ctx, name); err != nil { 29 e.T.Fatal(err) 30 } 31} 32 33// ReadWorkspaceFile reads a file from the workspace, calling t.Fatal on any 34// error. 35func (e *Env) ReadWorkspaceFile(name string) string { 36 e.T.Helper() 37 content, err := e.Sandbox.Workdir.ReadFile(name) 38 if err != nil { 39 e.T.Fatal(err) 40 } 41 return content 42} 43 44// WriteWorkspaceFile writes a file to disk but does nothing in the editor. 45// It calls t.Fatal on any error. 46func (e *Env) WriteWorkspaceFile(name, content string) { 47 e.T.Helper() 48 if err := e.Sandbox.Workdir.WriteFile(e.Ctx, name, content); err != nil { 49 e.T.Fatal(err) 50 } 51} 52 53// WriteWorkspaceFiles deletes a file on disk but does nothing in the 54// editor. It calls t.Fatal on any error. 55func (e *Env) WriteWorkspaceFiles(files map[string]string) { 56 e.T.Helper() 57 if err := e.Sandbox.Workdir.WriteFiles(e.Ctx, files); err != nil { 58 e.T.Fatal(err) 59 } 60} 61 62// OpenFile opens a file in the editor, calling t.Fatal on any error. 63func (e *Env) OpenFile(name string) { 64 e.T.Helper() 65 if err := e.Editor.OpenFile(e.Ctx, name); err != nil { 66 e.T.Fatal(err) 67 } 68} 69 70// CreateBuffer creates a buffer in the editor, calling t.Fatal on any error. 71func (e *Env) CreateBuffer(name string, content string) { 72 e.T.Helper() 73 if err := e.Editor.CreateBuffer(e.Ctx, name, content); err != nil { 74 e.T.Fatal(err) 75 } 76} 77 78// CloseBuffer closes an editor buffer without saving, calling t.Fatal on any 79// error. 80func (e *Env) CloseBuffer(name string) { 81 e.T.Helper() 82 if err := e.Editor.CloseBuffer(e.Ctx, name); err != nil { 83 e.T.Fatal(err) 84 } 85} 86 87// EditBuffer applies edits to an editor buffer, calling t.Fatal on any error. 88func (e *Env) EditBuffer(name string, edits ...fake.Edit) { 89 e.T.Helper() 90 if err := e.Editor.EditBuffer(e.Ctx, name, edits); err != nil { 91 e.T.Fatal(err) 92 } 93} 94 95func (e *Env) SetBufferContent(name string, content string) { 96 e.T.Helper() 97 if err := e.Editor.SetBufferContent(e.Ctx, name, content); err != nil { 98 e.T.Fatal(err) 99 } 100} 101 102// RegexpRange returns the range of the first match for re in the buffer 103// specified by name, calling t.Fatal on any error. It first searches for the 104// position in open buffers, then in workspace files. 105func (e *Env) RegexpRange(name, re string) (fake.Pos, fake.Pos) { 106 e.T.Helper() 107 start, end, err := e.Editor.RegexpRange(name, re) 108 if err == fake.ErrUnknownBuffer { 109 start, end, err = e.Sandbox.Workdir.RegexpRange(name, re) 110 } 111 if err != nil { 112 e.T.Fatalf("RegexpRange: %v, %v", name, err) 113 } 114 return start, end 115} 116 117// RegexpSearch returns the starting position of the first match for re in the 118// buffer specified by name, calling t.Fatal on any error. It first searches 119// for the position in open buffers, then in workspace files. 120func (e *Env) RegexpSearch(name, re string) fake.Pos { 121 e.T.Helper() 122 pos, err := e.Editor.RegexpSearch(name, re) 123 if err == fake.ErrUnknownBuffer { 124 pos, err = e.Sandbox.Workdir.RegexpSearch(name, re) 125 } 126 if err != nil { 127 e.T.Fatalf("RegexpSearch: %v, %v", name, err) 128 } 129 return pos 130} 131 132// RegexpReplace replaces the first group in the first match of regexpStr with 133// the replace text, calling t.Fatal on any error. 134func (e *Env) RegexpReplace(name, regexpStr, replace string) { 135 e.T.Helper() 136 if err := e.Editor.RegexpReplace(e.Ctx, name, regexpStr, replace); err != nil { 137 e.T.Fatalf("RegexpReplace: %v", err) 138 } 139} 140 141// SaveBuffer saves an editor buffer, calling t.Fatal on any error. 142func (e *Env) SaveBuffer(name string) { 143 e.T.Helper() 144 if err := e.Editor.SaveBuffer(e.Ctx, name); err != nil { 145 e.T.Fatal(err) 146 } 147} 148 149// GoToDefinition goes to definition in the editor, calling t.Fatal on any 150// error. 151func (e *Env) GoToDefinition(name string, pos fake.Pos) (string, fake.Pos) { 152 e.T.Helper() 153 n, p, err := e.Editor.GoToDefinition(e.Ctx, name, pos) 154 if err != nil { 155 e.T.Fatal(err) 156 } 157 return n, p 158} 159 160// Symbol returns symbols matching query 161func (e *Env) Symbol(query string) []fake.SymbolInformation { 162 e.T.Helper() 163 r, err := e.Editor.Symbol(e.Ctx, query) 164 if err != nil { 165 e.T.Fatal(err) 166 } 167 return r 168} 169 170// FormatBuffer formats the editor buffer, calling t.Fatal on any error. 171func (e *Env) FormatBuffer(name string) { 172 e.T.Helper() 173 if err := e.Editor.FormatBuffer(e.Ctx, name); err != nil { 174 e.T.Fatal(err) 175 } 176} 177 178// OrganizeImports processes the source.organizeImports codeAction, calling 179// t.Fatal on any error. 180func (e *Env) OrganizeImports(name string) { 181 e.T.Helper() 182 if err := e.Editor.OrganizeImports(e.Ctx, name); err != nil { 183 e.T.Fatal(err) 184 } 185} 186 187// ApplyQuickFixes processes the quickfix codeAction, calling t.Fatal on any error. 188func (e *Env) ApplyQuickFixes(path string, diagnostics []protocol.Diagnostic) { 189 e.T.Helper() 190 if err := e.Editor.ApplyQuickFixes(e.Ctx, path, nil, diagnostics); err != nil { 191 e.T.Fatal(err) 192 } 193} 194 195// Hover in the editor, calling t.Fatal on any error. 196func (e *Env) Hover(name string, pos fake.Pos) (*protocol.MarkupContent, fake.Pos) { 197 e.T.Helper() 198 c, p, err := e.Editor.Hover(e.Ctx, name, pos) 199 if err != nil { 200 e.T.Fatal(err) 201 } 202 return c, p 203} 204 205func (e *Env) DocumentLink(name string) []protocol.DocumentLink { 206 e.T.Helper() 207 links, err := e.Editor.DocumentLink(e.Ctx, name) 208 if err != nil { 209 e.T.Fatal(err) 210 } 211 return links 212} 213 214func checkIsFatal(t *testing.T, err error) { 215 t.Helper() 216 if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrClosedPipe) { 217 t.Fatal(err) 218 } 219} 220 221// CloseEditor shuts down the editor, calling t.Fatal on any error. 222func (e *Env) CloseEditor() { 223 e.T.Helper() 224 checkIsFatal(e.T, e.Editor.Close(e.Ctx)) 225} 226 227// RunGenerate runs go:generate on the given dir, calling t.Fatal on any error. 228// It waits for the generate command to complete and checks for file changes 229// before returning. 230func (e *Env) RunGenerate(dir string) { 231 e.T.Helper() 232 if err := e.Editor.RunGenerate(e.Ctx, dir); err != nil { 233 e.T.Fatal(err) 234 } 235 e.Await(NoOutstandingWork()) 236 // Ideally the fake.Workspace would handle all synthetic file watching, but 237 // we help it out here as we need to wait for the generate command to 238 // complete before checking the filesystem. 239 e.CheckForFileChanges() 240} 241 242// RunGoCommand runs the given command in the sandbox's default working 243// directory. 244func (e *Env) RunGoCommand(verb string, args ...string) { 245 if err := e.Sandbox.RunGoCommand(e.Ctx, "", verb, args); err != nil { 246 e.T.Fatal(err) 247 } 248} 249 250func (e *Env) DumpGoSum() { 251 e.T.Helper() 252 e.RunGoCommand("list", "-mod=mod", "...") 253 e.T.Log("\n\n-- go.sum --\n" + e.ReadWorkspaceFile("go.sum")) 254 e.T.Fatal("see contents above") 255} 256 257// CheckForFileChanges triggers a manual poll of the workspace for any file 258// changes since creation, or since last polling. It is a workaround for the 259// lack of true file watching support in the fake workspace. 260func (e *Env) CheckForFileChanges() { 261 e.T.Helper() 262 if err := e.Sandbox.Workdir.CheckForFileChanges(e.Ctx); err != nil { 263 e.T.Fatal(err) 264 } 265} 266 267// CodeLens calls textDocument/codeLens for the given path, calling t.Fatal on 268// any error. 269func (e *Env) CodeLens(path string) []protocol.CodeLens { 270 e.T.Helper() 271 lens, err := e.Editor.CodeLens(e.Ctx, path) 272 if err != nil { 273 e.T.Fatal(err) 274 } 275 return lens 276} 277 278// ExecuteCodeLensCommand executes the command for the code lens matching the 279// given command name. 280func (e *Env) ExecuteCodeLensCommand(path string, cmd *source.Command) { 281 lenses := e.CodeLens(path) 282 var lens protocol.CodeLens 283 var found bool 284 for _, l := range lenses { 285 if l.Command.Command == cmd.ID() { 286 lens = l 287 found = true 288 } 289 } 290 if !found { 291 e.T.Fatalf("found no command with the ID %s", cmd.ID()) 292 } 293 if _, err := e.Editor.ExecuteCommand(e.Ctx, &protocol.ExecuteCommandParams{ 294 Command: lens.Command.Command, 295 Arguments: lens.Command.Arguments, 296 }); err != nil { 297 e.T.Fatal(err) 298 } 299} 300 301// References calls textDocument/references for the given path at the given 302// position. 303func (e *Env) References(path string, pos fake.Pos) []protocol.Location { 304 e.T.Helper() 305 locations, err := e.Editor.References(e.Ctx, path, pos) 306 if err != nil { 307 e.T.Fatal(err) 308 } 309 return locations 310} 311 312// Completion executes a completion request on the server. 313func (e *Env) Completion(path string, pos fake.Pos) *protocol.CompletionList { 314 e.T.Helper() 315 completions, err := e.Editor.Completion(e.Ctx, path, pos) 316 if err != nil { 317 e.T.Fatal(err) 318 } 319 return completions 320} 321 322// CodeAction calls testDocument/codeAction for the given path, and calls 323// t.Fatal if there are errors. 324func (e *Env) CodeAction(path string) []protocol.CodeAction { 325 e.T.Helper() 326 actions, err := e.Editor.CodeAction(e.Ctx, path, nil) 327 if err != nil { 328 e.T.Fatal(err) 329 } 330 return actions 331} 332 333func (e *Env) changeConfiguration(t *testing.T, config *fake.EditorConfig) { 334 e.Editor.Config = *config 335 if err := e.Editor.Server.DidChangeConfiguration(e.Ctx, &protocol.DidChangeConfigurationParams{ 336 // gopls currently ignores the Settings field 337 }); err != nil { 338 t.Fatal(err) 339 } 340} 341 342// ChangeEnv modifies the editor environment and reconfigures the LSP client. 343// TODO: extend this to "ChangeConfiguration", once we refactor the way editor 344// configuration is defined. 345func (e *Env) ChangeEnv(overlay map[string]string) { 346 e.T.Helper() 347 // TODO: to be correct, this should probably be synchronized, but right now 348 // configuration is only ever modified synchronously in a regtest, so this 349 // correctness can wait for the previously mentioned refactoring. 350 if e.Editor.Config.Env == nil { 351 e.Editor.Config.Env = make(map[string]string) 352 } 353 for k, v := range overlay { 354 e.Editor.Config.Env[k] = v 355 } 356 var params protocol.DidChangeConfigurationParams 357 if err := e.Editor.Server.DidChangeConfiguration(e.Ctx, ¶ms); err != nil { 358 e.T.Fatal(err) 359 } 360} 361