blob: 95906274b938327cd2e7c4aa1645ae07866c0c3a [file] [log] [blame]
// Copyright 2023 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 workspace
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/protocol/command"
. "golang.org/x/tools/gopls/internal/test/integration"
)
func TestAddAndRemoveGoWork(t *testing.T) {
// Use a workspace with a module in the root directory to exercise the case
// where a go.work is added to the existing root directory. This verifies
// that we're detecting changes to the module source, not just the root
// directory.
const nomod = `
-- go.mod --
module a.com
go 1.16
-- main.go --
package main
func main() {}
-- b/go.mod --
module b.com
go 1.16
-- b/main.go --
package main
func main() {}
`
WithOptions(
Modes(Default),
).Run(t, nomod, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
env.OpenFile("b/main.go")
summary := func(typ cache.ViewType, root, folder string) command.View {
return command.View{
Type: typ.String(),
Root: env.Sandbox.Workdir.URI(root),
Folder: env.Sandbox.Workdir.URI(folder),
}
}
checkViews := func(want ...command.View) {
got := env.Views()
if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" {
t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff)
}
}
// Zero-config gopls makes this work.
env.AfterChange(
NoDiagnostics(ForFile("main.go")),
NoDiagnostics(env.AtRegexp("b/main.go", "package (main)")),
)
checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", "."))
env.WriteWorkspaceFile("go.work", `go 1.16
use (
.
b
)
`)
env.AfterChange(NoDiagnostics())
checkViews(summary(cache.GoWorkView, ".", "."))
// Removing the go.work file should put us back where we started.
env.RemoveWorkspaceFile("go.work")
// Again, zero-config gopls makes this work.
env.AfterChange(
NoDiagnostics(ForFile("main.go")),
NoDiagnostics(env.AtRegexp("b/main.go", "package (main)")),
)
checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", "."))
// Close and reopen b, to ensure the views are adjusted accordingly.
env.CloseBuffer("b/main.go")
env.AfterChange()
checkViews(summary(cache.GoModView, ".", "."))
env.OpenFile("b/main.go")
env.AfterChange()
checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", "."))
})
}
func TestOpenAndClosePorts(t *testing.T) {
// This test checks that as we open and close files requiring a different
// port, the set of Views is adjusted accordingly.
const files = `
-- go.mod --
module a.com/a
go 1.20
-- a_linux.go --
package a
-- a_darwin.go --
package a
-- a_windows.go --
package a
`
WithOptions(
EnvVars{
"GOOS": "linux", // assume that linux is the default GOOS
},
).Run(t, files, func(t *testing.T, env *Env) {
summary := func(envOverlay ...string) command.View {
return command.View{
Type: cache.GoModView.String(),
Root: env.Sandbox.Workdir.URI("."),
Folder: env.Sandbox.Workdir.URI("."),
EnvOverlay: envOverlay,
}
}
checkViews := func(want ...command.View) {
got := env.Views()
if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" {
t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff)
}
}
checkViews(summary())
env.OpenFile("a_linux.go")
checkViews(summary())
env.OpenFile("a_darwin.go")
checkViews(
summary(),
summary("GOARCH=amd64", "GOOS=darwin"),
)
env.OpenFile("a_windows.go")
checkViews(
summary(),
summary("GOARCH=amd64", "GOOS=darwin"),
summary("GOARCH=amd64", "GOOS=windows"),
)
env.CloseBuffer("a_darwin.go")
checkViews(
summary(),
summary("GOARCH=amd64", "GOOS=windows"),
)
env.CloseBuffer("a_linux.go")
checkViews(
summary(),
summary("GOARCH=amd64", "GOOS=windows"),
)
env.CloseBuffer("a_windows.go")
checkViews(summary())
})
}
func TestCriticalErrorsInOrphanedFiles(t *testing.T) {
// This test checks that as we open and close files requiring a different
// port, the set of Views is adjusted accordingly.
const files = `
-- go.mod --
modul golang.org/lsptests/broken
go 1.20
-- a.go --
package broken
const C = 0
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("a.go")
env.AfterChange(
Diagnostics(env.AtRegexp("go.mod", "modul")),
Diagnostics(env.AtRegexp("a.go", "broken"), WithMessage("initialization failed")),
)
})
}
func TestGoModReplace(t *testing.T) {
// This test checks that we treat locally replaced modules as workspace
// modules, according to the "includeReplaceInWorkspace" setting.
const files = `
-- moda/go.mod --
module golang.org/a
require golang.org/b v1.2.3
replace golang.org/b => ../modb
go 1.20
-- moda/a.go --
package a
import "golang.org/b"
const A = b.B
-- modb/go.mod --
module golang.org/b
go 1.20
-- modb/b.go --
package b
const B = 1
`
for useReplace, expectation := range map[bool]Expectation{
true: FileWatchMatching("modb"),
false: NoFileWatchMatching("modb"),
} {
WithOptions(
WorkspaceFolders("moda"),
Settings{
"includeReplaceInWorkspace": useReplace,
},
).Run(t, files, func(t *testing.T, env *Env) {
env.OnceMet(
InitialWorkspaceLoad,
expectation,
)
})
}
}
func TestDisableZeroConfig(t *testing.T) {
// This test checks that we treat locally replaced modules as workspace
// modules, according to the "includeReplaceInWorkspace" setting.
const files = `
-- moda/go.mod --
module golang.org/a
go 1.20
-- moda/a.go --
package a
-- modb/go.mod --
module golang.org/b
go 1.20
-- modb/b.go --
package b
`
WithOptions(
Settings{"zeroConfig": false},
).Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("moda/a.go")
env.OpenFile("modb/b.go")
env.AfterChange()
if got := env.Views(); len(got) != 1 || got[0].Type != cache.AdHocView.String() {
t.Errorf("Views: got %v, want one adhoc view", got)
}
})
}
func TestVendorExcluded(t *testing.T) {
// Test that we don't create Views for vendored modules.
//
// We construct the vendor directory manually here, as `go mod vendor` will
// omit the go.mod file. This synthesizes the setup of Kubernetes, where the
// entire module is vendored through a symlinked directory.
const src = `
-- go.mod --
module example.com/a
go 1.18
require other.com/b v1.0.0
-- a.go --
package a
import "other.com/b"
var _ b.B
-- vendor/modules.txt --
# other.com/b v1.0.0
## explicit; go 1.14
other.com/b
-- vendor/other.com/b/go.mod --
module other.com/b
go 1.14
-- vendor/other.com/b/b.go --
package b
type B int
func _() {
var V int // unused
}
`
WithOptions(
Modes(Default),
).Run(t, src, func(t *testing.T, env *Env) {
env.OpenFile("a.go")
env.AfterChange(NoDiagnostics())
loc := env.GoToDefinition(env.RegexpSearch("a.go", `b\.(B)`))
if !strings.Contains(string(loc.URI), "/vendor/") {
t.Fatalf("Definition(b.B) = %v, want vendored location", loc.URI)
}
env.AfterChange(
Diagnostics(env.AtRegexp("vendor/other.com/b/b.go", "V"), WithMessage("not used")),
)
if views := env.Views(); len(views) != 1 {
t.Errorf("After opening /vendor/, got %d views, want 1. Views:\n%v", len(views), views)
}
})
}