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	"golang.org/x/tools/internal/testenv"
14)
15
16func TestEditFile(t *testing.T) {
17	const pkg = `
18-- go.mod --
19module mod.com
20
21go 1.14
22-- a/a.go --
23package a
24
25func _() {
26	var x int
27}
28`
29	// Edit the file when it's *not open* in the workspace, and check that
30	// diagnostics are updated.
31	t.Run("unopened", func(t *testing.T) {
32		runner.Run(t, pkg, func(t *testing.T, env *Env) {
33			env.Await(
34				env.DiagnosticAtRegexp("a/a.go", "x"),
35			)
36			env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`)
37			env.Await(
38				EmptyDiagnostics("a/a.go"),
39			)
40		})
41	})
42
43	// Edit the file when it *is open* in the workspace, and check that
44	// diagnostics are *not* updated.
45	t.Run("opened", func(t *testing.T) {
46		runner.Run(t, pkg, func(t *testing.T, env *Env) {
47			env.OpenFile("a/a.go")
48			// Insert a trivial edit so that we don't automatically update the buffer
49			// (see CL 267577).
50			env.EditBuffer("a/a.go", fake.NewEdit(0, 0, 0, 0, " "))
51			env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
52			env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`)
53			env.Await(
54				OnceMet(
55					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
56					env.DiagnosticAtRegexp("a/a.go", "x"),
57				))
58		})
59	})
60}
61
62// Edit a dependency on disk and expect a new diagnostic.
63func TestEditDependency(t *testing.T) {
64	const pkg = `
65-- go.mod --
66module mod.com
67
68go 1.14
69-- b/b.go --
70package b
71
72func B() int { return 0 }
73-- a/a.go --
74package a
75
76import (
77	"mod.com/b"
78)
79
80func _() {
81	_ = b.B()
82}
83`
84	runner.Run(t, pkg, func(t *testing.T, env *Env) {
85		env.OpenFile("a/a.go")
86		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
87		env.WriteWorkspaceFile("b/b.go", `package b; func B() {};`)
88		env.Await(
89			env.DiagnosticAtRegexp("a/a.go", "b.B"),
90		)
91	})
92}
93
94// Edit both the current file and one of its dependencies on disk and
95// expect diagnostic changes.
96func TestEditFileAndDependency(t *testing.T) {
97	const pkg = `
98-- go.mod --
99module mod.com
100
101go 1.14
102-- b/b.go --
103package b
104
105func B() int { return 0 }
106-- a/a.go --
107package a
108
109import (
110	"mod.com/b"
111)
112
113func _() {
114	var x int
115	_ = b.B()
116}
117`
118	runner.Run(t, pkg, func(t *testing.T, env *Env) {
119		env.Await(
120			env.DiagnosticAtRegexp("a/a.go", "x"),
121		)
122		env.WriteWorkspaceFiles(map[string]string{
123			"b/b.go": `package b; func B() {};`,
124			"a/a.go": `package a
125
126import "mod.com/b"
127
128func _() {
129	b.B()
130}`,
131		})
132		env.Await(
133			EmptyDiagnostics("a/a.go"),
134			NoDiagnostics("b/b.go"),
135		)
136	})
137}
138
139// Delete a dependency and expect a new diagnostic.
140func TestDeleteDependency(t *testing.T) {
141	const pkg = `
142-- go.mod --
143module mod.com
144
145go 1.14
146-- b/b.go --
147package b
148
149func B() int { return 0 }
150-- a/a.go --
151package a
152
153import (
154	"mod.com/b"
155)
156
157func _() {
158	_ = b.B()
159}
160`
161	runner.Run(t, pkg, func(t *testing.T, env *Env) {
162		env.OpenFile("a/a.go")
163		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
164		env.RemoveWorkspaceFile("b/b.go")
165		env.Await(
166			env.DiagnosticAtRegexp("a/a.go", "\"mod.com/b\""),
167		)
168	})
169}
170
171// Create a dependency on disk and expect the diagnostic to go away.
172func TestCreateDependency(t *testing.T) {
173	const missing = `
174-- go.mod --
175module mod.com
176
177go 1.14
178-- b/b.go --
179package b
180
181func B() int { return 0 }
182-- a/a.go --
183package a
184
185import (
186	"mod.com/c"
187)
188
189func _() {
190	c.C()
191}
192`
193	runner.Run(t, missing, func(t *testing.T, env *Env) {
194		t.Skip("the initial workspace load fails and never retries")
195
196		env.Await(
197			env.DiagnosticAtRegexp("a/a.go", "\"mod.com/c\""),
198		)
199		env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`)
200		env.Await(
201			EmptyDiagnostics("c/c.go"),
202		)
203	})
204}
205
206// Create a new dependency and add it to the file on disk.
207// This is similar to what might happen if you switch branches.
208func TestCreateAndAddDependency(t *testing.T) {
209	const original = `
210-- go.mod --
211module mod.com
212
213go 1.14
214-- a/a.go --
215package a
216
217func _() {}
218`
219	runner.Run(t, original, func(t *testing.T, env *Env) {
220		env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`)
221		env.WriteWorkspaceFile("a/a.go", `package a; import "mod.com/c"; func _() { c.C() }`)
222		env.Await(
223			NoDiagnostics("a/a.go"),
224		)
225	})
226}
227
228// Create a new file that defines a new symbol, in the same package.
229func TestCreateFile(t *testing.T) {
230	const pkg = `
231-- go.mod --
232module mod.com
233
234go 1.14
235-- a/a.go --
236package a
237
238func _() {
239	hello()
240}
241`
242	runner.Run(t, pkg, func(t *testing.T, env *Env) {
243		env.Await(
244			env.DiagnosticAtRegexp("a/a.go", "hello"),
245		)
246		env.WriteWorkspaceFile("a/a2.go", `package a; func hello() {};`)
247		env.Await(
248			EmptyDiagnostics("a/a.go"),
249		)
250	})
251}
252
253// Add a new method to an interface and implement it.
254// Inspired by the structure of internal/lsp/source and internal/lsp/cache.
255func TestCreateImplementation(t *testing.T) {
256	const pkg = `
257-- go.mod --
258module mod.com
259
260go 1.14
261-- b/b.go --
262package b
263
264type B interface{
265	Hello() string
266}
267
268func SayHello(bee B) {
269	println(bee.Hello())
270}
271-- a/a.go --
272package a
273
274import "mod.com/b"
275
276type X struct {}
277
278func (_ X) Hello() string {
279	return ""
280}
281
282func _() {
283	x := X{}
284	b.SayHello(x)
285}
286`
287	const newMethod = `package b
288type B interface{
289	Hello() string
290	Bye() string
291}
292
293func SayHello(bee B) {
294	println(bee.Hello())
295}`
296	const implementation = `package a
297
298import "mod.com/b"
299
300type X struct {}
301
302func (_ X) Hello() string {
303	return ""
304}
305
306func (_ X) Bye() string {
307	return ""
308}
309
310func _() {
311	x := X{}
312	b.SayHello(x)
313}`
314
315	// Add the new method before the implementation. Expect diagnostics.
316	t.Run("method before implementation", func(t *testing.T) {
317		runner.Run(t, pkg, func(t *testing.T, env *Env) {
318			env.WriteWorkspaceFile("b/b.go", newMethod)
319			env.Await(
320				OnceMet(
321					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
322					DiagnosticAt("a/a.go", 12, 12),
323				),
324			)
325			env.WriteWorkspaceFile("a/a.go", implementation)
326			env.Await(
327				EmptyDiagnostics("a/a.go"),
328			)
329		})
330	})
331	// Add the new implementation before the new method. Expect no diagnostics.
332	t.Run("implementation before method", func(t *testing.T) {
333		runner.Run(t, pkg, func(t *testing.T, env *Env) {
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.WriteWorkspaceFiles(map[string]string{
351				"a/a.go": implementation,
352				"b/b.go": newMethod,
353			})
354			env.Await(
355				OnceMet(
356					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
357					NoDiagnostics("a/a.go"),
358				),
359				NoDiagnostics("b/b.go"),
360			)
361		})
362	})
363}
364
365// Tests golang/go#38498. Delete a file and then force a reload.
366// Assert that we no longer try to load the file.
367func TestDeleteFiles(t *testing.T) {
368	const pkg = `
369-- go.mod --
370module mod.com
371
372go 1.14
373-- a/a.go --
374package a
375
376func _() {
377	var _ int
378}
379-- a/a_unneeded.go --
380package a
381`
382	t.Run("close then delete", func(t *testing.T) {
383		withOptions(EditorConfig{
384			VerboseOutput: true,
385		}).run(t, pkg, func(t *testing.T, env *Env) {
386			env.OpenFile("a/a.go")
387			env.OpenFile("a/a_unneeded.go")
388			env.Await(
389				OnceMet(
390					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2),
391					LogMatching(protocol.Info, "a_unneeded.go", 1),
392				),
393			)
394
395			// Close and delete the open file, mimicking what an editor would do.
396			env.CloseBuffer("a/a_unneeded.go")
397			env.RemoveWorkspaceFile("a/a_unneeded.go")
398			env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")")
399			env.Await(
400				env.DiagnosticAtRegexp("a/a.go", "fmt"),
401			)
402			env.SaveBuffer("a/a.go")
403			env.Await(
404				OnceMet(
405					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidSave), 1),
406					// There should only be one log message containing
407					// a_unneeded.go, from the initial workspace load, which we
408					// check for earlier. If there are more, there's a bug.
409					LogMatching(protocol.Info, "a_unneeded.go", 1),
410				),
411				EmptyDiagnostics("a/a.go"),
412			)
413		})
414	})
415
416	t.Run("delete then close", func(t *testing.T) {
417		withOptions(EditorConfig{
418			VerboseOutput: true,
419		}).run(t, pkg, func(t *testing.T, env *Env) {
420			env.OpenFile("a/a.go")
421			env.OpenFile("a/a_unneeded.go")
422			env.Await(
423				OnceMet(
424					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2),
425					LogMatching(protocol.Info, "a_unneeded.go", 1),
426				),
427			)
428
429			// Delete and then close the file.
430			env.RemoveWorkspaceFile("a/a_unneeded.go")
431			env.CloseBuffer("a/a_unneeded.go")
432			env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")")
433			env.Await(
434				env.DiagnosticAtRegexp("a/a.go", "fmt"),
435			)
436			env.SaveBuffer("a/a.go")
437			env.Await(
438				OnceMet(
439					CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidSave), 1),
440					// There should only be one log message containing
441					// a_unneeded.go, from the initial workspace load, which we
442					// check for earlier. If there are more, there's a bug.
443					LogMatching(protocol.Info, "a_unneeded.go", 1),
444				),
445				EmptyDiagnostics("a/a.go"),
446			)
447		})
448	})
449}
450
451// This change reproduces the behavior of switching branches, with multiple
452// files being created and deleted. The key change here is the movement of a
453// symbol from one file to another in a given package through a deletion and
454// creation. To reproduce an issue with metadata invalidation in batched
455// changes, the last change in the batch is an on-disk file change that doesn't
456// require metadata invalidation.
457func TestMoveSymbol(t *testing.T) {
458	const pkg = `
459-- go.mod --
460module mod.com
461
462go 1.14
463-- main.go --
464package main
465
466import "mod.com/a"
467
468func main() {
469	var x int
470	x = a.Hello
471	println(x)
472}
473-- a/a1.go --
474package a
475
476var Hello int
477-- a/a2.go --
478package a
479
480func _() {}
481`
482	runner.Run(t, pkg, func(t *testing.T, env *Env) {
483		env.ChangeFilesOnDisk([]fake.FileEvent{
484			{
485				Path: "a/a3.go",
486				Content: `package a
487
488var Hello int
489`,
490				ProtocolEvent: protocol.FileEvent{
491					URI:  env.Sandbox.Workdir.URI("a/a3.go"),
492					Type: protocol.Created,
493				},
494			},
495			{
496				Path: "a/a1.go",
497				ProtocolEvent: protocol.FileEvent{
498					URI:  env.Sandbox.Workdir.URI("a/a1.go"),
499					Type: protocol.Deleted,
500				},
501			},
502			{
503				Path:    "a/a2.go",
504				Content: `package a; func _() {};`,
505				ProtocolEvent: protocol.FileEvent{
506					URI:  env.Sandbox.Workdir.URI("a/a2.go"),
507					Type: protocol.Changed,
508				},
509			},
510		})
511		env.Await(
512			OnceMet(
513				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
514				NoDiagnostics("main.go"),
515			),
516		)
517	})
518}
519
520// Reproduce golang/go#40456.
521func TestChangeVersion(t *testing.T) {
522	const proxy = `
523-- example.com@v1.2.3/go.mod --
524module example.com
525
526go 1.12
527-- example.com@v1.2.3/blah/blah.go --
528package blah
529
530const Name = "Blah"
531
532func X(x int) {}
533-- example.com@v1.2.2/go.mod --
534module example.com
535
536go 1.12
537-- example.com@v1.2.2/blah/blah.go --
538package blah
539
540const Name = "Blah"
541
542func X() {}
543-- random.org@v1.2.3/go.mod --
544module random.org
545
546go 1.12
547-- random.org@v1.2.3/blah/blah.go --
548package hello
549
550const Name = "Hello"
551`
552	const mod = `
553-- go.mod --
554module mod.com
555
556go 1.12
557
558require example.com v1.2.2
559-- main.go --
560package main
561
562import "example.com/blah"
563
564func main() {
565	blah.X()
566}
567`
568	withOptions(ProxyFiles(proxy)).run(t, mod, func(t *testing.T, env *Env) {
569		env.WriteWorkspaceFiles(map[string]string{
570			"go.mod": `module mod.com
571
572go 1.12
573
574require example.com v1.2.3
575`,
576			"main.go": `package main
577
578import (
579	"example.com/blah"
580)
581
582func main() {
583	blah.X(1)
584}
585`,
586		})
587		env.Await(
588			CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
589			NoDiagnostics("main.go"),
590		)
591	})
592}
593
594// Reproduces golang/go#40340.
595func TestSwitchFromGOPATHToModules(t *testing.T) {
596	testenv.NeedsGo1Point(t, 13)
597
598	const files = `
599-- foo/blah/blah.go --
600package blah
601
602const Name = ""
603-- foo/main.go --
604package main
605
606import "blah"
607
608func main() {
609	_ = blah.Name
610}
611`
612	withOptions(
613		InGOPATH(),
614		Modes(Experimental), // module is in a subdirectory
615	).run(t, files, func(t *testing.T, env *Env) {
616		env.OpenFile("foo/main.go")
617		env.Await(env.DiagnosticAtRegexp("foo/main.go", `"blah"`))
618		if err := env.Sandbox.RunGoCommand(env.Ctx, "foo", "mod", []string{"init", "mod.com"}); err != nil {
619			t.Fatal(err)
620		}
621		env.Await(
622			OnceMet(
623				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
624				env.DiagnosticAtRegexp("foo/main.go", `"blah"`),
625			),
626		)
627		env.RegexpReplace("foo/main.go", `"blah"`, `"mod.com/blah"`)
628		env.Await(
629			EmptyDiagnostics("foo/main.go"),
630		)
631	})
632}
633
634// Reproduces golang/go#40487.
635func TestSwitchFromModulesToGOPATH(t *testing.T) {
636	testenv.NeedsGo1Point(t, 13)
637
638	const files = `
639-- foo/go.mod --
640module mod.com
641
642go 1.14
643-- foo/blah/blah.go --
644package blah
645
646const Name = ""
647-- foo/main.go --
648package main
649
650import "mod.com/blah"
651
652func main() {
653	_ = blah.Name
654}
655`
656	withOptions(
657		InGOPATH(),
658	).run(t, files, func(t *testing.T, env *Env) {
659		env.OpenFile("foo/main.go")
660		env.RemoveWorkspaceFile("foo/go.mod")
661		env.Await(
662			OnceMet(
663				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
664				env.DiagnosticAtRegexp("foo/main.go", `"mod.com/blah"`),
665			),
666		)
667		env.RegexpReplace("foo/main.go", `"mod.com/blah"`, `"foo/blah"`)
668		env.Await(
669			EmptyDiagnostics("foo/main.go"),
670		)
671	})
672}
673
674func TestNewSymbolInTestVariant(t *testing.T) {
675	const files = `
676-- go.mod --
677module mod.com
678
679go 1.12
680-- a/a.go --
681package a
682
683func bob() {}
684-- a/a_test.go --
685package a
686
687import "testing"
688
689func TestBob(t *testing.T) {
690	bob()
691}
692`
693	run(t, files, func(t *testing.T, env *Env) {
694		// Add a new symbol to the package under test and use it in the test
695		// variant. Expect no diagnostics.
696		env.WriteWorkspaceFiles(map[string]string{
697			"a/a.go": `package a
698
699func bob() {}
700func george() {}
701`,
702			"a/a_test.go": `package a
703
704import "testing"
705
706func TestAll(t *testing.T) {
707	bob()
708	george()
709}
710`,
711		})
712		env.Await(
713			OnceMet(
714				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
715				NoDiagnostics("a/a.go"),
716			),
717			OnceMet(
718				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
719				NoDiagnostics("a/a_test.go"),
720			),
721		)
722		// Now, add a new file to the test variant and use its symbol in the
723		// original test file. Expect no diagnostics.
724		env.WriteWorkspaceFiles(map[string]string{
725			"a/a_test.go": `package a
726
727import "testing"
728
729func TestAll(t *testing.T) {
730	bob()
731	george()
732	hi()
733}
734`,
735			"a/a2_test.go": `package a
736
737import "testing"
738
739func hi() {}
740
741func TestSomething(t *testing.T) {}
742`,
743		})
744		env.Await(
745			OnceMet(
746				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2),
747				NoDiagnostics("a/a_test.go"),
748			),
749			OnceMet(
750				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2),
751				NoDiagnostics("a/a2_test.go"),
752			),
753		)
754	})
755}
756