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.
11 "golang.org/x/tools/internal/lsp"
12 "golang.org/x/tools/internal/lsp/protocol"
13 "golang.org/x/tools/internal/lsp/tests"
14 "golang.org/x/tools/internal/testenv"
18 -- example.com@v1.2.3/go.mod --
22 -- example.com@v1.2.3/blah/blah.go --
26 -- random.org@v1.2.3/go.mod --
30 -- random.org@v1.2.3/blah/blah.go --
36 func TestModFileModification(t *testing.T) {
37 testenv.NeedsGo1Point(t, 14)
39 const untidyModule = `
46 import "example.com/blah"
52 t.Run("basic", func(t *testing.T) {
53 withOptions(WithProxyFiles(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
54 // Open the file and make sure that the initial workspace load does not
55 // modify the go.mod file.
56 goModContent := env.ReadWorkspaceFile("go.mod")
57 env.OpenFile("main.go")
59 env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
61 if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
62 t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
64 // Save the buffer, which will format and organize imports.
65 // Confirm that the go.mod file still does not change.
66 env.SaveBuffer("main.go")
68 env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
70 if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
71 t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
76 // Reproduce golang/go#40269 by deleting and recreating main.go.
77 t.Run("delete main.go", func(t *testing.T) {
78 t.Skip("This test will be flaky until golang/go#40269 is resolved.")
80 withOptions(WithProxyFiles(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
81 goModContent := env.ReadWorkspaceFile("go.mod")
82 mainContent := env.ReadWorkspaceFile("main.go")
83 env.OpenFile("main.go")
84 env.SaveBuffer("main.go")
86 env.RemoveWorkspaceFile("main.go")
88 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1),
89 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidSave), 1),
90 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2),
93 env.WriteWorkspaceFile("main.go", mainContent)
95 env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
97 if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
98 t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
104 // Tests that multiple missing dependencies gives good single fixes.
105 func TestMissingDependencyFixes(t *testing.T) {
106 testenv.NeedsGo1Point(t, 14)
116 import "example.com/blah"
117 import "random.org/blah"
119 var _, _ = blah.Name, hello.Name
122 const want = `module mod.com
126 require random.org v1.2.3
129 withOptions(WithProxyFiles(proxy)).run(t, mod, func(t *testing.T, env *Env) {
130 env.OpenFile("main.go")
131 var d protocol.PublishDiagnosticsParams
134 env.DiagnosticAtRegexp("main.go", `"random.org/blah"`),
135 ReadDiagnostics("main.go", &d),
138 var randomDiag protocol.Diagnostic
139 for _, diag := range d.Diagnostics {
140 if strings.Contains(diag.Message, "random.org") {
144 env.OpenFile("go.mod")
145 env.ApplyQuickFixes("main.go", []protocol.Diagnostic{randomDiag})
146 if got := env.Editor.BufferText("go.mod"); got != want {
147 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
152 func TestIndirectDependencyFix(t *testing.T) {
153 testenv.NeedsGo1Point(t, 14)
161 require example.com v1.2.3 // indirect
165 import "example.com/blah"
168 fmt.Println(blah.Name)
170 const want = `module mod.com
174 require example.com v1.2.3
176 runner.Run(t, mod, func(t *testing.T, env *Env) {
177 env.OpenFile("go.mod")
178 var d protocol.PublishDiagnosticsParams
181 env.DiagnosticAtRegexp("go.mod", "// indirect"),
182 ReadDiagnostics("go.mod", &d),
185 env.ApplyQuickFixes("go.mod", d.Diagnostics)
186 if got := env.Editor.BufferText("go.mod"); got != want {
187 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
189 }, WithProxyFiles(proxy))
192 func TestUnusedDiag(t *testing.T) {
193 testenv.NeedsGo1Point(t, 14)
196 -- example.com@v1.0.0/x.go --
204 require example.com v1.0.0
211 const want = `module mod.com
216 withOptions(WithProxyFiles(proxy)).run(t, files, func(t *testing.T, env *Env) {
217 env.OpenFile("go.mod")
218 var d protocol.PublishDiagnosticsParams
221 env.DiagnosticAtRegexp("go.mod", `require example.com`),
222 ReadDiagnostics("go.mod", &d),
225 env.ApplyQuickFixes("go.mod", d.Diagnostics)
226 if got := env.Editor.BufferText("go.mod"); got != want {
227 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
232 // Test to reproduce golang/go#39041. It adds a new require to a go.mod file
233 // that already has an unused require.
234 func TestNewDepWithUnusedDep(t *testing.T) {
235 testenv.NeedsGo1Point(t, 14)
238 -- github.com/esimov/caire@v1.2.5/go.mod --
239 module github.com/esimov/caire
242 -- github.com/esimov/caire@v1.2.5/caire.go --
245 func RemoveTempImage() {}
246 -- google.golang.org/protobuf@v1.20.0/go.mod --
247 module google.golang.org/protobuf
250 -- google.golang.org/protobuf@v1.20.0/hello/hello.go --
259 require google.golang.org/protobuf v1.20.0
264 "github.com/esimov/caire"
268 caire.RemoveTempImage()
270 runner.Run(t, repro, func(t *testing.T, env *Env) {
271 env.OpenFile("go.mod")
272 env.OpenFile("main.go")
273 var d protocol.PublishDiagnosticsParams
276 env.DiagnosticAtRegexp("main.go", `"github.com/esimov/caire"`),
277 ReadDiagnostics("main.go", &d),
280 env.ApplyQuickFixes("main.go", d.Diagnostics)
281 want := `module mod.com
286 github.com/esimov/caire v1.2.5
287 google.golang.org/protobuf v1.20.0
290 if got := env.Editor.BufferText("go.mod"); got != want {
291 t.Fatalf("TestNewDepWithUnusedDep failed:\n%s", tests.Diff(want, got))
293 }, WithProxyFiles(proxy))
296 // TODO: For this test to be effective, the sandbox's file watcher must respect
297 // the file watching GlobPattern in the capability registration. See
299 func TestModuleChangesOnDisk(t *testing.T) {
300 testenv.NeedsGo1Point(t, 14)
308 require example.com v1.2.3
313 fmt.Println(blah.Name)
315 runner.Run(t, mod, func(t *testing.T, env *Env) {
316 env.Await(env.DiagnosticAtRegexp("go.mod", "require"))
317 env.RunGoCommand("mod", "tidy")
319 EmptyDiagnostics("go.mod"),
321 }, WithProxyFiles(proxy))
324 func TestBadlyVersionedModule(t *testing.T) {
325 testenv.NeedsGo1Point(t, 14)
328 -- example.com/blah/@v/list --
330 -- example.com/blah/@v/v1.0.0.mod --
334 -- example.com/blah@v1.0.0/blah.go --
338 -- example.com/blah@v1.0.0/blah_test.go --
345 func TestBlah(t *testing.T) {}
347 -- example.com/blah/v2/@v/list --
349 -- example.com/blah/v2/@v/v2.0.0.mod --
353 -- example.com/blah/v2@v2.0.0/blah.go --
357 -- example.com/blah/v2@v2.0.0/blah_test.go --
366 func TestBlah(t *testing.T) {}
373 example.com/blah/v2 v2.0.0
378 import "example.com/blah/v2"
384 runner.Run(t, pkg, func(t *testing.T, env *Env) {
385 env.OpenFile("main.go")
386 env.OpenFile("go.mod")
387 var d protocol.PublishDiagnosticsParams
390 DiagnosticAt("go.mod", 0, 0),
391 ReadDiagnostics("go.mod", &d),
394 env.ApplyQuickFixes("main.go", d.Diagnostics)
395 const want = `module mod.com
398 example.com/blah v1.0.0
399 example.com/blah/v2 v2.0.0
402 if got := env.Editor.BufferText("go.mod"); got != want {
403 t.Fatalf("suggested fixes failed:\n%s", tests.Diff(want, got))
405 }, WithProxyFiles(badModule))
408 // Reproduces golang/go#38232.
409 func TestUnknownRevision(t *testing.T) {
410 testenv.NeedsGo1Point(t, 14)
422 import "example.com/blah"
429 // Start from a bad state/bad IWL, and confirm that we recover.
430 t.Run("bad", func(t *testing.T) {
431 runner.Run(t, unknown, func(t *testing.T, env *Env) {
432 env.OpenFile("go.mod")
434 env.DiagnosticAtRegexp("go.mod", "example.com v1.2.2"),
436 env.RegexpReplace("go.mod", "v1.2.2", "v1.2.3")
437 env.Editor.SaveBufferWithoutActions(env.Ctx, "go.mod") // go.mod changes must be on disk
439 env.DiagnosticAtRegexp("main.go", "x = "),
441 }, WithProxyFiles(proxy))
454 import "example.com/blah"
460 // Start from a good state, transform to a bad state, and confirm that we
462 t.Run("good", func(t *testing.T) {
463 runner.Run(t, known, func(t *testing.T, env *Env) {
464 env.OpenFile("go.mod")
466 env.DiagnosticAtRegexp("main.go", "x = "),
468 env.RegexpReplace("go.mod", "v1.2.3", "v1.2.2")
469 env.Editor.SaveBufferWithoutActions(env.Ctx, "go.mod") // go.mod changes must be on disk
471 env.DiagnosticAtRegexp("go.mod", "example.com v1.2.2"),
473 env.RegexpReplace("go.mod", "v1.2.2", "v1.2.3")
474 env.Editor.SaveBufferWithoutActions(env.Ctx, "go.mod") // go.mod changes must be on disk
476 env.DiagnosticAtRegexp("main.go", "x = "),
478 }, WithProxyFiles(proxy))
482 func TestTidyOnSave(t *testing.T) {
483 testenv.NeedsGo1Point(t, 14)
485 const untidyModule = `
491 require random.org v1.2.3
495 import "example.com/blah"
498 fmt.Println(blah.Name)
501 withOptions(WithProxyFiles(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
502 env.OpenFile("go.mod")
504 env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
505 env.DiagnosticAtRegexp("go.mod", `require random.org v1.2.3`),
507 env.SaveBuffer("go.mod")
508 const want = `module mod.com
512 require example.com v1.2.3
514 if got := env.ReadWorkspaceFile("go.mod"); got != want {
515 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
520 // Confirm that an error in an indirect dependency of a requirement is surfaced
521 // as a diagnostic in the go.mod file.
522 func TestErrorInIndirectDependency(t *testing.T) {
523 testenv.NeedsGo1Point(t, 14)
526 -- example.com@v1.2.3/go.mod --
531 require random.org v1.2.3 // indirect
532 -- example.com@v1.2.3/blah/blah.go --
536 -- random.org@v1.2.3/go.mod --
540 -- random.org@v1.2.3/blah/blah.go --
551 require example.com v1.2.3
555 import "example.com/blah"
561 withOptions(WithProxyFiles(badProxy)).run(t, module, func(t *testing.T, env *Env) {
562 env.OpenFile("go.mod")
564 env.DiagnosticAtRegexp("go.mod", "require example.com v1.2.3"),
569 // A copy of govim's config_set_env_goflags_mod_readonly test.
570 func TestGovimModReadonly(t *testing.T) {
579 import "example.com/blah"
587 Env: map[string]string{
588 "GOFLAGS": "-mod=readonly",
591 WithProxyFiles(proxy),
592 WithModes(WithoutExperiments),
593 ).run(t, mod, func(t *testing.T, env *Env) {
594 env.OpenFile("main.go")
595 original := env.ReadWorkspaceFile("go.mod")
597 env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
599 got := env.ReadWorkspaceFile("go.mod")
601 t.Fatalf("go.mod file modified:\n%s", tests.Diff(original, got))
603 env.RunGoCommand("get", "example.com/blah@v1.2.3")
604 env.RunGoCommand("mod", "tidy")
606 EmptyDiagnostics("main.go"),
611 func TestMultiModuleModDiagnostics(t *testing.T) {
612 testenv.NeedsGo1Point(t, 14)
634 import "example.com/blah"
641 WithProxyFiles(workspaceProxy),
642 WithModes(Experimental),
643 ).run(t, mod, func(t *testing.T, env *Env) {
645 env.DiagnosticAtRegexp("a/go.mod", "example.com v1.2.3"),
646 env.DiagnosticAtRegexp("b/go.mod", "module mod.com"),
651 func TestModTidyWithBuildTags(t *testing.T) {
652 testenv.NeedsGo1Point(t, 14)
664 import "example.com/blah"
671 WithProxyFiles(workspaceProxy),
673 BuildFlags: []string{"-tags", "bob"},
675 ).run(t, mod, func(t *testing.T, env *Env) {
677 env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),