Skip to content

Commit f157068

Browse files
committed
internal/lsp/regtest: allow sharing memoized results across regtests
Each regtest does a significant amount of extra work re-doing things like parsing and type-checking the runtime package. We can share this work across regtests by using a shared cache, significantly speeding them up at the cost of potentially hiding bugs related to timing. Sharing this work still retains most of the benefit of the regtests, so implement this in the default mode (formerly called "singleton" and now renamed to "default"). In a subsequent CL, modes will be cleaned up so that "default" is the only mode that runs with -short. Making this change actually revealed a caching bug: our cached package stores error messages extracted from go/packages errors, but does not include these errors in the cache key. Fix this by hashing all metadata errors into the package cache key. Updates golang/go#39384 Change-Id: I37ab9604149d34c9a79fc02b0e1bc23fcb17c454 Reviewed-on: https://go-review.googlesource.com/c/tools/+/417587 TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Robert Findley <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
1 parent 8ccb25c commit f157068

22 files changed

+182
-80
lines changed

gopls/internal/regtest/bench/bench_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func benchmarkOptions(dir string) []RunOption {
3333
// Skip logs as they buffer up memory unnaturally.
3434
SkipLogs(),
3535
// The Debug server only makes sense if running in singleton mode.
36-
Modes(Singleton),
36+
Modes(Default),
3737
// Remove the default timeout. Individual tests should control their
3838
// own graceful termination.
3939
NoDefaultTimeout(),

gopls/internal/regtest/debug/debug_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestBugNotification(t *testing.T) {
2020
// Verify that a properly configured session gets notified of a bug on the
2121
// server.
2222
WithOptions(
23-
Modes(Singleton), // must be in-process to receive the bug report below
23+
Modes(Default), // must be in-process to receive the bug report below
2424
Settings{"showBugReports": true},
2525
).Run(t, "", func(t *testing.T, env *Env) {
2626
const desc = "got a bug"

gopls/internal/regtest/diagnostics/diagnostics_test.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func Hello() {
298298

299299
t.Run("without workspace module", func(t *testing.T) {
300300
WithOptions(
301-
Modes(Singleton),
301+
Modes(Default),
302302
).Run(t, noMod, func(t *testing.T, env *Env) {
303303
env.Await(
304304
env.DiagnosticAtRegexp("main.go", `"mod.com/bob"`),
@@ -1678,7 +1678,7 @@ import (
16781678
WithOptions(
16791679
InGOPATH(),
16801680
EnvVars{"GO111MODULE": "off"},
1681-
Modes(Singleton),
1681+
Modes(Default),
16821682
).Run(t, mod, func(t *testing.T, env *Env) {
16831683
env.Await(
16841684
env.DiagnosticAtRegexpWithMessage("main.go", `"nosuchpkg"`, `cannot find package "nosuchpkg" in any of`),
@@ -1705,7 +1705,7 @@ package b
17051705
for _, go111module := range []string{"on", "auto"} {
17061706
t.Run("GO111MODULE="+go111module, func(t *testing.T) {
17071707
WithOptions(
1708-
Modes(Singleton),
1708+
Modes(Default),
17091709
EnvVars{"GO111MODULE": go111module},
17101710
).Run(t, modules, func(t *testing.T, env *Env) {
17111711
env.OpenFile("a/a.go")
@@ -1722,7 +1722,7 @@ package b
17221722
// Expect no warning if GO111MODULE=auto in a directory in GOPATH.
17231723
t.Run("GOPATH_GO111MODULE_auto", func(t *testing.T) {
17241724
WithOptions(
1725-
Modes(Singleton),
1725+
Modes(Default),
17261726
EnvVars{"GO111MODULE": "auto"},
17271727
InGOPATH(),
17281728
).Run(t, modules, func(t *testing.T, env *Env) {
@@ -1784,7 +1784,7 @@ func helloHelper() {}
17841784
`
17851785
WithOptions(
17861786
ProxyFiles(proxy),
1787-
Modes(Singleton),
1787+
Modes(Default),
17881788
).Run(t, nested, func(t *testing.T, env *Env) {
17891789
// Expect a diagnostic in a nested module.
17901790
env.OpenFile("nested/hello/hello.go")
@@ -1996,7 +1996,7 @@ func Hello() {}
19961996
`
19971997
WithOptions(
19981998
Settings{"experimentalUseInvalidMetadata": true},
1999-
Modes(Singleton),
1999+
Modes(Default),
20002000
).Run(t, mod, func(t *testing.T, env *Env) {
20012001
env.OpenFile("go.mod")
20022002
env.RegexpReplace("go.mod", "module mod.com", "modul mod.com") // break the go.mod file
@@ -2052,7 +2052,7 @@ func _() {}
20522052
Settings{"experimentalUseInvalidMetadata": true},
20532053
// ExperimentalWorkspaceModule has a different failure mode for this
20542054
// case.
2055-
Modes(Singleton),
2055+
Modes(Default),
20562056
).Run(t, mod, func(t *testing.T, env *Env) {
20572057
env.Await(
20582058
OnceMet(

gopls/internal/regtest/misc/semantictokens_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func main() {}
2525
2626
`
2727
WithOptions(
28-
Modes(Singleton),
28+
Modes(Default),
2929
Settings{"allExperiments": true},
3030
).Run(t, src, func(t *testing.T, env *Env) {
3131
params := &protocol.SemanticTokensParams{}

gopls/internal/regtest/misc/vendor_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func _() {
4949
}
5050
`
5151
WithOptions(
52-
Modes(Singleton),
52+
Modes(Default),
5353
ProxyFiles(basicProxy),
5454
).Run(t, pkgThatUsesVendoring, func(t *testing.T, env *Env) {
5555
env.OpenFile("a/a1.go")

gopls/internal/regtest/modfile/modfile_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ func main() {
742742
WithOptions(
743743
EnvVars{"GOFLAGS": "-mod=readonly"},
744744
ProxyFiles(proxy),
745-
Modes(Singleton),
745+
Modes(Default),
746746
).Run(t, mod, func(t *testing.T, env *Env) {
747747
env.OpenFile("main.go")
748748
original := env.ReadWorkspaceFile("go.mod")
@@ -922,7 +922,7 @@ func hello() {}
922922
// TODO(rFindley) this doesn't work in multi-module workspace mode, because
923923
// it keeps around the last parsing modfile. Update this test to also
924924
// exercise the workspace module.
925-
Modes(Singleton),
925+
Modes(Default),
926926
).Run(t, mod, func(t *testing.T, env *Env) {
927927
env.OpenFile("go.mod")
928928
env.Await(env.DoneWithOpen())
@@ -1090,7 +1090,7 @@ func main() {
10901090
`
10911091
WithOptions(
10921092
ProxyFiles(workspaceProxy),
1093-
Modes(Singleton),
1093+
Modes(Default),
10941094
).Run(t, mod, func(t *testing.T, env *Env) {
10951095
env.OpenFile("go.mod")
10961096
params := &protocol.PublishDiagnosticsParams{}
@@ -1159,7 +1159,7 @@ func main() {
11591159
`
11601160
WithOptions(
11611161
ProxyFiles(proxy),
1162-
Modes(Singleton),
1162+
Modes(Default),
11631163
).Run(t, mod, func(t *testing.T, env *Env) {
11641164
env.OpenFile("main.go")
11651165
d := &protocol.PublishDiagnosticsParams{}

gopls/internal/regtest/workspace/workspace_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ package main
12051205
`
12061206
WithOptions(
12071207
EnvVars{"GOPATH": filepath.FromSlash("$SANDBOX_WORKDIR/gopath")},
1208-
Modes(Singleton),
1208+
Modes(Default),
12091209
).Run(t, mod, func(t *testing.T, env *Env) {
12101210
env.Await(
12111211
// Confirm that the build configuration is seen as valid,
@@ -1236,7 +1236,7 @@ package main
12361236
func main() {}
12371237
`
12381238
WithOptions(
1239-
Modes(Singleton),
1239+
Modes(Default),
12401240
).Run(t, nomod, func(t *testing.T, env *Env) {
12411241
env.OpenFile("a/main.go")
12421242
env.OpenFile("b/main.go")

gopls/internal/vulncheck/command_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func runTest(t *testing.T, workspaceData, proxyData string, test func(context.Co
293293
t.Fatal(err)
294294
}
295295

296-
cache := cache.New(nil)
296+
cache := cache.New(nil, nil, nil)
297297
session := cache.NewSession(ctx)
298298
options := source.DefaultOptions().Clone()
299299
tests.DefaultOptions(options)

internal/lsp/cache/cache.go

+28-5
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,46 @@ import (
2828
"golang.org/x/tools/internal/span"
2929
)
3030

31-
func New(options func(*source.Options)) *Cache {
31+
// New Creates a new cache for gopls operation results, using the given file
32+
// set, shared store, and session options.
33+
//
34+
// All of the fset, store and options may be nil, but if store is non-nil so
35+
// must be fset (and they must always be used together), otherwise it may be
36+
// possible to get cached data referencing token.Pos values not mapped by the
37+
// FileSet.
38+
func New(fset *token.FileSet, store *memoize.Store, options func(*source.Options)) *Cache {
3239
index := atomic.AddInt64(&cacheIndex, 1)
40+
41+
if store != nil && fset == nil {
42+
panic("non-nil store with nil fset")
43+
}
44+
if fset == nil {
45+
fset = token.NewFileSet()
46+
}
47+
if store == nil {
48+
store = &memoize.Store{}
49+
}
50+
3351
c := &Cache{
3452
id: strconv.FormatInt(index, 10),
35-
fset: token.NewFileSet(),
53+
fset: fset,
3654
options: options,
55+
store: store,
3756
fileContent: map[span.URI]*fileHandle{},
3857
}
3958
return c
4059
}
4160

4261
type Cache struct {
43-
id string
44-
fset *token.FileSet
62+
id string
63+
fset *token.FileSet
64+
65+
// TODO(rfindley): it doesn't make sense that cache accepts LSP options, just
66+
// so that it can create a session: the cache does not (and should not)
67+
// depend on options. Invert this relationship to remove options from Cache.
4568
options func(*source.Options)
4669

47-
store memoize.Store
70+
store *memoize.Store
4871

4972
fileMu sync.Mutex
5073
fileContent map[span.URI]*fileHandle

internal/lsp/cache/check.go

+10
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ func computePackageKey(id PackageID, files []source.FileHandle, m *KnownMetadata
249249
for _, file := range files {
250250
b.WriteString(file.FileIdentity().String())
251251
}
252+
// Metadata errors are interpreted and memoized on the computed package, so
253+
// we must hash them into the key here.
254+
//
255+
// TODO(rfindley): handle metadata diagnostics independently from
256+
// type-checking diagnostics.
257+
for _, err := range m.Errors {
258+
b.WriteString(err.Msg)
259+
b.WriteString(err.Pos)
260+
b.WriteRune(rune(err.Kind))
261+
}
252262
return packageHandleKey(source.HashOf(b.Bytes()))
253263
}
254264

internal/lsp/cache/session.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
231231
backgroundCtx: backgroundCtx,
232232
cancel: cancel,
233233
initializeOnce: &sync.Once{},
234-
store: &s.cache.store,
234+
store: s.cache.store,
235235
packages: persistent.NewMap(packageKeyLessInterface),
236236
meta: &metadataGraph{},
237237
files: newFilesMap(),

internal/lsp/cmd/capabilities_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestCapabilities(t *testing.T) {
4343
params.Capabilities.Workspace.Configuration = true
4444

4545
// Send an initialize request to the server.
46-
c.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), c.Client)
46+
c.Server = lsp.NewServer(cache.New(nil, nil, app.options).NewSession(ctx), c.Client)
4747
result, err := c.Server.Initialize(ctx, params)
4848
if err != nil {
4949
t.Fatal(err)

internal/lsp/cmd/cmd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func (app *Application) connect(ctx context.Context) (*connection, error) {
286286
switch {
287287
case app.Remote == "":
288288
connection := newConnection(app)
289-
connection.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), connection.Client)
289+
connection.Server = lsp.NewServer(cache.New(nil, nil, app.options).NewSession(ctx), connection.Client)
290290
ctx = protocol.WithClient(ctx, connection.Client)
291291
return connection, connection.initialize(ctx, app.options)
292292
case strings.HasPrefix(app.Remote, "internal@"):

internal/lsp/cmd/serve.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (s *Serve) Run(ctx context.Context, args ...string) error {
101101
return fmt.Errorf("creating forwarder: %w", err)
102102
}
103103
} else {
104-
ss = lsprpc.NewStreamServer(cache.New(s.app.options), isDaemon)
104+
ss = lsprpc.NewStreamServer(cache.New(nil, nil, s.app.options), isDaemon)
105105
}
106106

107107
var network, addr string

internal/lsp/cmd/test/cmdtest.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestCommandLine(t *testing.T, testdata string, options func(*source.Options
5050

5151
func NewTestServer(ctx context.Context, options func(*source.Options)) *servertest.TCPServer {
5252
ctx = debug.WithInstance(ctx, "", "")
53-
cache := cache.New(options)
53+
cache := cache.New(nil, nil, options)
5454
ss := lsprpc.NewStreamServer(cache, false)
5555
return servertest.NewTCPServer(ctx, ss, nil)
5656
}

internal/lsp/lsp_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type runner struct {
4949
func testLSP(t *testing.T, datum *tests.Data) {
5050
ctx := tests.Context(t)
5151

52-
cache := cache.New(nil)
52+
cache := cache.New(nil, nil, nil)
5353
session := cache.NewSession(ctx)
5454
options := source.DefaultOptions().Clone()
5555
tests.DefaultOptions(options)

internal/lsp/lsprpc/lsprpc_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestClientLogging(t *testing.T) {
5858
client := FakeClient{Logs: make(chan string, 10)}
5959

6060
ctx = debug.WithInstance(ctx, "", "")
61-
ss := NewStreamServer(cache.New(nil), false)
61+
ss := NewStreamServer(cache.New(nil, nil, nil), false)
6262
ss.serverForTest = server
6363
ts := servertest.NewPipeServer(ss, nil)
6464
defer checkClose(t, ts.Close)
@@ -121,7 +121,7 @@ func checkClose(t *testing.T, closer func() error) {
121121
func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) {
122122
t.Helper()
123123
serveCtx := debug.WithInstance(ctx, "", "")
124-
ss := NewStreamServer(cache.New(nil), false)
124+
ss := NewStreamServer(cache.New(nil, nil, nil), false)
125125
ss.serverForTest = s
126126
tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
127127

@@ -216,7 +216,7 @@ func TestDebugInfoLifecycle(t *testing.T) {
216216
clientCtx := debug.WithInstance(baseCtx, "", "")
217217
serverCtx := debug.WithInstance(baseCtx, "", "")
218218

219-
cache := cache.New(nil)
219+
cache := cache.New(nil, nil, nil)
220220
ss := NewStreamServer(cache, false)
221221
tsBackend := servertest.NewTCPServer(serverCtx, ss, nil)
222222

internal/lsp/mod/mod_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestModfileRemainsUnchanged(t *testing.T) {
2626
testenv.NeedsGo1Point(t, 14)
2727

2828
ctx := tests.Context(t)
29-
cache := cache.New(nil)
29+
cache := cache.New(nil, nil, nil)
3030
session := cache.NewSession(ctx)
3131
options := source.DefaultOptions().Clone()
3232
tests.DefaultOptions(options)

internal/lsp/regtest/regtest.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"flag"
1010
"fmt"
11+
"go/token"
1112
"io/ioutil"
1213
"os"
1314
"runtime"
@@ -16,6 +17,7 @@ import (
1617

1718
"golang.org/x/tools/internal/lsp/cmd"
1819
"golang.org/x/tools/internal/lsp/source"
20+
"golang.org/x/tools/internal/memoize"
1921
"golang.org/x/tools/internal/testenv"
2022
"golang.org/x/tools/internal/tool"
2123
)
@@ -87,9 +89,11 @@ var slowGOOS = map[string]bool{
8789
}
8890

8991
func DefaultModes() Mode {
90-
normal := Singleton | Experimental
92+
// TODO(rfindley): these modes should *not* depend on GOOS. Depending on
93+
// testing.Short() should be sufficient.
94+
normal := Default | Experimental
9195
if slowGOOS[runtime.GOOS] && testing.Short() {
92-
normal = Singleton
96+
normal = Default
9397
}
9498
if *runSubprocessTests {
9599
return normal | SeparateProcess
@@ -116,6 +120,8 @@ func Main(m *testing.M, hook func(*source.Options)) {
116120
PrintGoroutinesOnFailure: *printGoroutinesOnFailure,
117121
SkipCleanup: *skipCleanup,
118122
OptionsHook: hook,
123+
fset: token.NewFileSet(),
124+
store: memoize.NewStore(memoize.NeverEvict),
119125
}
120126
if *runSubprocessTests {
121127
goplsPath := *goplsBinaryPath
@@ -126,13 +132,13 @@ func Main(m *testing.M, hook func(*source.Options)) {
126132
panic(fmt.Sprintf("finding test binary path: %v", err))
127133
}
128134
}
129-
runner.GoplsPath = goplsPath
135+
runner.goplsPath = goplsPath
130136
}
131137
dir, err := ioutil.TempDir("", "gopls-regtest-")
132138
if err != nil {
133139
panic(fmt.Errorf("creating regtest temp directory: %v", err))
134140
}
135-
runner.TempDir = dir
141+
runner.tempDir = dir
136142

137143
code := m.Run()
138144
if err := runner.Close(); err != nil {

0 commit comments

Comments
 (0)