diff --git a/gopls/internal/regtest/bench/bench_test.go b/gopls/internal/regtest/bench/bench_test.go index 22f157f471..dfe41f65b1 100644 --- a/gopls/internal/regtest/bench/bench_test.go +++ b/gopls/internal/regtest/bench/bench_test.go @@ -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) { diff --git a/gopls/internal/regtest/codelens/codelens_test.go b/gopls/internal/regtest/codelens/codelens_test.go index a64f9c480a..12a7575415 100644 --- a/gopls/internal/regtest/codelens/codelens_test.go +++ b/gopls/internal/regtest/codelens/codelens_test.go @@ -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) diff --git a/gopls/internal/regtest/completion/completion_test.go b/gopls/internal/regtest/completion/completion_test.go index 1ffb0000d3..51a54c4973 100644 --- a/gopls/internal/regtest/completion/completion_test.go +++ b/gopls/internal/regtest/completion/completion_test.go @@ -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") diff --git a/gopls/internal/regtest/debug/debug_test.go b/gopls/internal/regtest/debug/debug_test.go index d60b3f780d..d01d44ed98 100644 --- a/gopls/internal/regtest/debug/debug_test.go +++ b/gopls/internal/regtest/debug/debug_test.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) diff --git a/gopls/internal/regtest/diagnostics/diagnostics_test.go b/gopls/internal/regtest/diagnostics/diagnostics_test.go index 6f5db4cd41..b9dc2d434b 100644 --- a/gopls/internal/regtest/diagnostics/diagnostics_test.go +++ b/gopls/internal/regtest/diagnostics/diagnostics_test.go @@ -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), diff --git a/gopls/internal/regtest/inlayhints/inlayhints_test.go b/gopls/internal/regtest/inlayhints/inlayhints_test.go index a7cbe65731..1ca1dfbc09 100644 --- a/gopls/internal/regtest/inlayhints/inlayhints_test.go +++ b/gopls/internal/regtest/inlayhints/inlayhints_test.go @@ -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") diff --git a/gopls/internal/regtest/misc/configuration_test.go b/gopls/internal/regtest/misc/configuration_test.go index d9cce96a43..9629a2382e 100644 --- a/gopls/internal/regtest/misc/configuration_test.go +++ b/gopls/internal/regtest/misc/configuration_test.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")) }) } diff --git a/gopls/internal/regtest/misc/definition_test.go b/gopls/internal/regtest/misc/definition_test.go index 2f5a54820d..b71cf23107 100644 --- a/gopls/internal/regtest/misc/definition_test.go +++ b/gopls/internal/regtest/misc/definition_test.go @@ -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"`)) diff --git a/gopls/internal/regtest/misc/formatting_test.go b/gopls/internal/regtest/misc/formatting_test.go index 75d8f62245..71b8cadab4 100644 --- a/gopls/internal/regtest/misc/formatting_test.go +++ b/gopls/internal/regtest/misc/formatting_test.go @@ -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") diff --git a/gopls/internal/regtest/misc/imports_test.go b/gopls/internal/regtest/misc/imports_test.go index 1250e78e77..c0e213e9ae 100644 --- a/gopls/internal/regtest/misc/imports_test.go +++ b/gopls/internal/regtest/misc/imports_test.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") diff --git a/gopls/internal/regtest/misc/link_test.go b/gopls/internal/regtest/misc/link_test.go index e84f6377ee..1005de9a24 100644 --- a/gopls/internal/regtest/misc/link_test.go +++ b/gopls/internal/regtest/misc/link_test.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")) diff --git a/gopls/internal/regtest/misc/semantictokens_test.go b/gopls/internal/regtest/misc/semantictokens_test.go index 79507876a6..dca2b8e751 100644 --- a/gopls/internal/regtest/misc/semantictokens_test.go +++ b/gopls/internal/regtest/misc/semantictokens_test.go @@ -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" diff --git a/gopls/internal/regtest/misc/settings_test.go b/gopls/internal/regtest/misc/settings_test.go index 7704c3c043..62d3d90316 100644 --- a/gopls/internal/regtest/misc/settings_test.go +++ b/gopls/internal/regtest/misc/settings_test.go @@ -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. diff --git a/gopls/internal/regtest/misc/shared_test.go b/gopls/internal/regtest/misc/shared_test.go index a6b0cd87ef..6b5acd02f7 100644 --- a/gopls/internal/regtest/misc/shared_test.go +++ b/gopls/internal/regtest/misc/shared_test.go @@ -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) diff --git a/gopls/internal/regtest/misc/staticcheck_test.go b/gopls/internal/regtest/misc/staticcheck_test.go index 94bb39903a..6f1bda3506 100644 --- a/gopls/internal/regtest/misc/staticcheck_test.go +++ b/gopls/internal/regtest/misc/staticcheck_test.go @@ -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"), diff --git a/gopls/internal/regtest/misc/workspace_symbol_test.go b/gopls/internal/regtest/misc/workspace_symbol_test.go index a21d47312d..2dc3a1b10d 100644 --- a/gopls/internal/regtest/misc/workspace_symbol_test.go +++ b/gopls/internal/regtest/misc/workspace_symbol_test.go @@ -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"}) diff --git a/gopls/internal/regtest/modfile/modfile_test.go b/gopls/internal/regtest/modfile/modfile_test.go index 93d4325304..c0bef833f4 100644 --- a/gopls/internal/regtest/modfile/modfile_test.go +++ b/gopls/internal/regtest/modfile/modfile_test.go @@ -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"`), diff --git a/gopls/internal/regtest/template/template_test.go b/gopls/internal/regtest/template/template_test.go index 9489e9bf7f..0fdc3bda6f 100644 --- a/gopls/internal/regtest/template/template_test.go +++ b/gopls/internal/regtest/template/template_test.go @@ -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") diff --git a/gopls/internal/regtest/watch/watch_test.go b/gopls/internal/regtest/watch/watch_test.go index e66d08ab12..04414f6b74 100644 --- a/gopls/internal/regtest/watch/watch_test.go +++ b/gopls/internal/regtest/watch/watch_test.go @@ -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") diff --git a/gopls/internal/regtest/workspace/workspace_test.go b/gopls/internal/regtest/workspace/workspace_test.go index 9e4b85fced..7eafaf191d 100644 --- a/gopls/internal/regtest/workspace/workspace_test.go +++ b/gopls/internal/regtest/workspace/workspace_test.go @@ -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( diff --git a/internal/lsp/fake/client.go b/internal/lsp/fake/client.go index fdc67a6cc6..4c5f2a2e1b 100644 --- a/internal/lsp/fake/client.go +++ b/internal/lsp/fake/client.go @@ -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 } diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go index 0fc99a0498..240e35cffa 100644 --- a/internal/lsp/fake/editor.go +++ b/internal/lsp/fake/editor.go @@ -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 { diff --git a/internal/lsp/regtest/env.go b/internal/lsp/regtest/env.go index a37cbf6661..8960a0dc91 100644 --- a/internal/lsp/regtest/env.go +++ b/internal/lsp/regtest/env.go @@ -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() diff --git a/internal/lsp/regtest/runner.go b/internal/lsp/regtest/runner.go index 5726f88ea0..0640e452fb 100644 --- a/internal/lsp/regtest/runner.go +++ b/internal/lsp/regtest/runner.go @@ -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 diff --git a/internal/lsp/regtest/wrappers.go b/internal/lsp/regtest/wrappers.go index 96e2de9627..d8c080c0fe 100644 --- a/internal/lsp/regtest/wrappers.go +++ b/internal/lsp/regtest/wrappers.go @@ -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) } }