blob: 13ba4e1c305d7d43f89f975db124365a86e92eda [file] [log] [blame]
// 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 codelens
import (
"fmt"
"testing"
"golang.org/x/tools/gopls/internal/hooks"
. "golang.org/x/tools/gopls/internal/lsp/regtest"
"golang.org/x/tools/gopls/internal/lsp/tests/compare"
"golang.org/x/tools/internal/bug"
"golang.org/x/tools/gopls/internal/lsp/command"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/internal/testenv"
)
func TestMain(m *testing.M) {
bug.PanicOnBugs = true
Main(m, hooks.Options)
}
func TestDisablingCodeLens(t *testing.T) {
const workspace = `
-- go.mod --
module codelens.test
go 1.12
-- lib.go --
package lib
type Number int
const (
Zero Number = iota
One
Two
)
//go:generate stringer -type=Number
`
tests := []struct {
label string
enabled map[string]bool
wantCodeLens bool
}{
{
label: "default",
wantCodeLens: true,
},
{
label: "generate disabled",
enabled: map[string]bool{string(command.Generate): false},
wantCodeLens: false,
},
}
for _, test := range tests {
t.Run(test.label, func(t *testing.T) {
WithOptions(
Settings{"codelenses": test.enabled},
).Run(t, workspace, func(t *testing.T, env *Env) {
env.OpenFile("lib.go")
lens := env.CodeLens("lib.go")
if gotCodeLens := len(lens) > 0; gotCodeLens != test.wantCodeLens {
t.Errorf("got codeLens: %t, want %t", gotCodeLens, test.wantCodeLens)
}
})
})
}
}
// This test confirms the full functionality of the code lenses for updating
// dependencies in a go.mod file. It checks for the code lens that suggests
// an update and then executes the command associated with that code lens. A
// regression test for golang/go#39446. It also checks that these code lenses
// only affect the diagnostics and contents of the containing go.mod file.
func TestUpgradeCodelens(t *testing.T) {
testenv.NeedsGo1Point(t, 18) // uses go.work
const proxyWithLatest = `
-- golang.org/x/hello@v1.3.3/go.mod --
module golang.org/x/hello
go 1.12
-- golang.org/x/hello@v1.3.3/hi/hi.go --
package hi
var Goodbye error
-- golang.org/x/hello@v1.2.3/go.mod --
module golang.org/x/hello
go 1.12
-- golang.org/x/hello@v1.2.3/hi/hi.go --
package hi
var Goodbye error
`
const shouldUpdateDep = `
-- go.work --
go 1.18
use (
./a
./b
)
-- a/go.mod --
module mod.com/a
go 1.14
require golang.org/x/hello v1.2.3
-- a/go.sum --
golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg=
golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY=
-- a/main.go --
package main
import "golang.org/x/hello/hi"
func main() {
_ = hi.Goodbye
}
-- b/go.mod --
module mod.com/b
go 1.14
require golang.org/x/hello v1.2.3
-- b/go.sum --
golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg=
golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY=
-- b/main.go --
package main
import (
"golang.org/x/hello/hi"
)
func main() {
_ = hi.Goodbye
}
`
const wantGoModA = `module mod.com/a
go 1.14
require golang.org/x/hello v1.3.3
`
// Applying the diagnostics or running the codelenses for a/go.mod
// should not change the contents of b/go.mod
const wantGoModB = `module mod.com/b
go 1.14
require golang.org/x/hello v1.2.3
`
for _, commandTitle := range []string{
"Upgrade transitive dependencies",
"Upgrade direct dependencies",
} {
t.Run(commandTitle, func(t *testing.T) {
WithOptions(
ProxyFiles(proxyWithLatest),
).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
env.OpenFile("a/go.mod")
env.OpenFile("b/go.mod")
var lens protocol.CodeLens
var found bool
for _, l := range env.CodeLens("a/go.mod") {
if l.Command.Title == commandTitle {
lens = l
found = true
}
}
if !found {
t.Fatalf("found no command with the title %s", commandTitle)
}
if _, err := env.Editor.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{
Command: lens.Command.Command,
Arguments: lens.Command.Arguments,
}); err != nil {
t.Fatal(err)
}
env.AfterChange()
if got := env.BufferText("a/go.mod"); got != wantGoModA {
t.Fatalf("a/go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got))
}
if got := env.BufferText("b/go.mod"); got != wantGoModB {
t.Fatalf("b/go.mod changed unexpectedly:\n%s", compare.Text(wantGoModB, got))
}
})
})
}
for _, vendoring := range []bool{false, true} {
t.Run(fmt.Sprintf("Upgrade individual dependency vendoring=%v", vendoring), func(t *testing.T) {
WithOptions(ProxyFiles(proxyWithLatest)).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
if vendoring {
env.RunGoCommandInDir("a", "mod", "vendor")
}
env.AfterChange()
env.OpenFile("a/go.mod")
env.OpenFile("b/go.mod")
env.ExecuteCodeLensCommand("a/go.mod", command.CheckUpgrades, nil)
d := &protocol.PublishDiagnosticsParams{}
env.Await(
OnceMet(
env.DiagnosticAtRegexpWithMessage("a/go.mod", `require`, "can be upgraded"),
ReadDiagnostics("a/go.mod", d),
// We do not want there to be a diagnostic for b/go.mod,
// but there may be some subtlety in timing here, where this
// should always succeed, but may not actually test the correct
// behavior.
NoMatchingDiagnostics(env.AtRegexp("b/go.mod", `require`)),
),
)
// Check for upgrades in b/go.mod and then clear them.
env.ExecuteCodeLensCommand("b/go.mod", command.CheckUpgrades, nil)
env.Await(env.DiagnosticAtRegexpWithMessage("b/go.mod", `require`, "can be upgraded"))
env.ExecuteCodeLensCommand("b/go.mod", command.ResetGoModDiagnostics, nil)
env.Await(NoDiagnostics("b/go.mod"))
// Apply the diagnostics to a/go.mod.
env.ApplyQuickFixes("a/go.mod", d.Diagnostics)
env.AfterChange()
if got := env.BufferText("a/go.mod"); got != wantGoModA {
t.Fatalf("a/go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got))
}
if got := env.BufferText("b/go.mod"); got != wantGoModB {
t.Fatalf("b/go.mod changed unexpectedly:\n%s", compare.Text(wantGoModB, got))
}
})
})
}
}
func TestUnusedDependenciesCodelens(t *testing.T) {
const proxy = `
-- golang.org/x/hello@v1.0.0/go.mod --
module golang.org/x/hello
go 1.14
-- golang.org/x/hello@v1.0.0/hi/hi.go --
package hi
var Goodbye error
-- golang.org/x/unused@v1.0.0/go.mod --
module golang.org/x/unused
go 1.14
-- golang.org/x/unused@v1.0.0/nouse/nouse.go --
package nouse
var NotUsed error
`
const shouldRemoveDep = `
-- go.mod --
module mod.com
go 1.14
require golang.org/x/hello v1.0.0
require golang.org/x/unused v1.0.0
-- go.sum --
golang.org/x/hello v1.0.0 h1:qbzE1/qT0/zojAMd/JcPsO2Vb9K4Bkeyq0vB2JGMmsw=
golang.org/x/hello v1.0.0/go.mod h1:WW7ER2MRNXWA6c8/4bDIek4Hc/+DofTrMaQQitGXcco=
golang.org/x/unused v1.0.0 h1:LecSbCn5P3vTcxubungSt1Pn4D/WocCaiWOPDC0y0rw=
golang.org/x/unused v1.0.0/go.mod h1:ihoW8SgWzugwwj0N2SfLfPZCxTB1QOVfhMfB5PWTQ8U=
-- main.go --
package main
import "golang.org/x/hello/hi"
func main() {
_ = hi.Goodbye
}
`
WithOptions(ProxyFiles(proxy)).Run(t, shouldRemoveDep, func(t *testing.T, env *Env) {
env.OpenFile("go.mod")
env.ExecuteCodeLensCommand("go.mod", command.Tidy, nil)
env.Await(env.DoneWithChangeWatchedFiles())
got := env.BufferText("go.mod")
const wantGoMod = `module mod.com
go 1.14
require golang.org/x/hello v1.0.0
`
if got != wantGoMod {
t.Fatalf("go.mod tidy failed:\n%s", compare.Text(wantGoMod, got))
}
})
}
func TestRegenerateCgo(t *testing.T) {
testenv.NeedsTool(t, "cgo")
const workspace = `
-- go.mod --
module example.com
go 1.12
-- cgo.go --
package x
/*
int fortythree() { return 42; }
*/
import "C"
func Foo() {
print(C.fortytwo())
}
`
Run(t, workspace, func(t *testing.T, env *Env) {
// Open the file. We have a nonexistant symbol that will break cgo processing.
env.OpenFile("cgo.go")
env.AfterChange(
env.DiagnosticAtRegexpWithMessage("cgo.go", ``, "go list failed to return CompiledGoFiles"),
)
// Fix the C function name. We haven't regenerated cgo, so nothing should be fixed.
env.RegexpReplace("cgo.go", `int fortythree`, "int fortytwo")
env.SaveBuffer("cgo.go")
env.AfterChange(
env.DiagnosticAtRegexpWithMessage("cgo.go", ``, "go list failed to return CompiledGoFiles"),
)
// Regenerate cgo, fixing the diagnostic.
env.ExecuteCodeLensCommand("cgo.go", command.RegenerateCgo, nil)
env.Await(NoDiagnostics("cgo.go"))
})
}