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	"testing"
9
10	"golang.org/x/tools/internal/lsp"
11	"golang.org/x/tools/internal/lsp/fake"
12	"golang.org/x/tools/internal/lsp/protocol"
13)
14
15func TestEditFile(t *testing.T) {
16	const pkg = `
17-- go.mod --
18module mod.com
19
20go 1.14
21-- a/a.go --
22package a
23
24func _() {
25	var x int
26}
27`
28	// Edit the file when it's *not open* in the workspace, and check that
29	// diagnostics are updated.
30	t.Run("unopened", func(t *testing.T) {
31		runner.Run(t, pkg, func(t *testing.T, env *Env) {
32			env.Await(
33				env.DiagnosticAtRegexp("a/a.go", "x"),
34			)
35			env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`)
36			env.Await(
37				EmptyDiagnostics("a/a.go"),
38			)
39		})
40	})
41
42	// Edit the file when it *is open* in the workspace, and check that
43	// diagnostics are *not* updated.
44	t.Run("opened", func(t *testing.T) {
45		runner.Run(t, pkg, func(t *testing.T, env *Env) {
46			env.OpenFile("a/a.go")
47			env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
48			env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`)
49			env.Await(
50				env.DiagnosticAtRegexp("a/a.go", "x"),
51			)
52		})
53	})
54}
55
56// Edit a dependency on disk and expect a new diagnostic.
57func TestEditDependency(t *testing.T) {
58	const pkg = `
59-- go.mod --
60module mod.com
61
62go 1.14
63-- b/b.go --
64package b
65
66func B() int { return 0 }
67-- a/a.go --
68package a
69
70import (
71	"mod.com/b"
72)
73
74func _() {
75	_ = b.B()
76}
77`
78	runner.Run(t, pkg, func(t *testing.T, env *Env) {
79		env.OpenFile("a/a.go")
80		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
81		env.WriteWorkspaceFile("b/b.go", `package b; func B() {};`)
82		env.Await(
83			env.DiagnosticAtRegexp("a/a.go", "b.B"),
84		)
85	})
86}
87
88// Edit both the current file and one of its dependencies on disk and
89// expect diagnostic changes.
90func TestEditFileAndDependency(t *testing.T) {
91	const pkg = `
92-- go.mod --
93module mod.com
94
95go 1.14
96-- b/b.go --
97package b
98
99func B() int { return 0 }
100-- a/a.go --
101package a
102
103import (
104	"mod.com/b"
105)
106
107func _() {
108	var x int
109	_ = b.B()
110}
111`
112	runner.Run(t, pkg, func(t *testing.T, env *Env) {
113		env.Await(
114			env.DiagnosticAtRegexp("a/a.go", "x"),
115		)
116		env.WriteWorkspaceFiles(map[string]string{
117			"b/b.go": `package b; func B() {};`,
118			"a/a.go": `package a
119
120import "mod.com/b"
121
122func _() {
123	b.B()
124}`,
125		})
126		env.Await(
127			EmptyDiagnostics("a/a.go"),
128			NoDiagnostics("b/b.go"),
129		)
130	})
131}
132
133// Delete a dependency and expect a new diagnostic.
134func TestDeleteDependency(t *testing.T) {
135	const pkg = `
136-- go.mod --
137module mod.com
138
139go 1.14
140-- b/b.go --
141package b
142
143func B() int { return 0 }
144-- a/a.go --
145package a
146
147import (
148	"mod.com/b"
149)
150
151func _() {
152	_ = b.B()
153}
154`
155	runner.Run(t, pkg, func(t *testing.T, env *Env) {
156		env.OpenFile("a/a.go")
157		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
158		env.RemoveWorkspaceFile("b/b.go")
159		env.Await(
160			env.DiagnosticAtRegexp("a/a.go", "\"mod.com/b\""),
161		)
162	})
163}
164
165// Create a dependency on disk and expect the diagnostic to go away.
166func TestCreateDependency(t *testing.T) {
167	const missing = `
168-- go.mod --
169module mod.com
170
171go 1.14
172-- b/b.go --
173package b
174
175func B() int { return 0 }
176-- a/a.go --
177package a
178
179import (
180	"mod.com/c"
181)
182
183func _() {
184	c.C()
185}
186`
187	runner.Run(t, missing, func(t *testing.T, env *Env) {
188		t.Skipf("the initial workspace load fails and never retries")
189
190		env.Await(
191			env.DiagnosticAtRegexp("a/a.go", "\"mod.com/c\""),
192		)
193		env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`)
194		env.Await(
195			EmptyDiagnostics("c/c.go"),
196		)
197	})
198}
199
200// Create a new dependency and add it to the file on disk.
201// This is similar to what might happen if you switch branches.
202func TestCreateAndAddDependency(t *testing.T) {
203	const original = `
204-- go.mod --
205module mod.com
206
207go 1.14
208-- a/a.go --
209package a
210
211func _() {}
212`
213	runner.Run(t, original, func(t *testing.T, env *Env) {
214		env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`)
215		env.WriteWorkspaceFile("a/a.go", `package a; import "mod.com/c"; func _() { c.C() }`)
216		env.Await(
217			NoDiagnostics("a/a.go"),
218		)
219	})
220}
221
222// Create a new file that defines a new symbol, in the same package.
223func TestCreateFile(t *testing.T) {
224	const pkg = `
225-- go.mod --
226module mod.com
227
228go 1.14
229-- a/a.go --
230package a
231
232func _() {
233	hello()
234}
235`
236	runner.Run(t, pkg, func(t *testing.T, env *Env) {
237		env.Await(
238			env.DiagnosticAtRegexp("a/a.go", "hello"),
239		)
240		env.WriteWorkspaceFile("a/a2.go", `package a; func hello() {};`)
241		env.Await(
242			EmptyDiagnostics("a/a.go"),
243		)
244	})
245}
246
247// Add a new method to an interface and implement it.
248// Inspired by the structure of internal/lsp/source and internal/lsp/cache.
249func TestCreateImplementation(t *testing.T) {
250	const pkg = `
251-- go.mod --
252module mod.com
253
254go 1.14
255-- b/b.go --
256package b
257
258type B interface{
259	Hello() string
260}
261
262func SayHello(bee B) {
263	println(bee.Hello())
264}
265-- a/a.go --
266package a
267
268import "mod.com/b"
269
270type X struct {}
271
272func (_ X) Hello() string {
273	return ""
274}
275
276func _() {
277	x := X{}
278	b.SayHello(x)
279}
280`
281	const newMethod = `package b
282type B interface{
283	Hello() string
284	Bye() string
285}
286
287func SayHello(bee B) {
288	println(bee.Hello())
289}`
290	const implementation = `package a
291
292import "mod.com/b"
293
294type X struct {}
295
296func (_ X) Hello() string {
297	return ""
298}
299
300func (_ X) Bye() string {
301	return ""
302}
303
304func _() {
305	x := X{}
306	b.SayHello(x)
307}`
308
309	// Add the new method before the implementation. Expect diagnostics.
310	t.Run("method before implementation", func(t *testing.T) {
311		runner.Run(t, pkg, func(t *testing.T, env *Env) {
312			env.Await(
313				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1),
314			)
315			env.WriteWorkspaceFile("b/b.go", newMethod)
316			env.Await(
317				OnceMet(
318					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
319					DiagnosticAt("a/a.go", 12, 12),
320				),
321			)
322			env.WriteWorkspaceFile("a/a.go", implementation)
323			env.Await(
324				EmptyDiagnostics("a/a.go"),
325			)
326		})
327	})
328	// Add the new implementation before the new method. Expect no diagnostics.
329	t.Run("implementation before method", func(t *testing.T) {
330		runner.Run(t, pkg, func(t *testing.T, env *Env) {
331			env.Await(
332				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1),
333			)
334			env.WriteWorkspaceFile("a/a.go", implementation)
335			env.Await(
336				OnceMet(
337					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
338					NoDiagnostics("a/a.go"),
339				),
340			)
341			env.WriteWorkspaceFile("b/b.go", newMethod)
342			env.Await(
343				NoDiagnostics("a/a.go"),
344			)
345		})
346	})
347	// Add both simultaneously. Expect no diagnostics.
348	t.Run("implementation and method simultaneously", func(t *testing.T) {
349		runner.Run(t, pkg, func(t *testing.T, env *Env) {
350			env.Await(
351				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1),
352			)
353			env.WriteWorkspaceFiles(map[string]string{
354				"a/a.go": implementation,
355				"b/b.go": newMethod,
356			})
357			env.Await(
358				OnceMet(
359					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
360					NoDiagnostics("a/a.go"),
361				),
362				NoDiagnostics("b/b.go"),
363			)
364		})
365	})
366}
367
368// Tests golang/go#38498. Delete a file and then force a reload.
369// Assert that we no longer try to load the file.
370func TestDeleteFiles(t *testing.T) {
371	const pkg = `
372-- go.mod --
373module mod.com
374
375go 1.14
376-- a/a.go --
377package a
378
379func _() {
380	var _ int
381}
382-- a/a_unneeded.go --
383package a
384`
385	t.Run("close then delete", func(t *testing.T) {
386		runner.Run(t, pkg, func(t *testing.T, env *Env) {
387			env.OpenFile("a/a.go")
388			env.OpenFile("a/a_unneeded.go")
389			env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2))
390
391			// Close and delete the open file, mimicking what an editor would do.
392			env.CloseBuffer("a/a_unneeded.go")
393			env.RemoveWorkspaceFile("a/a_unneeded.go")
394			env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")")
395			env.Await(
396				env.DiagnosticAtRegexp("a/a.go", "fmt"),
397			)
398			env.SaveBuffer("a/a.go")
399			env.Await(
400				NoLogMatching(protocol.Info, "a_unneeded.go"),
401				EmptyDiagnostics("a/a.go"),
402			)
403		})
404	})
405
406	t.Run("delete then close", func(t *testing.T) {
407		runner.Run(t, pkg, func(t *testing.T, env *Env) {
408			env.OpenFile("a/a.go")
409			env.OpenFile("a/a_unneeded.go")
410			env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2))
411
412			// Delete and then close the file.
413			env.CloseBuffer("a/a_unneeded.go")
414			env.RemoveWorkspaceFile("a/a_unneeded.go")
415			env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")")
416			env.Await(
417				env.DiagnosticAtRegexp("a/a.go", "fmt"),
418			)
419			env.SaveBuffer("a/a.go")
420			env.Await(
421				NoLogMatching(protocol.Info, "a_unneeded.go"),
422				EmptyDiagnostics("a/a.go"),
423			)
424		})
425	})
426}
427
428// This change reproduces the behavior of switching branches, with multiple
429// files being created and deleted. The key change here is the movement of a
430// symbol from one file to another in a given package through a deletion and
431// creation. To reproduce an issue with metadata invalidation in batched
432// changes, the last change in the batch is an on-disk file change that doesn't
433// require metadata invalidation.
434func TestMoveSymbol(t *testing.T) {
435	const pkg = `
436-- go.mod --
437module mod.com
438
439go 1.14
440-- main.go --
441package main
442
443import "mod.com/a"
444
445func main() {
446	var x int
447	x = a.Hello
448	println(x)
449}
450-- a/a1.go --
451package a
452
453var Hello int
454-- a/a2.go --
455package a
456
457func _() {}
458`
459	runner.Run(t, pkg, func(t *testing.T, env *Env) {
460		env.Await(
461			CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1),
462		)
463		env.ChangeFilesOnDisk([]fake.FileEvent{
464			{
465				Path: "a/a3.go",
466				Content: `package a
467
468var Hello int
469`,
470				ProtocolEvent: protocol.FileEvent{
471					URI:  env.Sandbox.Workdir.URI("a/a3.go"),
472					Type: protocol.Created,
473				},
474			},
475			{
476				Path: "a/a1.go",
477				ProtocolEvent: protocol.FileEvent{
478					URI:  env.Sandbox.Workdir.URI("a/a1.go"),
479					Type: protocol.Deleted,
480				},
481			},
482			{
483				Path:    "a/a2.go",
484				Content: `package a; func _() {};`,
485				ProtocolEvent: protocol.FileEvent{
486					URI:  env.Sandbox.Workdir.URI("a/a2.go"),
487					Type: protocol.Changed,
488				},
489			},
490		})
491		env.Await(
492			OnceMet(
493				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
494				NoDiagnostics("main.go"),
495			),
496		)
497	})
498}
499
500// Reproduce golang/go#40456.
501func TestChangeVersion(t *testing.T) {
502	const proxy = `
503-- example.com@v1.2.3/go.mod --
504module example.com
505
506go 1.12
507-- example.com@v1.2.3/blah/blah.go --
508package blah
509
510const Name = "Blah"
511
512func X(x int) {}
513-- example.com@v1.2.2/go.mod --
514module example.com
515
516go 1.12
517-- example.com@v1.2.2/blah/blah.go --
518package blah
519
520const Name = "Blah"
521
522func X() {}
523-- random.org@v1.2.3/go.mod --
524module random.org
525
526go 1.12
527-- random.org@v1.2.3/blah/blah.go --
528package hello
529
530const Name = "Hello"
531`
532	const mod = `
533-- go.mod --
534module mod.com
535
536go 1.12
537
538require example.com v1.2.2
539-- main.go --
540package main
541
542import "example.com/blah"
543
544func main() {
545	blah.X()
546}
547`
548	withOptions(WithProxyFiles(proxy)).run(t, mod, func(t *testing.T, env *Env) {
549		env.Await(
550			CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1),
551		)
552		env.WriteWorkspaceFiles(map[string]string{
553			"go.mod": `module mod.com
554
555go 1.12
556
557require example.com v1.2.3
558`,
559			"main.go": `package main
560
561import (
562	"example.com/blah"
563)
564
565func main() {
566	blah.X(1)
567}
568`,
569		})
570		env.Await(
571			CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
572			NoDiagnostics("main.go"),
573		)
574	})
575}
576