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.
15 . "golang.org/x/tools/gopls/internal/regtest"
17 "golang.org/x/tools/internal/lsp/fake"
18 "golang.org/x/tools/internal/lsp/protocol"
19 "golang.org/x/tools/internal/testenv"
22 func TestMain(m *testing.M) {
26 const workspaceProxy = `
27 -- example.com@v1.2.3/go.mod --
31 -- example.com@v1.2.3/blah/blah.go --
35 fmt.Println("something")
37 -- random.org@v1.2.3/go.mod --
41 -- random.org@v1.2.3/bye/bye.go --
49 // TODO: Add a replace directive.
50 const workspaceModule = `
61 example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c=
62 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
63 random.org v1.2.3 h1:+JE2Fkp7gS0zsHXGEQJ7hraom3pNTlkxC4b2qPfA+/Q=
64 random.org v1.2.3/go.mod h1:E9KM6+bBX2g5ykHZ9H27w16sWo3QwgonyjM44Dnej3I=
87 -- pkg/inner/inner.go --
90 import "example.com/blah"
95 -- goodbye/bye/bye.go --
105 // Confirm that find references returns all of the references in the module,
106 // regardless of what the workspace root is.
107 func TestReferences(t *testing.T) {
108 for _, tt := range []struct {
109 name, rootPath string
116 name: "subdirectory",
117 rootPath: "pkg/inner",
120 t.Run(tt.name, func(t *testing.T) {
121 opts := []RunOption{ProxyFiles(workspaceProxy)}
122 if tt.rootPath != "" {
123 opts = append(opts, WorkspaceFolders(tt.rootPath))
125 WithOptions(opts...).Run(t, workspaceModule, func(t *testing.T, env *Env) {
126 f := "pkg/inner/inner.go"
128 locations := env.References(f, env.RegexpSearch(f, `SaySomething`))
130 if got := len(locations); got != want {
131 t.Fatalf("expected %v locations, got %v", want, got)
138 // Make sure that analysis diagnostics are cleared for the whole package when
139 // the only opened file is closed. This test was inspired by the experience in
140 // VS Code, where clicking on a reference result triggers a
141 // textDocument/didOpen without a corresponding textDocument/didClose.
142 func TestClearAnalysisDiagnostics(t *testing.T) {
144 ProxyFiles(workspaceProxy),
145 WorkspaceFolders("pkg/inner"),
146 ).Run(t, workspaceModule, func(t *testing.T, env *Env) {
147 env.OpenFile("pkg/main.go")
149 env.DiagnosticAtRegexp("pkg/main2.go", "fmt.Print"),
151 env.CloseBuffer("pkg/main.go")
153 EmptyDiagnostics("pkg/main2.go"),
158 // This test checks that gopls updates the set of files it watches when a
159 // replace target is added to the go.mod.
160 func TestWatchReplaceTargets(t *testing.T) {
162 ProxyFiles(workspaceProxy),
163 WorkspaceFolders("pkg"),
164 ).Run(t, workspaceModule, func(t *testing.T, env *Env) {
165 // Add a replace directive and expect the files that gopls is watching
167 dir := env.Sandbox.Workdir.URI("goodbye").SpanURI().Filename()
168 goModWithReplace := fmt.Sprintf(`%s
169 replace random.org => %s
170 `, env.ReadWorkspaceFile("pkg/go.mod"), dir)
171 env.WriteWorkspaceFile("pkg/go.mod", goModWithReplace)
173 env.DoneWithChangeWatchedFiles(),
174 UnregistrationMatching("didChangeWatchedFiles"),
175 RegistrationMatching("didChangeWatchedFiles"),
180 const workspaceModuleProxy = `
181 -- example.com@v1.2.3/go.mod --
185 -- example.com@v1.2.3/blah/blah.go --
188 func SaySomething() {
189 fmt.Println("something")
191 -- b.com@v1.2.3/go.mod --
195 -- b.com@v1.2.3/b/b.go --
201 func TestAutomaticWorkspaceModule_Interdependent(t *testing.T) {
202 const multiModule = `
208 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
209 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
232 ProxyFiles(workspaceModuleProxy),
234 ).Run(t, multiModule, func(t *testing.T, env *Env) {
236 env.DiagnosticAtRegexp("moda/a/a.go", "x"),
237 env.DiagnosticAtRegexp("modb/b/b.go", "x"),
238 env.NoDiagnosticAtRegexp("moda/a/a.go", `"b.com/b"`),
243 // This change tests that the version of the module used changes after it has
244 // been deleted from the workspace.
245 func TestDeleteModule_Interdependent(t *testing.T) {
246 const multiModule = `
252 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
253 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
276 ProxyFiles(workspaceModuleProxy),
278 ).Run(t, multiModule, func(t *testing.T, env *Env) {
279 env.OpenFile("moda/a/a.go")
281 original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
282 if want := "modb/b/b.go"; !strings.HasSuffix(original, want) {
283 t.Errorf("expected %s, got %v", want, original)
285 env.CloseBuffer(original)
286 env.RemoveWorkspaceFile("modb/b/b.go")
287 env.RemoveWorkspaceFile("modb/go.mod")
289 env.DoneWithChangeWatchedFiles(),
292 d := protocol.PublishDiagnosticsParams{}
295 env.DiagnosticAtRegexpWithMessage("moda/a/go.mod", "require b.com v1.2.3", "b.com@v1.2.3 has not been downloaded"),
296 ReadDiagnostics("moda/a/go.mod", &d),
299 env.ApplyQuickFixes("moda/a/go.mod", d.Diagnostics)
300 got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
301 if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) {
302 t.Errorf("expected %s, got %v", want, got)
307 // Tests that the version of the module used changes after it has been added
309 func TestCreateModule_Interdependent(t *testing.T) {
310 const multiModule = `
316 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
317 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
332 ProxyFiles(workspaceModuleProxy),
333 ).Run(t, multiModule, func(t *testing.T, env *Env) {
334 env.OpenFile("moda/a/a.go")
335 original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
336 if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) {
337 t.Errorf("expected %s, got %v", want, original)
339 env.CloseBuffer(original)
340 env.WriteWorkspaceFiles(map[string]string{
341 "modb/go.mod": "module b.com",
342 "modb/b/b.go": `package b
351 env.DoneWithChangeWatchedFiles(),
352 env.DiagnosticAtRegexp("modb/b/b.go", "x"),
355 got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
356 if want := "modb/b/b.go"; !strings.HasSuffix(got, want) {
357 t.Errorf("expected %s, got %v", want, original)
362 // This test confirms that a gopls workspace can recover from initialization
363 // with one invalid module.
364 func TestOneBrokenModule(t *testing.T) {
365 const multiModule = `
383 modul b.com // typo here
393 ProxyFiles(workspaceModuleProxy),
395 ).Run(t, multiModule, func(t *testing.T, env *Env) {
396 env.OpenFile("modb/go.mod")
400 DiagnosticAt("modb/go.mod", 0, 0),
403 env.RegexpReplace("modb/go.mod", "modul", "module")
404 env.SaveBufferWithoutActions("modb/go.mod")
406 env.DiagnosticAtRegexp("modb/b/b.go", "x"),
411 func TestUseGoplsMod(t *testing.T) {
412 // This test validates certain functionality related to using a gopls.mod
413 // file to specify workspace modules.
414 testenv.NeedsGo1Point(t, 14)
415 const multiModule = `
421 b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
422 b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
437 require example.com v1.2.3
439 example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c=
440 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
448 module gopls-workspace
451 a.com v0.0.0-goplsworkspace
455 replace a.com => $SANDBOX_WORKDIR/moda/a
458 ProxyFiles(workspaceModuleProxy),
460 ).Run(t, multiModule, func(t *testing.T, env *Env) {
461 // Initially, the gopls.mod should cause only the a.com module to be
462 // loaded. Validate this by jumping to a definition in b.com and ensuring
463 // that we go to the module cache.
464 env.OpenFile("moda/a/a.go")
465 env.Await(env.DoneWithOpen())
467 // To verify which modules are loaded, we'll jump to the definition of
469 checkHelloLocation := func(want string) error {
470 location, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
471 if !strings.HasSuffix(location, want) {
472 return fmt.Errorf("expected %s, got %v", want, location)
477 // Initially this should be in the module cache, as b.com is not replaced.
478 if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil {
482 // Now, modify the gopls.mod file on disk to activate the b.com module in
484 workdir := env.Sandbox.Workdir.RootURI().SpanURI().Filename()
485 env.WriteWorkspaceFile("gopls.mod", fmt.Sprintf(`module gopls-workspace
488 a.com v1.9999999.0-goplsworkspace
489 b.com v1.9999999.0-goplsworkspace
492 replace a.com => %s/moda/a
493 replace b.com => %s/modb
494 `, workdir, workdir))
495 env.Await(env.DoneWithChangeWatchedFiles())
496 // Check that go.mod diagnostics picked up the newly active mod file.
497 // The local version of modb has an extra dependency we need to download.
498 env.OpenFile("modb/go.mod")
499 env.Await(env.DoneWithOpen())
501 var d protocol.PublishDiagnosticsParams
504 env.DiagnosticAtRegexpWithMessage("modb/go.mod", `require example.com v1.2.3`, "has not been downloaded"),
505 ReadDiagnostics("modb/go.mod", &d),
508 env.ApplyQuickFixes("modb/go.mod", d.Diagnostics)
509 env.Await(env.DiagnosticAtRegexp("modb/b/b.go", "x"))
510 // Jumping to definition should now go to b.com in the workspace.
511 if err := checkHelloLocation("modb/b/b.go"); err != nil {
515 // Now, let's modify the gopls.mod *overlay* (not on disk), and verify that
516 // this change is only picked up once it is saved.
517 env.OpenFile("gopls.mod")
518 env.Await(env.DoneWithOpen())
519 env.SetBufferContent("gopls.mod", fmt.Sprintf(`module gopls-workspace
522 a.com v0.0.0-goplsworkspace
525 replace a.com => %s/moda/a
528 // Editing the gopls.mod removes modb from the workspace modules, and so
529 // should clear outstanding diagnostics...
531 env.DoneWithChange(),
532 EmptyDiagnostics("modb/go.mod"),
534 // ...but does not yet cause a workspace reload, so we should still jump to modb.
535 if err := checkHelloLocation("modb/b/b.go"); err != nil {
538 // Saving should reload the workspace.
539 env.SaveBufferWithoutActions("gopls.mod")
540 if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil {
546 func TestNonWorkspaceFileCreation(t *testing.T) {
547 testenv.NeedsGo1Point(t, 13)
563 Run(t, files, func(t *testing.T, env *Env) {
564 env.CreateBuffer("/tmp/foo.go", "")
565 env.EditBuffer("/tmp/foo.go", fake.NewEdit(0, 0, 0, 0, code))
566 env.GoToDefinition("/tmp/foo.go", env.RegexpSearch("/tmp/foo.go", `Printf`))
570 func TestMultiModuleV2(t *testing.T) {
571 const multiModule = `
575 require b.com/v2 v2.1.9
606 module gopkg.in/yaml.v1 // test gopkg.in versions
616 ).Run(t, multiModule, func(t *testing.T, env *Env) {
618 env.DiagnosticAtRegexp("moda/a/a.go", "x"),
619 env.DiagnosticAtRegexp("modb/b/b.go", "x"),
620 env.DiagnosticAtRegexp("modb/v2/b/b.go", "x"),
621 env.DiagnosticAtRegexp("modc/main.go", "x"),
626 func TestWorkspaceDirAccess(t *testing.T) {
627 const multiModule = `
649 ).Run(t, multiModule, func(t *testing.T, env *Env) {
651 // Don't factor this out of Server.addFolders. vscode-go expects this
653 modPath := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.workspace", pid), "go.mod")
654 gotb, err := ioutil.ReadFile(modPath)
656 t.Fatalf("reading expected workspace modfile: %v", err)
659 for _, want := range []string{"a.com v1.9999999.0-goplsworkspace", "b.com v1.9999999.0-goplsworkspace"} {
660 if !strings.Contains(got, want) {
661 // want before got here, since the go.mod is multi-line
662 t.Fatalf("workspace go.mod missing %q. got:\n%s", want, got)
665 workdir := env.Sandbox.Workdir.RootURI().SpanURI().Filename()
666 env.WriteWorkspaceFile("gopls.mod", fmt.Sprintf(`
667 module gopls-workspace
670 a.com v1.9999999.0-goplsworkspace
673 replace a.com => %s/moda/a
675 env.Await(env.DoneWithChangeWatchedFiles())
676 gotb, err = ioutil.ReadFile(modPath)
678 t.Fatalf("reading expected workspace modfile: %v", err)
681 want := "b.com v1.9999999.0-goplsworkspace"
682 if strings.Contains(got, want) {
683 t.Fatalf("workspace go.mod contains unexpected %q. got:\n%s", want, got)
688 func TestDirectoryFiltersLoads(t *testing.T) {
689 // exclude, and its error, should be excluded from the workspace.
695 -- exclude/exclude.go --
698 const _ = Nonexistant
701 DirectoryFilters: []string{"-exclude"},
703 WithOptions(cfg).Run(t, files, func(t *testing.T, env *Env) {
704 env.Await(NoDiagnostics("exclude/x.go"))
708 func TestDirectoryFiltersTransitiveDep(t *testing.T) {
709 // Even though exclude is excluded from the workspace, it should
710 // still be importable as a non-workspace package.
716 -- include/include.go --
718 import "example.com/exclude"
721 -- exclude/exclude.go --
724 const _ = Nonexistant // should be ignored, since this is a non-workspace package
729 DirectoryFilters: []string{"-exclude"},
731 WithOptions(cfg).Run(t, files, func(t *testing.T, env *Env) {
733 NoDiagnostics("exclude/exclude.go"), // filtered out
734 NoDiagnostics("include/include.go"), // successfully builds
739 func TestDirectoryFiltersWorkspaceModules(t *testing.T) {
740 // Define a module include.com which should be in the workspace, plus a
741 // module exclude.com which should be excluded and therefore come from
749 require exclude.com v1.0.0
752 exclude.com v1.0.0 h1:Q5QSfDXY5qyNCBeUiWovUGqcLCRZKoTs9XdBeVz+w1I=
753 exclude.com v1.0.0/go.mod h1:hFox2uDlNB2s2Jfd9tHlQVfgqUiLVTmh6ZKat4cvnj4=
755 -- include/include.go --
760 var _ = exclude.X // satisfied only by the workspace version
765 -- exclude/exclude.go --
771 -- exclude.com@v1.0.0/go.mod --
775 -- exclude.com@v1.0.0/exclude.go --
779 DirectoryFilters: []string{"-exclude"},
781 WithOptions(cfg, Modes(Experimental), ProxyFiles(proxy)).Run(t, files, func(t *testing.T, env *Env) {
782 env.Await(env.DiagnosticAtRegexp("include/include.go", `exclude.(X)`))
786 // Confirm that a fix for a tidy module will correct all modules in the
788 func TestMultiModule_OneBrokenModule(t *testing.T) {
789 testenv.NeedsGo1Point(t, 15)
810 import "example.com/blah"
817 ProxyFiles(workspaceProxy),
819 ).Run(t, mod, func(t *testing.T, env *Env) {
820 params := &protocol.PublishDiagnosticsParams{}
821 env.OpenFile("b/go.mod")
824 env.GoSumDiagnostic("b/go.mod", `example.com v1.2.3`),
825 ReadDiagnostics("b/go.mod", params),
828 for _, d := range params.Diagnostics {
829 if !strings.Contains(d.Message, "go.sum is out of sync") {
832 actions := env.GetQuickFixes("b/go.mod", []protocol.Diagnostic{d})
833 if len(actions) != 2 {
834 t.Fatalf("expected 2 code actions, got %v", len(actions))
836 env.ApplyQuickFixes("b/go.mod", []protocol.Diagnostic{d})
839 EmptyDiagnostics("b/go.mod"),