mirror of https://github.com/golang/go.git
1200 lines
27 KiB
Go
1200 lines
27 KiB
Go
// 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"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/tools/gopls/internal/hooks"
|
|
"golang.org/x/tools/internal/lsp/bug"
|
|
. "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) {
|
|
bug.PanicOnBugs = true
|
|
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))
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests that multiple missing dependencies gives good single fixes.
|
|
func TestMissingDependencyFixesWithGoWork(t *testing.T) {
|
|
testenv.NeedsGo1Point(t, 18)
|
|
const mod = `
|
|
-- go.work --
|
|
go 1.18
|
|
|
|
use (
|
|
./a
|
|
)
|
|
-- 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) {
|
|
if runtime.GOOS == "plan9" {
|
|
t.Skipf("skipping test that fails for unknown reasons on plan9; see https://go.dev/issue/50477")
|
|
}
|
|
|
|
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) {
|
|
t.Skipf("Skipping known-flaky test; see https://go.dev/issue/51352.")
|
|
|
|
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"),
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestInvalidGoVersion(t *testing.T) {
|
|
testenv.NeedsGo1Point(t, 14) // Times out on 1.13 for reasons unclear. Not worth worrying about.
|
|
const files = `
|
|
-- go.mod --
|
|
module mod.com
|
|
|
|
go foo
|
|
-- main.go --
|
|
package main
|
|
`
|
|
Run(t, files, func(t *testing.T, env *Env) {
|
|
env.Await(env.DiagnosticAtRegexpWithMessage("go.mod", `go foo`, "invalid go version"))
|
|
env.WriteWorkspaceFile("go.mod", "module mod.com \n\ngo 1.12\n")
|
|
env.Await(EmptyDiagnostics("go.mod"))
|
|
})
|
|
}
|