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