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