mirror of https://github.com/golang/go.git
internal/lsp/regtest: simplify, consolidate, and document settings
Configuration of LSP settings within the regression test runner had become a bit of a grab-bag: some were configured via explicit fields on EditorConfig, some via the catch-all EditorConfig.Settings field, and others via custom RunOption implementations. Consolidate these fields as follows: - Add an EnvVars and Settings field, for configuring environment and LSP settings. - Eliminate the EditorConfig RunOption wrapper. RunOptions help build the config. - Remove RunOptions that just wrap a key-value settings pair. By definition settings are user-facing and cannot change without breaking compatibility. Therefore, our tests can and should set the exact string keys they are using. - Eliminate the unused SendPID option. Also clean up some logic to change configuration. For golang/go#39384 Change-Id: Id5d1614f139550cbc62db2bab1d1e1f545ad9393 Reviewed-on: https://go-review.googlesource.com/c/tools/+/416876 TryBot-Result: Gopher Robot <gobot@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> Run-TryBot: Robert Findley <rfindley@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
3db2cdc060
commit
6e6f3131ec
|
|
@ -93,14 +93,14 @@ func TestBenchmarkSymbols(t *testing.T) {
|
|||
}
|
||||
|
||||
opts := benchmarkOptions(symbolOptions.workdir)
|
||||
conf := EditorConfig{}
|
||||
settings := make(Settings)
|
||||
if symbolOptions.matcher != "" {
|
||||
conf.SymbolMatcher = &symbolOptions.matcher
|
||||
settings["symbolMatcher"] = symbolOptions.matcher
|
||||
}
|
||||
if symbolOptions.style != "" {
|
||||
conf.SymbolStyle = &symbolOptions.style
|
||||
settings["symbolStyle"] = symbolOptions.style
|
||||
}
|
||||
opts = append(opts, conf)
|
||||
opts = append(opts, settings)
|
||||
|
||||
WithOptions(opts...).Run(t, "", func(t *testing.T, env *Env) {
|
||||
// We can't Await in this test, since we have disabled hooks. Instead, run
|
||||
|
|
@ -200,9 +200,10 @@ func TestBenchmarkDidChange(t *testing.T) {
|
|||
// Always run it in isolation since it measures global heap usage.
|
||||
//
|
||||
// Kubernetes example:
|
||||
// $ go test -run=TestPrintMemStats -didchange_dir=$HOME/w/kubernetes
|
||||
// TotalAlloc: 5766 MB
|
||||
// HeapAlloc: 1984 MB
|
||||
//
|
||||
// $ go test -run=TestPrintMemStats -didchange_dir=$HOME/w/kubernetes
|
||||
// TotalAlloc: 5766 MB
|
||||
// HeapAlloc: 1984 MB
|
||||
//
|
||||
// Both figures exhibit variance of less than 1%.
|
||||
func TestPrintMemStats(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ const (
|
|||
for _, test := range tests {
|
||||
t.Run(test.label, func(t *testing.T) {
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
CodeLenses: test.enabled,
|
||||
},
|
||||
Settings{"codelenses": test.enabled},
|
||||
).Run(t, workspace, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("lib.go")
|
||||
lens := env.CodeLens("lib.go")
|
||||
|
|
@ -308,10 +306,11 @@ func main() {
|
|||
}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
CodeLenses: map[string]bool{
|
||||
Settings{
|
||||
"codelenses": map[string]bool{
|
||||
"gc_details": true,
|
||||
}},
|
||||
},
|
||||
},
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("main.go")
|
||||
env.ExecuteCodeLensCommand("main.go", command.GCDetails)
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ func main() {
|
|||
}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{WindowsLineEndings: true},
|
||||
WindowsLineEndings(),
|
||||
).Run(t, src, func(t *testing.T, env *Env) {
|
||||
// Trigger unimported completions for the example.com/blah package.
|
||||
env.OpenFile("main.go")
|
||||
|
|
|
|||
|
|
@ -21,11 +21,7 @@ func TestBugNotification(t *testing.T) {
|
|||
// server.
|
||||
WithOptions(
|
||||
Modes(Singleton), // must be in-process to receive the bug report below
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"showBugReports": true,
|
||||
},
|
||||
},
|
||||
Settings{"showBugReports": true},
|
||||
).Run(t, "", func(t *testing.T, env *Env) {
|
||||
const desc = "got a bug"
|
||||
bug.Report(desc, nil)
|
||||
|
|
|
|||
|
|
@ -471,12 +471,11 @@ func _() {
|
|||
}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GOPATH": "",
|
||||
"GO111MODULE": "off",
|
||||
},
|
||||
}).Run(t, files, func(t *testing.T, env *Env) {
|
||||
EnvVars{
|
||||
"GOPATH": "",
|
||||
"GO111MODULE": "off",
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("main.go")
|
||||
env.Await(env.DiagnosticAtRegexp("main.go", "fmt"))
|
||||
env.SaveBuffer("main.go")
|
||||
|
|
@ -500,8 +499,9 @@ package x
|
|||
|
||||
var X = 0
|
||||
`
|
||||
editorConfig := EditorConfig{Env: map[string]string{"GOFLAGS": "-tags=foo"}}
|
||||
WithOptions(editorConfig).Run(t, files, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
EnvVars{"GOFLAGS": "-tags=foo"},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("main.go")
|
||||
env.OrganizeImports("main.go")
|
||||
env.Await(EmptyDiagnostics("main.go"))
|
||||
|
|
@ -573,9 +573,9 @@ hi mom
|
|||
`
|
||||
for _, go111module := range []string{"on", "off", ""} {
|
||||
t.Run(fmt.Sprintf("GO111MODULE_%v", go111module), func(t *testing.T) {
|
||||
WithOptions(EditorConfig{
|
||||
Env: map[string]string{"GO111MODULE": go111module},
|
||||
}).Run(t, files, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
EnvVars{"GO111MODULE": go111module},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.Await(
|
||||
NoOutstandingWork(),
|
||||
)
|
||||
|
|
@ -605,11 +605,7 @@ func main() {
|
|||
`
|
||||
WithOptions(
|
||||
InGOPATH(),
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GO111MODULE": "off",
|
||||
},
|
||||
},
|
||||
EnvVars{"GO111MODULE": "off"},
|
||||
).Run(t, collision, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("x/x.go")
|
||||
env.Await(
|
||||
|
|
@ -1236,7 +1232,7 @@ func main() {
|
|||
})
|
||||
WithOptions(
|
||||
WorkspaceFolders("a"),
|
||||
LimitWorkspaceScope(),
|
||||
Settings{"expandWorkspaceToModule": false},
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a/main.go")
|
||||
env.Await(
|
||||
|
|
@ -1267,11 +1263,7 @@ func main() {
|
|||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"staticcheck": true,
|
||||
},
|
||||
},
|
||||
Settings{"staticcheck": true},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("main.go")
|
||||
var d protocol.PublishDiagnosticsParams
|
||||
|
|
@ -1381,9 +1373,7 @@ func b(c bytes.Buffer) {
|
|||
}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
AllExperiments: true,
|
||||
},
|
||||
Settings{"allExperiments": true},
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
// Confirm that the setting doesn't cause any warnings.
|
||||
env.Await(NoShowMessage())
|
||||
|
|
@ -1495,11 +1485,7 @@ package foo_
|
|||
WithOptions(
|
||||
ProxyFiles(proxy),
|
||||
InGOPATH(),
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GO111MODULE": "off",
|
||||
},
|
||||
},
|
||||
EnvVars{"GO111MODULE": "off"},
|
||||
).Run(t, contents, func(t *testing.T, env *Env) {
|
||||
// Simulate typing character by character.
|
||||
env.OpenFile("foo/foo_test.go")
|
||||
|
|
@ -1698,9 +1684,7 @@ import (
|
|||
t.Run("GOPATH", func(t *testing.T) {
|
||||
WithOptions(
|
||||
InGOPATH(),
|
||||
EditorConfig{
|
||||
Env: map[string]string{"GO111MODULE": "off"},
|
||||
},
|
||||
EnvVars{"GO111MODULE": "off"},
|
||||
Modes(Singleton),
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.Await(
|
||||
|
|
@ -1729,11 +1713,7 @@ package b
|
|||
t.Run("GO111MODULE="+go111module, func(t *testing.T) {
|
||||
WithOptions(
|
||||
Modes(Singleton),
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GO111MODULE": go111module,
|
||||
},
|
||||
},
|
||||
EnvVars{"GO111MODULE": go111module},
|
||||
).Run(t, modules, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a/a.go")
|
||||
env.OpenFile("b/go.mod")
|
||||
|
|
@ -1750,11 +1730,7 @@ package b
|
|||
t.Run("GOPATH_GO111MODULE_auto", func(t *testing.T) {
|
||||
WithOptions(
|
||||
Modes(Singleton),
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GO111MODULE": "auto",
|
||||
},
|
||||
},
|
||||
EnvVars{"GO111MODULE": "auto"},
|
||||
InGOPATH(),
|
||||
).Run(t, modules, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a/a.go")
|
||||
|
|
@ -2026,9 +2002,7 @@ package a
|
|||
func Hello() {}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
ExperimentalUseInvalidMetadata: true,
|
||||
},
|
||||
Settings{"experimentalUseInvalidMetadata": true},
|
||||
Modes(Singleton),
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("go.mod")
|
||||
|
|
@ -2082,9 +2056,7 @@ package main
|
|||
func _() {}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
ExperimentalUseInvalidMetadata: true,
|
||||
},
|
||||
Settings{"experimentalUseInvalidMetadata": true},
|
||||
// ExperimentalWorkspaceModule has a different failure mode for this
|
||||
// case.
|
||||
Modes(Singleton),
|
||||
|
|
|
|||
|
|
@ -56,10 +56,8 @@ const (
|
|||
for _, test := range tests {
|
||||
t.Run(test.label, func(t *testing.T) {
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"hints": test.enabled,
|
||||
},
|
||||
Settings{
|
||||
"hints": test.enabled,
|
||||
},
|
||||
).Run(t, workspace, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("lib.go")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
. "golang.org/x/tools/internal/lsp/regtest"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/fake"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
)
|
||||
|
||||
|
|
@ -40,12 +39,11 @@ var FooErr = errors.New("foo")
|
|||
env.DoneWithOpen(),
|
||||
NoDiagnostics("a/a.go"),
|
||||
)
|
||||
cfg := &fake.EditorConfig{}
|
||||
*cfg = env.Editor.Config
|
||||
cfg := env.Editor.Config()
|
||||
cfg.Settings = map[string]interface{}{
|
||||
"staticcheck": true,
|
||||
}
|
||||
env.ChangeConfiguration(t, cfg)
|
||||
env.ChangeConfiguration(cfg)
|
||||
env.Await(
|
||||
DiagnosticAt("a/a.go", 5, 4),
|
||||
)
|
||||
|
|
@ -70,11 +68,9 @@ import "errors"
|
|||
var FooErr = errors.New("foo")
|
||||
`
|
||||
|
||||
WithOptions(EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"staticcheck": true,
|
||||
},
|
||||
}).Run(t, files, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
Settings{"staticcheck": true},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.Await(ShownMessage("staticcheck is not supported"))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,9 +162,7 @@ func main() {}
|
|||
} {
|
||||
t.Run(tt.importShortcut, func(t *testing.T) {
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
ImportShortcut: tt.importShortcut,
|
||||
},
|
||||
Settings{"importShortcut": tt.importShortcut},
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("main.go")
|
||||
file, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"fmt"`))
|
||||
|
|
|
|||
|
|
@ -352,10 +352,8 @@ const Bar = 42
|
|||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"gofumpt": true,
|
||||
},
|
||||
Settings{
|
||||
"gofumpt": true,
|
||||
},
|
||||
).Run(t, input, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("foo.go")
|
||||
|
|
|
|||
|
|
@ -153,9 +153,8 @@ var _, _ = x.X, y.Y
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(modcache)
|
||||
editorConfig := EditorConfig{Env: map[string]string{"GOMODCACHE": modcache}}
|
||||
WithOptions(
|
||||
editorConfig,
|
||||
EnvVars{"GOMODCACHE": modcache},
|
||||
ProxyFiles(proxy),
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("main.go")
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ const Hello = "Hello"
|
|||
}
|
||||
|
||||
// Then change the environment to make these links private.
|
||||
env.ChangeEnv(map[string]string{"GOPRIVATE": "import.test"})
|
||||
cfg := env.Editor.Config()
|
||||
cfg.Env = map[string]string{"GOPRIVATE": "import.test"}
|
||||
env.ChangeConfiguration(cfg)
|
||||
|
||||
// Finally, verify that the links are gone.
|
||||
content, _ = env.Hover("main.go", env.RegexpSearch("main.go", "pkg.Hello"))
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ func main() {}
|
|||
`
|
||||
WithOptions(
|
||||
Modes(Singleton),
|
||||
EditorConfig{
|
||||
AllExperiments: true,
|
||||
},
|
||||
Settings{"allExperiments": true},
|
||||
).Run(t, src, func(t *testing.T, env *Env) {
|
||||
params := &protocol.SemanticTokensParams{}
|
||||
const badURI = "http://foo"
|
||||
|
|
|
|||
|
|
@ -24,11 +24,7 @@ func main() {
|
|||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"directoryFilters": []string{""},
|
||||
},
|
||||
},
|
||||
Settings{"directoryFilters": []string{""}},
|
||||
).Run(t, src, func(t *testing.T, env *Env) {
|
||||
// No need to do anything. Issue golang/go#51843 is triggered by the empty
|
||||
// directory filter above.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func runShared(t *testing.T, testFunc func(env1 *Env, env2 *Env)) {
|
|||
WithOptions(Modes(modes)).Run(t, sharedProgram, func(t *testing.T, env1 *Env) {
|
||||
// Create a second test session connected to the same workspace and server
|
||||
// as the first.
|
||||
env2, cleanup := NewEnv(env1.Ctx, t, env1.Sandbox, env1.Server, env1.Editor.Config, true)
|
||||
env2, cleanup := NewEnv(env1.Ctx, t, env1.Sandbox, env1.Server, env1.Editor.Config(), true)
|
||||
defer cleanup()
|
||||
env2.Await(InitialWorkspaceLoad)
|
||||
testFunc(env1, env2)
|
||||
|
|
|
|||
|
|
@ -60,11 +60,9 @@ func testGenerics[P *T, T any](p P) {
|
|||
var FooErr error = errors.New("foo")
|
||||
`
|
||||
|
||||
WithOptions(EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"staticcheck": true,
|
||||
},
|
||||
}).Run(t, files, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
Settings{"staticcheck": true},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a/a.go")
|
||||
env.Await(
|
||||
env.DiagnosticAtRegexpFromSource("a/a.go", "sort.Slice", "sortslice"),
|
||||
|
|
|
|||
|
|
@ -72,9 +72,7 @@ const (
|
|||
|
||||
var symbolMatcher = string(source.SymbolFastFuzzy)
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
SymbolMatcher: &symbolMatcher,
|
||||
},
|
||||
Settings{"symbolMatcher": symbolMatcher},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
want := []string{
|
||||
"Foo", // prefer exact segment matches first
|
||||
|
|
@ -105,9 +103,7 @@ const (
|
|||
|
||||
var symbolMatcher = string(source.SymbolFastFuzzy)
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
SymbolMatcher: &symbolMatcher,
|
||||
},
|
||||
Settings{"symbolMatcher": symbolMatcher},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
compareSymbols(t, env.WorkspaceSymbol("ABC"), []string{"ABC", "AxxBxxCxx"})
|
||||
compareSymbols(t, env.WorkspaceSymbol("'ABC"), []string{"ABC"})
|
||||
|
|
|
|||
|
|
@ -740,11 +740,7 @@ func main() {
|
|||
}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GOFLAGS": "-mod=readonly",
|
||||
},
|
||||
},
|
||||
EnvVars{"GOFLAGS": "-mod=readonly"},
|
||||
ProxyFiles(proxy),
|
||||
Modes(Singleton),
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
|
|
@ -830,9 +826,7 @@ func main() {
|
|||
`
|
||||
WithOptions(
|
||||
ProxyFiles(workspaceProxy),
|
||||
EditorConfig{
|
||||
BuildFlags: []string{"-tags", "bob"},
|
||||
},
|
||||
Settings{"buildFlags": []string{"-tags", "bob"}},
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.Await(
|
||||
env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
|
||||
|
|
|
|||
|
|
@ -35,11 +35,9 @@ go 1.17
|
|||
{{end}}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
"semanticTokens": true,
|
||||
},
|
||||
Settings{
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
"semanticTokens": true,
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
var p protocol.SemanticTokensParams
|
||||
|
|
@ -66,11 +64,9 @@ Hello {{}} <-- missing body
|
|||
{{end}}
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
"semanticTokens": true,
|
||||
},
|
||||
Settings{
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
"semanticTokens": true,
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
// TODO: can we move this diagnostic onto {{}}?
|
||||
|
|
@ -112,11 +108,9 @@ B {{}} <-- missing body
|
|||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
},
|
||||
DirectoryFilters: []string{"-b"},
|
||||
Settings{
|
||||
"directoryFilters": []string{"-b"},
|
||||
"templateExtensions": []string{"tmpl"},
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.Await(
|
||||
|
|
@ -184,10 +178,8 @@ go 1.12
|
|||
`
|
||||
|
||||
WithOptions(
|
||||
EditorConfig{
|
||||
Settings: map[string]interface{}{
|
||||
"templateExtensions": []string{"tmpl", "gotmpl"},
|
||||
},
|
||||
Settings{
|
||||
"templateExtensions": []string{"tmpl", "gotmpl"},
|
||||
},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a.tmpl")
|
||||
|
|
|
|||
|
|
@ -389,9 +389,9 @@ func _() {
|
|||
package a
|
||||
`
|
||||
t.Run("close then delete", func(t *testing.T) {
|
||||
WithOptions(EditorConfig{
|
||||
VerboseOutput: true,
|
||||
}).Run(t, pkg, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
Settings{"verboseOutput": true},
|
||||
).Run(t, pkg, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a/a.go")
|
||||
env.OpenFile("a/a_unneeded.go")
|
||||
env.Await(
|
||||
|
|
@ -424,7 +424,7 @@ package a
|
|||
|
||||
t.Run("delete then close", func(t *testing.T) {
|
||||
WithOptions(
|
||||
EditorConfig{VerboseOutput: true},
|
||||
Settings{"verboseOutput": true},
|
||||
).Run(t, pkg, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a/a.go")
|
||||
env.OpenFile("a/a_unneeded.go")
|
||||
|
|
@ -620,11 +620,7 @@ func main() {
|
|||
`
|
||||
WithOptions(
|
||||
InGOPATH(),
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GO111MODULE": "auto",
|
||||
},
|
||||
},
|
||||
EnvVars{"GO111MODULE": "auto"},
|
||||
Modes(Experimental), // module is in a subdirectory
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("foo/main.go")
|
||||
|
|
@ -663,11 +659,7 @@ func main() {
|
|||
`
|
||||
WithOptions(
|
||||
InGOPATH(),
|
||||
EditorConfig{
|
||||
Env: map[string]string{
|
||||
"GO111MODULE": "auto",
|
||||
},
|
||||
},
|
||||
EnvVars{"GO111MODULE": "auto"},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("foo/main.go")
|
||||
env.RemoveWorkspaceFile("foo/go.mod")
|
||||
|
|
|
|||
|
|
@ -1036,10 +1036,10 @@ package exclude
|
|||
|
||||
const _ = Nonexistant
|
||||
`
|
||||
cfg := EditorConfig{
|
||||
DirectoryFilters: []string{"-exclude"},
|
||||
}
|
||||
WithOptions(cfg).Run(t, files, func(t *testing.T, env *Env) {
|
||||
|
||||
WithOptions(
|
||||
Settings{"directoryFilters": []string{"-exclude"}},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.Await(NoDiagnostics("exclude/x.go"))
|
||||
})
|
||||
}
|
||||
|
|
@ -1064,10 +1064,9 @@ const _ = Nonexistant // should be ignored, since this is a non-workspace packag
|
|||
const X = 1
|
||||
`
|
||||
|
||||
cfg := EditorConfig{
|
||||
DirectoryFilters: []string{"-exclude"},
|
||||
}
|
||||
WithOptions(cfg).Run(t, files, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
Settings{"directoryFilters": []string{"-exclude"}},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.Await(
|
||||
NoDiagnostics("exclude/exclude.go"), // filtered out
|
||||
NoDiagnostics("include/include.go"), // successfully builds
|
||||
|
|
@ -1114,10 +1113,11 @@ go 1.12
|
|||
-- exclude.com@v1.0.0/exclude.go --
|
||||
package exclude
|
||||
`
|
||||
cfg := EditorConfig{
|
||||
DirectoryFilters: []string{"-exclude"},
|
||||
}
|
||||
WithOptions(cfg, Modes(Experimental), ProxyFiles(proxy)).Run(t, files, func(t *testing.T, env *Env) {
|
||||
WithOptions(
|
||||
Modes(Experimental),
|
||||
ProxyFiles(proxy),
|
||||
Settings{"directoryFilters": []string{"-exclude"}},
|
||||
).Run(t, files, func(t *testing.T, env *Env) {
|
||||
env.Await(env.DiagnosticAtRegexp("include/include.go", `exclude.(X)`))
|
||||
})
|
||||
}
|
||||
|
|
@ -1204,9 +1204,7 @@ go 1.12
|
|||
package main
|
||||
`
|
||||
WithOptions(
|
||||
EditorConfig{Env: map[string]string{
|
||||
"GOPATH": filepath.FromSlash("$SANDBOX_WORKDIR/gopath"),
|
||||
}},
|
||||
EnvVars{"GOPATH": filepath.FromSlash("$SANDBOX_WORKDIR/gopath")},
|
||||
Modes(Singleton),
|
||||
).Run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.Await(
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func (c *Client) Configuration(_ context.Context, p *protocol.ParamConfiguration
|
|||
if item.Section != "gopls" {
|
||||
continue
|
||||
}
|
||||
results[i] = c.editor.configuration()
|
||||
results[i] = c.editor.settings()
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import (
|
|||
// Editor is a fake editor client. It keeps track of client state and can be
|
||||
// used for writing LSP tests.
|
||||
type Editor struct {
|
||||
Config EditorConfig
|
||||
|
||||
// Server, client, and sandbox are concurrency safe and written only
|
||||
// at construction time, so do not require synchronization.
|
||||
|
|
@ -35,13 +34,10 @@ type Editor struct {
|
|||
sandbox *Sandbox
|
||||
defaultEnv map[string]string
|
||||
|
||||
// Since this editor is intended just for testing, we use very coarse
|
||||
// locking.
|
||||
mu sync.Mutex
|
||||
// Editor state.
|
||||
buffers map[string]buffer
|
||||
// Capabilities / Options
|
||||
serverCapabilities protocol.ServerCapabilities
|
||||
mu sync.Mutex // guards config, buffers, serverCapabilities
|
||||
config EditorConfig // editor configuration
|
||||
buffers map[string]buffer // open buffers
|
||||
serverCapabilities protocol.ServerCapabilities // capabilities / options
|
||||
|
||||
// Call metrics for the purpose of expectations. This is done in an ad-hoc
|
||||
// manner for now. Perhaps in the future we should do something more
|
||||
|
|
@ -77,21 +73,11 @@ func (b buffer) text() string {
|
|||
//
|
||||
// The zero value for EditorConfig should correspond to its defaults.
|
||||
type EditorConfig struct {
|
||||
Env map[string]string
|
||||
BuildFlags []string
|
||||
|
||||
// CodeLenses is a map defining whether codelens are enabled, keyed by the
|
||||
// codeLens command. CodeLenses which are not present in this map are left in
|
||||
// their default state.
|
||||
CodeLenses map[string]bool
|
||||
|
||||
// SymbolMatcher is the config associated with the "symbolMatcher" gopls
|
||||
// config option.
|
||||
SymbolMatcher, SymbolStyle *string
|
||||
|
||||
// LimitWorkspaceScope is true if the user does not want to expand their
|
||||
// workspace scope to the entire module.
|
||||
LimitWorkspaceScope bool
|
||||
// Env holds environment variables to apply on top of the default editor
|
||||
// environment. When applying these variables, the special string
|
||||
// $SANDBOX_WORKDIR is replaced by the absolute path to the sandbox working
|
||||
// directory.
|
||||
Env map[string]string
|
||||
|
||||
// WorkspaceFolders is the workspace folders to configure on the LSP server,
|
||||
// relative to the sandbox workdir.
|
||||
|
|
@ -101,14 +87,6 @@ type EditorConfig struct {
|
|||
// To explicitly send no workspace folders, use an empty (non-nil) slice.
|
||||
WorkspaceFolders []string
|
||||
|
||||
// AllExperiments sets the "allExperiments" configuration, which enables
|
||||
// all of gopls's opt-in settings.
|
||||
AllExperiments bool
|
||||
|
||||
// Whether to send the current process ID, for testing data that is joined to
|
||||
// the PID. This can only be set by one test.
|
||||
SendPID bool
|
||||
|
||||
// Whether to edit files with windows line endings.
|
||||
WindowsLineEndings bool
|
||||
|
||||
|
|
@ -120,14 +98,8 @@ type EditorConfig struct {
|
|||
// "gotmpl" -> ".*tmpl"
|
||||
FileAssociations map[string]string
|
||||
|
||||
// Settings holds arbitrary additional settings to apply to the gopls config.
|
||||
// TODO(rfindley): replace existing EditorConfig fields with Settings.
|
||||
// Settings holds user-provided configuration for the LSP server.
|
||||
Settings map[string]interface{}
|
||||
|
||||
ImportShortcut string
|
||||
DirectoryFilters []string
|
||||
VerboseOutput bool
|
||||
ExperimentalUseInvalidMetadata bool
|
||||
}
|
||||
|
||||
// NewEditor Creates a new Editor.
|
||||
|
|
@ -136,7 +108,7 @@ func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
|
|||
buffers: make(map[string]buffer),
|
||||
sandbox: sandbox,
|
||||
defaultEnv: sandbox.GoEnv(),
|
||||
Config: config,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +127,7 @@ func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHo
|
|||
protocol.Handlers(
|
||||
protocol.ClientHandler(e.client,
|
||||
jsonrpc2.MethodNotFound)))
|
||||
if err := e.initialize(ctx, e.Config.WorkspaceFolders); err != nil {
|
||||
if err := e.initialize(ctx, e.config.WorkspaceFolders); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.sandbox.Workdir.AddWatcher(e.onFileChanges)
|
||||
|
|
@ -213,65 +185,47 @@ func (e *Editor) Client() *Client {
|
|||
return e.client
|
||||
}
|
||||
|
||||
func (e *Editor) overlayEnv() map[string]string {
|
||||
// settings builds the settings map for use in LSP settings
|
||||
// RPCs.
|
||||
func (e *Editor) settings() map[string]interface{} {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
env := make(map[string]string)
|
||||
for k, v := range e.defaultEnv {
|
||||
env[k] = v
|
||||
}
|
||||
for k, v := range e.config.Env {
|
||||
env[k] = v
|
||||
}
|
||||
for k, v := range env {
|
||||
v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
|
||||
env[k] = v
|
||||
}
|
||||
for k, v := range e.Config.Env {
|
||||
v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
|
||||
env[k] = v
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func (e *Editor) configuration() map[string]interface{} {
|
||||
config := map[string]interface{}{
|
||||
settings := map[string]interface{}{
|
||||
"env": env,
|
||||
|
||||
// Use verbose progress reporting so that regtests can assert on
|
||||
// asynchronous operations being completed (such as diagnosing a snapshot).
|
||||
"verboseWorkDoneProgress": true,
|
||||
"env": e.overlayEnv(),
|
||||
"expandWorkspaceToModule": !e.Config.LimitWorkspaceScope,
|
||||
"completionBudget": "10s",
|
||||
|
||||
// Set a generous completion budget, so that tests don't flake because
|
||||
// completions are too slow.
|
||||
"completionBudget": "10s",
|
||||
|
||||
// Shorten the diagnostic delay to speed up test execution (else we'd add
|
||||
// the default delay to each assertion about diagnostics)
|
||||
"diagnosticsDelay": "10ms",
|
||||
}
|
||||
|
||||
for k, v := range e.Config.Settings {
|
||||
config[k] = v
|
||||
for k, v := range e.config.Settings {
|
||||
if k == "env" {
|
||||
panic("must not provide env via the EditorConfig.Settings field: use the EditorConfig.Env field instead")
|
||||
}
|
||||
settings[k] = v
|
||||
}
|
||||
|
||||
if e.Config.BuildFlags != nil {
|
||||
config["buildFlags"] = e.Config.BuildFlags
|
||||
}
|
||||
if e.Config.DirectoryFilters != nil {
|
||||
config["directoryFilters"] = e.Config.DirectoryFilters
|
||||
}
|
||||
if e.Config.ExperimentalUseInvalidMetadata {
|
||||
config["experimentalUseInvalidMetadata"] = true
|
||||
}
|
||||
if e.Config.CodeLenses != nil {
|
||||
config["codelenses"] = e.Config.CodeLenses
|
||||
}
|
||||
if e.Config.SymbolMatcher != nil {
|
||||
config["symbolMatcher"] = *e.Config.SymbolMatcher
|
||||
}
|
||||
if e.Config.SymbolStyle != nil {
|
||||
config["symbolStyle"] = *e.Config.SymbolStyle
|
||||
}
|
||||
if e.Config.AllExperiments {
|
||||
config["allExperiments"] = true
|
||||
}
|
||||
|
||||
if e.Config.VerboseOutput {
|
||||
config["verboseOutput"] = true
|
||||
}
|
||||
|
||||
if e.Config.ImportShortcut != "" {
|
||||
config["importShortcut"] = e.Config.ImportShortcut
|
||||
}
|
||||
|
||||
config["diagnosticsDelay"] = "10ms"
|
||||
|
||||
// ExperimentalWorkspaceModule is only set as a mode, not a configuration.
|
||||
return config
|
||||
return settings
|
||||
}
|
||||
|
||||
func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) error {
|
||||
|
|
@ -293,10 +247,7 @@ func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) erro
|
|||
params.Capabilities.Window.WorkDoneProgress = true
|
||||
// TODO: set client capabilities
|
||||
params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated}
|
||||
params.InitializationOptions = e.configuration()
|
||||
if e.Config.SendPID {
|
||||
params.ProcessID = int32(os.Getpid())
|
||||
}
|
||||
params.InitializationOptions = e.settings()
|
||||
|
||||
params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
|
||||
params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
|
||||
|
|
@ -397,20 +348,21 @@ func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
|
|||
}
|
||||
|
||||
func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content string) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
buf := buffer{
|
||||
windowsLineEndings: e.Config.WindowsLineEndings,
|
||||
windowsLineEndings: e.config.WindowsLineEndings,
|
||||
version: 1,
|
||||
path: path,
|
||||
lines: lines(content),
|
||||
dirty: dirty,
|
||||
}
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.buffers[path] = buf
|
||||
|
||||
item := protocol.TextDocumentItem{
|
||||
URI: e.sandbox.Workdir.URI(buf.path),
|
||||
LanguageID: e.languageID(buf.path),
|
||||
LanguageID: languageID(buf.path, e.config.FileAssociations),
|
||||
Version: int32(buf.version),
|
||||
Text: buf.text(),
|
||||
}
|
||||
|
|
@ -436,9 +388,11 @@ var defaultFileAssociations = map[string]*regexp.Regexp{
|
|||
"gotmpl": regexp.MustCompile(`^.*tmpl$`),
|
||||
}
|
||||
|
||||
func (e *Editor) languageID(p string) string {
|
||||
// languageID returns the language identifier for the path p given the user
|
||||
// configured fileAssociations.
|
||||
func languageID(p string, fileAssociations map[string]string) string {
|
||||
base := path.Base(p)
|
||||
for lang, re := range e.Config.FileAssociations {
|
||||
for lang, re := range fileAssociations {
|
||||
re := regexp.MustCompile(re)
|
||||
if re.MatchString(base) {
|
||||
return lang
|
||||
|
|
@ -1205,6 +1159,30 @@ func (e *Editor) applyProtocolEdit(ctx context.Context, change protocol.TextDocu
|
|||
return e.EditBuffer(ctx, path, fakeEdits)
|
||||
}
|
||||
|
||||
// Config returns the current editor configuration.
|
||||
func (e *Editor) Config() EditorConfig {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.config
|
||||
}
|
||||
|
||||
// ChangeConfiguration sets the new editor configuration, and if applicable
|
||||
// sends a didChangeConfiguration notification.
|
||||
//
|
||||
// An error is returned if the change notification failed to send.
|
||||
func (e *Editor) ChangeConfiguration(ctx context.Context, newConfig EditorConfig) error {
|
||||
e.mu.Lock()
|
||||
e.config = newConfig
|
||||
e.mu.Unlock() // don't hold e.mu during server calls
|
||||
if e.Server != nil {
|
||||
var params protocol.DidChangeConfigurationParams // empty: gopls ignores the Settings field
|
||||
if err := e.Server.DidChangeConfiguration(ctx, ¶ms); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CodeAction executes a codeAction request on the server.
|
||||
func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
|
||||
if e.Server == nil {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,11 @@ type condition struct {
|
|||
// NewEnv creates a new test environment using the given scratch environment
|
||||
// and gopls server.
|
||||
//
|
||||
// The resulting func must be called to close the jsonrpc2 connection.
|
||||
// The resulting cleanup func must be called to close the jsonrpc2 connection.
|
||||
//
|
||||
// TODO(rfindley): this function provides questionable value. Consider
|
||||
// refactoring to move things like creating the server outside of this
|
||||
// constructor.
|
||||
func NewEnv(ctx context.Context, tb testing.TB, sandbox *fake.Sandbox, ts servertest.Connector, editorConfig fake.EditorConfig, withHooks bool) (_ *Env, cleanup func()) {
|
||||
tb.Helper()
|
||||
|
||||
|
|
|
|||
|
|
@ -133,17 +133,27 @@ func Options(hook func(*source.Options)) RunOption {
|
|||
})
|
||||
}
|
||||
|
||||
func SendPID() RunOption {
|
||||
// WindowsLineEndings configures the editor to use windows line endings.
|
||||
func WindowsLineEndings() RunOption {
|
||||
return optionSetter(func(opts *runConfig) {
|
||||
opts.editor.SendPID = true
|
||||
opts.editor.WindowsLineEndings = true
|
||||
})
|
||||
}
|
||||
|
||||
// EditorConfig is a RunOption option that configured the regtest editor.
|
||||
type EditorConfig fake.EditorConfig
|
||||
// Settings is a RunOption that sets user-provided configuration for the LSP
|
||||
// server.
|
||||
//
|
||||
// As a special case, the env setting must not be provided via Settings: use
|
||||
// EnvVars instead.
|
||||
type Settings map[string]interface{}
|
||||
|
||||
func (c EditorConfig) set(opts *runConfig) {
|
||||
opts.editor = fake.EditorConfig(c)
|
||||
func (s Settings) set(opts *runConfig) {
|
||||
if opts.editor.Settings == nil {
|
||||
opts.editor.Settings = make(map[string]interface{})
|
||||
}
|
||||
for k, v := range s {
|
||||
opts.editor.Settings[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// WorkspaceFolders configures the workdir-relative workspace folders to send
|
||||
|
|
@ -160,6 +170,20 @@ func WorkspaceFolders(relFolders ...string) RunOption {
|
|||
})
|
||||
}
|
||||
|
||||
// EnvVars sets environment variables for the LSP session. When applying these
|
||||
// variables to the session, the special string $SANDBOX_WORKDIR is replaced by
|
||||
// the absolute path to the sandbox working directory.
|
||||
type EnvVars map[string]string
|
||||
|
||||
func (e EnvVars) set(opts *runConfig) {
|
||||
if opts.editor.Env == nil {
|
||||
opts.editor.Env = make(map[string]string)
|
||||
}
|
||||
for k, v := range e {
|
||||
opts.editor.Env[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// InGOPATH configures the workspace working directory to be GOPATH, rather
|
||||
// than a separate working directory for use with modules.
|
||||
func InGOPATH() RunOption {
|
||||
|
|
@ -212,13 +236,6 @@ func GOPROXY(goproxy string) RunOption {
|
|||
})
|
||||
}
|
||||
|
||||
// LimitWorkspaceScope sets the LimitWorkspaceScope configuration.
|
||||
func LimitWorkspaceScope() RunOption {
|
||||
return optionSetter(func(opts *runConfig) {
|
||||
opts.editor.LimitWorkspaceScope = true
|
||||
})
|
||||
}
|
||||
|
||||
type TestFunc func(t *testing.T, env *Env)
|
||||
|
||||
// Run executes the test function in the default configured gopls execution
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ package regtest
|
|||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/command"
|
||||
"golang.org/x/tools/internal/lsp/fake"
|
||||
|
|
@ -427,31 +426,10 @@ func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []proto
|
|||
return actions
|
||||
}
|
||||
|
||||
func (e *Env) ChangeConfiguration(t *testing.T, config *fake.EditorConfig) {
|
||||
e.Editor.Config = *config
|
||||
if err := e.Editor.Server.DidChangeConfiguration(e.Ctx, &protocol.DidChangeConfigurationParams{
|
||||
// gopls currently ignores the Settings field
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeEnv modifies the editor environment and reconfigures the LSP client.
|
||||
// TODO: extend this to "ChangeConfiguration", once we refactor the way editor
|
||||
// configuration is defined.
|
||||
func (e *Env) ChangeEnv(overlay map[string]string) {
|
||||
// ChangeConfiguration updates the editor config, calling t.Fatal on any error.
|
||||
func (e *Env) ChangeConfiguration(newConfig fake.EditorConfig) {
|
||||
e.T.Helper()
|
||||
// TODO: to be correct, this should probably be synchronized, but right now
|
||||
// configuration is only ever modified synchronously in a regtest, so this
|
||||
// correctness can wait for the previously mentioned refactoring.
|
||||
if e.Editor.Config.Env == nil {
|
||||
e.Editor.Config.Env = make(map[string]string)
|
||||
}
|
||||
for k, v := range overlay {
|
||||
e.Editor.Config.Env[k] = v
|
||||
}
|
||||
var params protocol.DidChangeConfigurationParams
|
||||
if err := e.Editor.Server.DidChangeConfiguration(e.Ctx, ¶ms); err != nil {
|
||||
if err := e.Editor.ChangeConfiguration(e.Ctx, newConfig); err != nil {
|
||||
e.T.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue