|  | // Copyright 2020 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package modfile | 
|  |  | 
|  | import ( | 
|  | "path/filepath" | 
|  | "strings" | 
|  | "testing" | 
|  |  | 
|  | "golang.org/x/tools/gopls/internal/hooks" | 
|  | . "golang.org/x/tools/internal/lsp/regtest" | 
|  |  | 
|  | "golang.org/x/tools/internal/lsp/protocol" | 
|  | "golang.org/x/tools/internal/lsp/tests" | 
|  | "golang.org/x/tools/internal/testenv" | 
|  | ) | 
|  |  | 
|  | func TestMain(m *testing.M) { | 
|  | Main(m, hooks.Options) | 
|  | } | 
|  |  | 
|  | const workspaceProxy = ` | 
|  | -- example.com@v1.2.3/go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  | -- example.com@v1.2.3/blah/blah.go -- | 
|  | package blah | 
|  |  | 
|  | func SaySomething() { | 
|  | fmt.Println("something") | 
|  | } | 
|  | -- random.org@v1.2.3/go.mod -- | 
|  | module random.org | 
|  |  | 
|  | go 1.12 | 
|  | -- random.org@v1.2.3/bye/bye.go -- | 
|  | package bye | 
|  |  | 
|  | func Goodbye() { | 
|  | println("Bye") | 
|  | } | 
|  | ` | 
|  |  | 
|  | const proxy = ` | 
|  | -- example.com@v1.2.3/go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  | -- example.com@v1.2.3/blah/blah.go -- | 
|  | package blah | 
|  |  | 
|  | const Name = "Blah" | 
|  | -- random.org@v1.2.3/go.mod -- | 
|  | module random.org | 
|  |  | 
|  | go 1.12 | 
|  | -- random.org@v1.2.3/blah/blah.go -- | 
|  | package hello | 
|  |  | 
|  | const Name = "Hello" | 
|  | ` | 
|  |  | 
|  | func TestModFileModification(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const untidyModule = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | println(blah.Name) | 
|  | } | 
|  | ` | 
|  |  | 
|  | runner := RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | } | 
|  |  | 
|  | t.Run("basic", func(t *testing.T) { | 
|  | runner.Run(t, untidyModule, func(t *testing.T, env *Env) { | 
|  | // Open the file and make sure that the initial workspace load does not | 
|  | // modify the go.mod file. | 
|  | goModContent := env.ReadWorkspaceFile("a/go.mod") | 
|  | env.OpenFile("a/main.go") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/main.go", "\"example.com/blah\""), | 
|  | ) | 
|  | if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { | 
|  | t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(t, goModContent, got)) | 
|  | } | 
|  | // Save the buffer, which will format and organize imports. | 
|  | // Confirm that the go.mod file still does not change. | 
|  | env.SaveBuffer("a/main.go") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/main.go", "\"example.com/blah\""), | 
|  | ) | 
|  | if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { | 
|  | t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(t, goModContent, got)) | 
|  | } | 
|  | }) | 
|  | }) | 
|  |  | 
|  | // Reproduce golang/go#40269 by deleting and recreating main.go. | 
|  | t.Run("delete main.go", func(t *testing.T) { | 
|  | t.Skip("This test will be flaky until golang/go#40269 is resolved.") | 
|  |  | 
|  | runner.Run(t, untidyModule, func(t *testing.T, env *Env) { | 
|  | goModContent := env.ReadWorkspaceFile("a/go.mod") | 
|  | mainContent := env.ReadWorkspaceFile("a/main.go") | 
|  | env.OpenFile("a/main.go") | 
|  | env.SaveBuffer("a/main.go") | 
|  |  | 
|  | env.RemoveWorkspaceFile("a/main.go") | 
|  | env.Await( | 
|  | env.DoneWithOpen(), | 
|  | env.DoneWithSave(), | 
|  | env.DoneWithChangeWatchedFiles(), | 
|  | ) | 
|  |  | 
|  | env.WriteWorkspaceFile("main.go", mainContent) | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""), | 
|  | ) | 
|  | if got := env.ReadWorkspaceFile("go.mod"); got != goModContent { | 
|  | t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(t, goModContent, got)) | 
|  | } | 
|  | }) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestGoGetFix(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  | const mod = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | var _ = blah.Name | 
|  | ` | 
|  |  | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require example.com v1.2.3 | 
|  | ` | 
|  |  | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, mod, func(t *testing.T, env *Env) { | 
|  | if strings.Contains(t.Name(), "workspace_module") { | 
|  | t.Skip("workspace module mode doesn't set -mod=readonly") | 
|  | } | 
|  | env.OpenFile("a/main.go") | 
|  | var d protocol.PublishDiagnosticsParams | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.DiagnosticAtRegexp("a/main.go", `"example.com/blah"`), | 
|  | ReadDiagnostics("a/main.go", &d), | 
|  | ), | 
|  | ) | 
|  | var goGetDiag protocol.Diagnostic | 
|  | for _, diag := range d.Diagnostics { | 
|  | if strings.Contains(diag.Message, "could not import") { | 
|  | goGetDiag = diag | 
|  | } | 
|  | } | 
|  | env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{goGetDiag}) | 
|  | if got := env.ReadWorkspaceFile("a/go.mod"); got != want { | 
|  | t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Tests that multiple missing dependencies gives good single fixes. | 
|  | func TestMissingDependencyFixes(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  | const mod = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  | import "random.org/blah" | 
|  |  | 
|  | var _, _ = blah.Name, hello.Name | 
|  | ` | 
|  |  | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require random.org v1.2.3 | 
|  | ` | 
|  |  | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/main.go") | 
|  | var d protocol.PublishDiagnosticsParams | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.DiagnosticAtRegexp("a/main.go", `"random.org/blah"`), | 
|  | ReadDiagnostics("a/main.go", &d), | 
|  | ), | 
|  | ) | 
|  | var randomDiag protocol.Diagnostic | 
|  | for _, diag := range d.Diagnostics { | 
|  | if strings.Contains(diag.Message, "random.org") { | 
|  | randomDiag = diag | 
|  | } | 
|  | } | 
|  | env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{randomDiag}) | 
|  | if got := env.ReadWorkspaceFile("a/go.mod"); got != want { | 
|  | t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestIndirectDependencyFix(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const mod = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require example.com v1.2.3 // indirect | 
|  | -- a/go.sum -- | 
|  | example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | fmt.Println(blah.Name) | 
|  | ` | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require example.com v1.2.3 | 
|  | ` | 
|  |  | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/go.mod") | 
|  | var d protocol.PublishDiagnosticsParams | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.DiagnosticAtRegexp("a/go.mod", "// indirect"), | 
|  | ReadDiagnostics("a/go.mod", &d), | 
|  | ), | 
|  | ) | 
|  | env.ApplyQuickFixes("a/go.mod", d.Diagnostics) | 
|  | if got := env.Editor.BufferText("a/go.mod"); got != want { | 
|  | t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestUnusedDiag(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const proxy = ` | 
|  | -- example.com@v1.0.0/x.go -- | 
|  | package pkg | 
|  | const X = 1 | 
|  | ` | 
|  | const files = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  | go 1.14 | 
|  | require example.com v1.0.0 | 
|  | -- a/go.sum -- | 
|  | example.com v1.0.0 h1:38O7j5rEBajXk+Q5wzLbRN7KqMkSgEiN9NqcM1O2bBM= | 
|  | example.com v1.0.0/go.mod h1:vUsPMGpx9ZXXzECCOsOmYCW7npJTwuA16yl89n3Mgls= | 
|  | -- a/main.go -- | 
|  | package main | 
|  | func main() {} | 
|  | ` | 
|  |  | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.14 | 
|  | ` | 
|  |  | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, files, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/go.mod") | 
|  | var d protocol.PublishDiagnosticsParams | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.DiagnosticAtRegexp("a/go.mod", `require example.com`), | 
|  | ReadDiagnostics("a/go.mod", &d), | 
|  | ), | 
|  | ) | 
|  | env.ApplyQuickFixes("a/go.mod", d.Diagnostics) | 
|  | if got := env.Editor.BufferText("a/go.mod"); got != want { | 
|  | t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Test to reproduce golang/go#39041. It adds a new require to a go.mod file | 
|  | // that already has an unused require. | 
|  | func TestNewDepWithUnusedDep(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const proxy = ` | 
|  | -- github.com/esimov/caire@v1.2.5/go.mod -- | 
|  | module github.com/esimov/caire | 
|  |  | 
|  | go 1.12 | 
|  | -- github.com/esimov/caire@v1.2.5/caire.go -- | 
|  | package caire | 
|  |  | 
|  | func RemoveTempImage() {} | 
|  | -- google.golang.org/protobuf@v1.20.0/go.mod -- | 
|  | module google.golang.org/protobuf | 
|  |  | 
|  | go 1.12 | 
|  | -- google.golang.org/protobuf@v1.20.0/hello/hello.go -- | 
|  | package hello | 
|  | ` | 
|  | const repro = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.14 | 
|  |  | 
|  | require google.golang.org/protobuf v1.20.0 | 
|  | -- a/go.sum -- | 
|  | github.com/esimov/caire v1.2.5 h1:OcqDII/BYxcBYj3DuwDKjd+ANhRxRqLa2n69EGje7qw= | 
|  | github.com/esimov/caire v1.2.5/go.mod h1:mXnjRjg3+WUtuhfSC1rKRmdZU9vJZyS1ZWU0qSvJhK8= | 
|  | google.golang.org/protobuf v1.20.0 h1:y9T1vAtFKQg0faFNMOxJU7WuEqPWolVkjIkU6aI8qCY= | 
|  | google.golang.org/protobuf v1.20.0/go.mod h1:FcqsytGClbtLv1ot8NvsJHjBi0h22StKVP+K/j2liKA= | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "github.com/esimov/caire" | 
|  | ) | 
|  |  | 
|  | func _() { | 
|  | caire.RemoveTempImage() | 
|  | }` | 
|  |  | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, repro, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/main.go") | 
|  | var d protocol.PublishDiagnosticsParams | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.DiagnosticAtRegexp("a/main.go", `"github.com/esimov/caire"`), | 
|  | ReadDiagnostics("a/main.go", &d), | 
|  | ), | 
|  | ) | 
|  | env.ApplyQuickFixes("a/main.go", d.Diagnostics) | 
|  | want := `module mod.com | 
|  |  | 
|  | go 1.14 | 
|  |  | 
|  | require ( | 
|  | github.com/esimov/caire v1.2.5 | 
|  | google.golang.org/protobuf v1.20.0 | 
|  | ) | 
|  | ` | 
|  | if got := env.ReadWorkspaceFile("a/go.mod"); got != want { | 
|  | t.Fatalf("TestNewDepWithUnusedDep failed:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // TODO: For this test to be effective, the sandbox's file watcher must respect | 
|  | // the file watching GlobPattern in the capability registration. See | 
|  | // golang/go#39384. | 
|  | func TestModuleChangesOnDisk(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const mod = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require example.com v1.2.3 | 
|  | -- a/go.sum -- | 
|  | example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | func main() { | 
|  | fmt.Println(blah.Name) | 
|  | ` | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.Await(env.DiagnosticAtRegexp("a/go.mod", "require")) | 
|  | env.RunGoCommandInDir("a", "mod", "tidy") | 
|  | env.Await( | 
|  | EmptyDiagnostics("a/go.mod"), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Tests golang/go#39784: a missing indirect dependency, necessary | 
|  | // due to blah@v2.0.0's incomplete go.mod file. | 
|  | func TestBadlyVersionedModule(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const proxy = ` | 
|  | -- example.com/blah/@v/v1.0.0.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  | -- example.com/blah@v1.0.0/blah.go -- | 
|  | package blah | 
|  |  | 
|  | const Name = "Blah" | 
|  | -- example.com/blah/v2/@v/v2.0.0.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  | -- example.com/blah/v2@v2.0.0/blah.go -- | 
|  | package blah | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | var V1Name = blah.Name | 
|  | const Name = "Blah" | 
|  | ` | 
|  | const files = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require example.com/blah/v2 v2.0.0 | 
|  | -- a/go.sum -- | 
|  | example.com/blah v1.0.0 h1:kGPlWJbMsn1P31H9xp/q2mYI32cxLnCvauHN0AVaHnc= | 
|  | example.com/blah v1.0.0/go.mod h1:PZUQaGFeVjyDmAE8ywmLbmDn3fj4Ws8epg4oLuDzW3M= | 
|  | example.com/blah/v2 v2.0.0 h1:DNPsFPkKtTdxclRheaMCiYAoYizp6PuBzO0OmLOO0pY= | 
|  | example.com/blah/v2 v2.0.0/go.mod h1:UZiKbTwobERo/hrqFLvIQlJwQZQGxWMVY4xere8mj7w= | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah/v2" | 
|  |  | 
|  | var _ = blah.Name | 
|  | ` | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | }.Run(t, files, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/main.go") | 
|  | env.OpenFile("a/go.mod") | 
|  | env.Await( | 
|  | // We would like for the error to appear in the v2 module, but | 
|  | // as of writing non-workspace packages are not diagnosed. | 
|  | env.DiagnosticAtRegexpWithMessage("a/main.go", `"example.com/blah/v2"`, "cannot find module providing"), | 
|  | env.DiagnosticAtRegexpWithMessage("a/go.mod", `require example.com/blah/v2`, "cannot find module providing"), | 
|  | ) | 
|  | env.ApplyQuickFixes("a/go.mod", env.DiagnosticsFor("a/go.mod").Diagnostics) | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require ( | 
|  | example.com/blah v1.0.0 // indirect | 
|  | example.com/blah/v2 v2.0.0 | 
|  | ) | 
|  | ` | 
|  | env.SaveBuffer("a/go.mod") | 
|  | env.Await(EmptyDiagnostics("a/main.go")) | 
|  | if got := env.Editor.BufferText("a/go.mod"); got != want { | 
|  | t.Fatalf("suggested fixes failed:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Reproduces golang/go#38232. | 
|  | func TestUnknownRevision(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const unknown = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | require ( | 
|  | example.com v1.2.2 | 
|  | ) | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | var x = blah.Name | 
|  | } | 
|  | ` | 
|  |  | 
|  | runner := RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(proxy))}, | 
|  | } | 
|  | // Start from a bad state/bad IWL, and confirm that we recover. | 
|  | t.Run("bad", func(t *testing.T) { | 
|  | runner.Run(t, unknown, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/go.mod") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/go.mod", "example.com v1.2.2"), | 
|  | ) | 
|  | env.RegexpReplace("a/go.mod", "v1.2.2", "v1.2.3") | 
|  | env.SaveBuffer("a/go.mod") // Save to trigger diagnostics. | 
|  |  | 
|  | d := protocol.PublishDiagnosticsParams{} | 
|  | env.Await( | 
|  | OnceMet( | 
|  | // Make sure the diagnostic mentions the new version -- the old diagnostic is in the same place. | 
|  | env.DiagnosticAtRegexpWithMessage("a/go.mod", "example.com v1.2.3", "example.com@v1.2.3"), | 
|  | ReadDiagnostics("a/go.mod", &d), | 
|  | ), | 
|  | ) | 
|  | qfs := env.GetQuickFixes("a/go.mod", d.Diagnostics) | 
|  | if len(qfs) == 0 { | 
|  | t.Fatalf("got 0 code actions to fix %v, wanted at least 1", d.Diagnostics) | 
|  | } | 
|  | env.ApplyCodeAction(qfs[0]) // Arbitrarily pick a single fix to apply. Applying all of them seems to cause trouble in this particular test. | 
|  | env.SaveBuffer("a/go.mod")  // Save to trigger diagnostics. | 
|  | env.Await( | 
|  | EmptyDiagnostics("a/go.mod"), | 
|  | env.DiagnosticAtRegexp("a/main.go", "x = "), | 
|  | ) | 
|  | }) | 
|  | }) | 
|  |  | 
|  | const known = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | require ( | 
|  | example.com v1.2.3 | 
|  | ) | 
|  | -- a/go.sum -- | 
|  | example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | var x = blah.Name | 
|  | } | 
|  | ` | 
|  | // Start from a good state, transform to a bad state, and confirm that we | 
|  | // still recover. | 
|  | t.Run("good", func(t *testing.T) { | 
|  | runner.Run(t, known, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/go.mod") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/main.go", "x = "), | 
|  | ) | 
|  | env.RegexpReplace("a/go.mod", "v1.2.3", "v1.2.2") | 
|  | env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/go.mod", "example.com v1.2.2"), | 
|  | ) | 
|  | env.RegexpReplace("a/go.mod", "v1.2.2", "v1.2.3") | 
|  | env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/main.go", "x = "), | 
|  | ) | 
|  | }) | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Confirm that an error in an indirect dependency of a requirement is surfaced | 
|  | // as a diagnostic in the go.mod file. | 
|  | func TestErrorInIndirectDependency(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const badProxy = ` | 
|  | -- example.com@v1.2.3/go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require random.org v1.2.3 // indirect | 
|  | -- example.com@v1.2.3/blah/blah.go -- | 
|  | package blah | 
|  |  | 
|  | const Name = "Blah" | 
|  | -- random.org@v1.2.3/go.mod -- | 
|  | module bob.org | 
|  |  | 
|  | go 1.12 | 
|  | -- random.org@v1.2.3/blah/blah.go -- | 
|  | package hello | 
|  |  | 
|  | const Name = "Hello" | 
|  | ` | 
|  | const module = ` | 
|  | -- a/go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.14 | 
|  |  | 
|  | require example.com v1.2.3 | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | println(blah.Name) | 
|  | } | 
|  | ` | 
|  | RunMultiple{ | 
|  | {"default", WithOptions(ProxyFiles(badProxy), WorkspaceFolders("a"))}, | 
|  | {"nested", WithOptions(ProxyFiles(badProxy))}, | 
|  | }.Run(t, module, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("a/go.mod") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("a/go.mod", "require example.com v1.2.3"), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | // A copy of govim's config_set_env_goflags_mod_readonly test. | 
|  | func TestGovimModReadonly(t *testing.T) { | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.13 | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | println(blah.Name) | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | EditorConfig{ | 
|  | Env: map[string]string{ | 
|  | "GOFLAGS": "-mod=readonly", | 
|  | }, | 
|  | }, | 
|  | ProxyFiles(proxy), | 
|  | Modes(Singleton), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("main.go") | 
|  | original := env.ReadWorkspaceFile("go.mod") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("main.go", `"example.com/blah"`), | 
|  | ) | 
|  | got := env.ReadWorkspaceFile("go.mod") | 
|  | if got != original { | 
|  | t.Fatalf("go.mod file modified:\n%s", tests.Diff(t, original, got)) | 
|  | } | 
|  | env.RunGoCommand("get", "example.com/blah@v1.2.3") | 
|  | env.RunGoCommand("mod", "tidy") | 
|  | env.Await( | 
|  | EmptyDiagnostics("main.go"), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestMultiModuleModDiagnostics(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const mod = ` | 
|  | -- a/go.mod -- | 
|  | module moda.com | 
|  |  | 
|  | go 1.14 | 
|  |  | 
|  | require ( | 
|  | example.com v1.2.3 | 
|  | ) | 
|  | -- a/go.sum -- | 
|  | example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | -- a/main.go -- | 
|  | package main | 
|  |  | 
|  | func main() {} | 
|  | -- b/go.mod -- | 
|  | module modb.com | 
|  |  | 
|  | require example.com v1.2.3 | 
|  |  | 
|  | go 1.14 | 
|  | -- b/main.go -- | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | blah.SaySomething() | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(workspaceProxy), | 
|  | Modes(Experimental), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexpWithMessage("a/go.mod", "example.com v1.2.3", "is not used"), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestModTidyWithBuildTags(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.14 | 
|  | -- main.go -- | 
|  | // +build bob | 
|  |  | 
|  | package main | 
|  |  | 
|  | import "example.com/blah" | 
|  |  | 
|  | func main() { | 
|  | blah.SaySomething() | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(workspaceProxy), | 
|  | EditorConfig{ | 
|  | BuildFlags: []string{"-tags", "bob"}, | 
|  | }, | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("main.go", `"example.com/blah"`), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestModTypoDiagnostic(t *testing.T) { | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | func main() {} | 
|  | ` | 
|  | Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("go.mod") | 
|  | env.RegexpReplace("go.mod", "module", "modul") | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexp("go.mod", "modul"), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSumUpdateFixesDiagnostics(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require ( | 
|  | example.com v1.2.3 | 
|  | ) | 
|  | -- go.sum -- | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "example.com/blah" | 
|  | ) | 
|  |  | 
|  | func main() { | 
|  | println(blah.Name) | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(workspaceProxy), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | d := &protocol.PublishDiagnosticsParams{} | 
|  | env.OpenFile("go.mod") | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.GoSumDiagnostic("go.mod", `example.com v1.2.3`), | 
|  | ReadDiagnostics("go.mod", d), | 
|  | ), | 
|  | ) | 
|  | env.ApplyQuickFixes("go.mod", d.Diagnostics) | 
|  | env.SaveBuffer("go.mod") // Save to trigger diagnostics. | 
|  | env.Await( | 
|  | EmptyDiagnostics("go.mod"), | 
|  | ) | 
|  | }) | 
|  | } | 
|  |  | 
|  | // This test confirms that editing a go.mod file only causes metadata | 
|  | // to be invalidated when it's saved. | 
|  | func TestGoModInvalidatesOnSave(t *testing.T) { | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | func main() { | 
|  | hello() | 
|  | } | 
|  | -- hello.go -- | 
|  | package main | 
|  |  | 
|  | func hello() {} | 
|  | ` | 
|  | WithOptions( | 
|  | // TODO(rFindley) this doesn't work in multi-module workspace mode, because | 
|  | // it keeps around the last parsing modfile. Update this test to also | 
|  | // exercise the workspace module. | 
|  | Modes(Singleton), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("go.mod") | 
|  | env.Await(env.DoneWithOpen()) | 
|  | env.RegexpReplace("go.mod", "module", "modul") | 
|  | // Confirm that we still have metadata with only on-disk edits. | 
|  | env.OpenFile("main.go") | 
|  | file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", "hello")) | 
|  | if filepath.Base(file) != "hello.go" { | 
|  | t.Fatalf("expected definition in hello.go, got %s", file) | 
|  | } | 
|  | // Confirm that we no longer have metadata when the file is saved. | 
|  | env.SaveBufferWithoutActions("go.mod") | 
|  | _, _, err := env.Editor.GoToDefinition(env.Ctx, "main.go", env.RegexpSearch("main.go", "hello")) | 
|  | if err == nil { | 
|  | t.Fatalf("expected error, got none") | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestRemoveUnusedDependency(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const proxy = ` | 
|  | -- hasdep.com@v1.2.3/go.mod -- | 
|  | module hasdep.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require example.com v1.2.3 | 
|  | -- hasdep.com@v1.2.3/a/a.go -- | 
|  | package a | 
|  | -- example.com@v1.2.3/go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  | -- example.com@v1.2.3/blah/blah.go -- | 
|  | package blah | 
|  |  | 
|  | const Name = "Blah" | 
|  | -- random.com@v1.2.3/go.mod -- | 
|  | module random.com | 
|  |  | 
|  | go 1.12 | 
|  | -- random.com@v1.2.3/blah/blah.go -- | 
|  | package blah | 
|  |  | 
|  | const Name = "Blah" | 
|  | ` | 
|  | t.Run("almost tidied", func(t *testing.T) { | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require hasdep.com v1.2.3 | 
|  | -- go.sum -- | 
|  | example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | hasdep.com v1.2.3 h1:00y+N5oD+SpKoqV1zP2VOPawcW65Zb9NebANY3GSzGI= | 
|  | hasdep.com v1.2.3/go.mod h1:ePVZOlez+KZEOejfLPGL2n4i8qiAjrkhQZ4wcImqAes= | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | func main() {} | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(proxy), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("go.mod") | 
|  | d := &protocol.PublishDiagnosticsParams{} | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.DiagnosticAtRegexp("go.mod", "require hasdep.com v1.2.3"), | 
|  | ReadDiagnostics("go.mod", d), | 
|  | ), | 
|  | ) | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.12 | 
|  | ` | 
|  | env.ApplyQuickFixes("go.mod", d.Diagnostics) | 
|  | if got := env.Editor.BufferText("go.mod"); got != want { | 
|  | t.Fatalf("unexpected content in go.mod:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | }) | 
|  |  | 
|  | t.Run("not tidied", func(t *testing.T) { | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require hasdep.com v1.2.3 | 
|  | require random.com v1.2.3 | 
|  | -- go.sum -- | 
|  | example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | hasdep.com v1.2.3 h1:00y+N5oD+SpKoqV1zP2VOPawcW65Zb9NebANY3GSzGI= | 
|  | hasdep.com v1.2.3/go.mod h1:ePVZOlez+KZEOejfLPGL2n4i8qiAjrkhQZ4wcImqAes= | 
|  | random.com v1.2.3 h1:PzYTykzqqH6+qU0dIgh9iPFbfb4Mm8zNBjWWreRKtx0= | 
|  | random.com v1.2.3/go.mod h1:8EGj+8a4Hw1clAp8vbaeHAsKE4sbm536FP7nKyXO+qQ= | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | func main() {} | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(proxy), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | d := &protocol.PublishDiagnosticsParams{} | 
|  | env.OpenFile("go.mod") | 
|  | pos := env.RegexpSearch("go.mod", "require hasdep.com v1.2.3") | 
|  | env.Await( | 
|  | OnceMet( | 
|  | DiagnosticAt("go.mod", pos.Line, pos.Column), | 
|  | ReadDiagnostics("go.mod", d), | 
|  | ), | 
|  | ) | 
|  | const want = `module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require random.com v1.2.3 | 
|  | ` | 
|  | var diagnostics []protocol.Diagnostic | 
|  | for _, d := range d.Diagnostics { | 
|  | if d.Range.Start.Line != uint32(pos.Line) { | 
|  | continue | 
|  | } | 
|  | diagnostics = append(diagnostics, d) | 
|  | } | 
|  | env.ApplyQuickFixes("go.mod", diagnostics) | 
|  | if got := env.Editor.BufferText("go.mod"); got != want { | 
|  | t.Fatalf("unexpected content in go.mod:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSumUpdateQuickFix(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require ( | 
|  | example.com v1.2.3 | 
|  | ) | 
|  | -- go.sum -- | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "example.com/blah" | 
|  | ) | 
|  |  | 
|  | func main() { | 
|  | blah.Hello() | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(workspaceProxy), | 
|  | Modes(Singleton), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("go.mod") | 
|  | params := &protocol.PublishDiagnosticsParams{} | 
|  | env.Await( | 
|  | OnceMet( | 
|  | env.GoSumDiagnostic("go.mod", "example.com"), | 
|  | ReadDiagnostics("go.mod", params), | 
|  | ), | 
|  | ) | 
|  | env.ApplyQuickFixes("go.mod", params.Diagnostics) | 
|  | const want = `example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= | 
|  | example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= | 
|  | ` | 
|  | if got := env.ReadWorkspaceFile("go.sum"); got != want { | 
|  | t.Fatalf("unexpected go.sum contents:\n%s", tests.Diff(t, want, got)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestDownloadDeps(t *testing.T) { | 
|  | testenv.NeedsGo1Point(t, 14) | 
|  |  | 
|  | const proxy = ` | 
|  | -- example.com@v1.2.3/go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | require random.org v1.2.3 | 
|  | -- example.com@v1.2.3/blah/blah.go -- | 
|  | package blah | 
|  |  | 
|  | import "random.org/bye" | 
|  |  | 
|  | func SaySomething() { | 
|  | bye.Goodbye() | 
|  | } | 
|  | -- random.org@v1.2.3/go.mod -- | 
|  | module random.org | 
|  |  | 
|  | go 1.12 | 
|  | -- random.org@v1.2.3/bye/bye.go -- | 
|  | package bye | 
|  |  | 
|  | func Goodbye() { | 
|  | println("Bye") | 
|  | } | 
|  | ` | 
|  |  | 
|  | const mod = ` | 
|  | -- go.mod -- | 
|  | module mod.com | 
|  |  | 
|  | go 1.12 | 
|  | -- go.sum -- | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "example.com/blah" | 
|  | ) | 
|  |  | 
|  | func main() { | 
|  | blah.SaySomething() | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | ProxyFiles(proxy), | 
|  | Modes(Singleton), | 
|  | ).Run(t, mod, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("main.go") | 
|  | d := &protocol.PublishDiagnosticsParams{} | 
|  | env.Await( | 
|  | env.DiagnosticAtRegexpWithMessage("main.go", `"example.com/blah"`, `could not import example.com/blah (no required module provides package "example.com/blah")`), | 
|  | ReadDiagnostics("main.go", d), | 
|  | ) | 
|  | env.ApplyQuickFixes("main.go", d.Diagnostics) | 
|  | env.Await( | 
|  | EmptyDiagnostics("main.go"), | 
|  | NoDiagnostics("go.mod"), | 
|  | ) | 
|  | }) | 
|  | } |