Skip to content

Commit 06d96ee

Browse files
committed
gopls/internal/regtest/bench: add a test for completion following edits
For golang/go#53992 Change-Id: Ia1f1e27663992707eef9226273b152117ee977ac Reviewed-on: https://go-review.googlesource.com/c/tools/+/420220 Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Peter Weinberger <[email protected]>
1 parent 81c7dc4 commit 06d96ee

File tree

4 files changed

+69
-23
lines changed

4 files changed

+69
-23
lines changed

gopls/internal/regtest/bench/bench_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func benchmarkEnv(tb testing.TB) *Env {
133133
dir := benchmarkDir()
134134

135135
var err error
136-
sandbox, editor, awaiter, err = connectEditor(dir)
136+
sandbox, editor, awaiter, err = connectEditor(dir, fake.EditorConfig{})
137137
if err != nil {
138138
log.Fatalf("connecting editor: %v", err)
139139
}
@@ -154,7 +154,7 @@ func benchmarkEnv(tb testing.TB) *Env {
154154

155155
// connectEditor connects a fake editor session in the given dir, using the
156156
// given editor config.
157-
func connectEditor(dir string) (*fake.Sandbox, *fake.Editor, *regtest.Awaiter, error) {
157+
func connectEditor(dir string, config fake.EditorConfig) (*fake.Sandbox, *fake.Editor, *regtest.Awaiter, error) {
158158
s, err := fake.NewSandbox(&fake.SandboxConfig{
159159
Workdir: dir,
160160
GOPROXY: "https://proxy.golang.org",
@@ -165,7 +165,7 @@ func connectEditor(dir string) (*fake.Sandbox, *fake.Editor, *regtest.Awaiter, e
165165

166166
a := regtest.NewAwaiter(s.Workdir)
167167
ts := getServer()
168-
e, err := fake.NewEditor(s, fake.EditorConfig{}).Connect(context.Background(), ts, a.Hooks())
168+
e, err := fake.NewEditor(s, config).Connect(context.Background(), ts, a.Hooks())
169169
if err != nil {
170170
return nil, nil, nil, err
171171
}

gopls/internal/regtest/bench/completion_test.go

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@ import (
1818
type completionBenchOptions struct {
1919
file, locationRegexp string
2020

21-
// hook to run edits before initial completion
22-
preCompletionEdits func(*Env)
21+
// Hooks to run edits before initial completion
22+
setup func(*Env) // run before the benchmark starts
23+
beforeCompletion func(*Env) // run before each completion
2324
}
2425

2526
func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
2627
dir := benchmarkDir()
2728

2829
// Use a new environment for each test, to avoid any existing state from the
2930
// previous session.
30-
sandbox, editor, awaiter, err := connectEditor(dir)
31+
sandbox, editor, awaiter, err := connectEditor(dir, fake.EditorConfig{
32+
Settings: map[string]interface{}{
33+
"completionBudget": "1m", // arbitrary long completion budget
34+
},
35+
})
3136
if err != nil {
3237
b.Fatal(err)
3338
}
@@ -45,11 +50,10 @@ func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
4550
Sandbox: sandbox,
4651
Awaiter: awaiter,
4752
}
48-
env.OpenFile(options.file)
4953

5054
// Run edits required for this completion.
51-
if options.preCompletionEdits != nil {
52-
options.preCompletionEdits(env)
55+
if options.setup != nil {
56+
options.setup(env)
5357
}
5458

5559
// Run a completion to make sure the system is warm.
@@ -70,6 +74,9 @@ func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
7074
// initialization).
7175
b.Run("completion", func(b *testing.B) {
7276
for i := 0; i < b.N; i++ {
77+
if options.beforeCompletion != nil {
78+
options.beforeCompletion(env)
79+
}
7380
env.Completion(options.file, pos)
7481
}
7582
})
@@ -92,7 +99,7 @@ func endPosInBuffer(env *Env, name string) fake.Pos {
9299
func BenchmarkStructCompletion(b *testing.B) {
93100
file := "internal/lsp/cache/session.go"
94101

95-
preCompletionEdits := func(env *Env) {
102+
setup := func(env *Env) {
96103
env.OpenFile(file)
97104
originalBuffer := env.Editor.BufferText(file)
98105
env.EditBuffer(file, fake.Edit{
@@ -102,25 +109,27 @@ func BenchmarkStructCompletion(b *testing.B) {
102109
}
103110

104111
benchmarkCompletion(completionBenchOptions{
105-
file: file,
106-
locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`,
107-
preCompletionEdits: preCompletionEdits,
112+
file: file,
113+
locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`,
114+
setup: setup,
108115
}, b)
109116
}
110117

111118
// Benchmark import completion in tools codebase.
112119
func BenchmarkImportCompletion(b *testing.B) {
120+
const file = "internal/lsp/source/completion/completion.go"
113121
benchmarkCompletion(completionBenchOptions{
114-
file: "internal/lsp/source/completion/completion.go",
122+
file: file,
115123
locationRegexp: `go\/()`,
124+
setup: func(env *Env) { env.OpenFile(file) },
116125
}, b)
117126
}
118127

119128
// Benchmark slice completion in tools codebase.
120129
func BenchmarkSliceCompletion(b *testing.B) {
121130
file := "internal/lsp/cache/session.go"
122131

123-
preCompletionEdits := func(env *Env) {
132+
setup := func(env *Env) {
124133
env.OpenFile(file)
125134
originalBuffer := env.Editor.BufferText(file)
126135
env.EditBuffer(file, fake.Edit{
@@ -130,9 +139,9 @@ func BenchmarkSliceCompletion(b *testing.B) {
130139
}
131140

132141
benchmarkCompletion(completionBenchOptions{
133-
file: file,
134-
locationRegexp: `var testVariable \[\]byte (=)`,
135-
preCompletionEdits: preCompletionEdits,
142+
file: file,
143+
locationRegexp: `var testVariable \[\]byte (=)`,
144+
setup: setup,
136145
}, b)
137146
}
138147

@@ -144,7 +153,7 @@ func (c *completer) _() {
144153
c.inference.kindMatches(c.)
145154
}
146155
`
147-
preCompletionEdits := func(env *Env) {
156+
setup := func(env *Env) {
148157
env.OpenFile(file)
149158
originalBuffer := env.Editor.BufferText(file)
150159
env.EditBuffer(file, fake.Edit{
@@ -154,8 +163,42 @@ func (c *completer) _() {
154163
}
155164

156165
benchmarkCompletion(completionBenchOptions{
157-
file: file,
158-
locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
159-
preCompletionEdits: preCompletionEdits,
166+
file: file,
167+
locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
168+
setup: setup,
169+
}, b)
170+
}
171+
172+
// Benchmark completion following an arbitrary edit.
173+
//
174+
// Edits force type-checked packages to be invalidated, so we want to measure
175+
// how long it takes before completion results are available.
176+
func BenchmarkCompletionFollowingEdit(b *testing.B) {
177+
file := "internal/lsp/source/completion/completion2.go"
178+
fileContent := `
179+
package completion
180+
181+
func (c *completer) _() {
182+
c.inference.kindMatches(c.)
183+
// __MAGIC_STRING_1
184+
}
185+
`
186+
setup := func(env *Env) {
187+
env.CreateBuffer(file, fileContent)
188+
}
189+
190+
n := 1
191+
beforeCompletion := func(env *Env) {
192+
old := fmt.Sprintf("__MAGIC_STRING_%d", n)
193+
new := fmt.Sprintf("__MAGIC_STRING_%d", n+1)
194+
n++
195+
env.RegexpReplace(file, old, new)
196+
}
197+
198+
benchmarkCompletion(completionBenchOptions{
199+
file: file,
200+
locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
201+
setup: setup,
202+
beforeCompletion: beforeCompletion,
160203
}, b)
161204
}

gopls/internal/regtest/bench/iwl_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"testing"
1010

11+
"golang.org/x/tools/internal/lsp/fake"
1112
. "golang.org/x/tools/internal/lsp/regtest"
1213
)
1314

@@ -19,7 +20,7 @@ func BenchmarkIWL(b *testing.B) {
1920

2021
ctx := context.Background()
2122
for i := 0; i < b.N; i++ {
22-
_, editor, awaiter, err := connectEditor(dir)
23+
_, editor, awaiter, err := connectEditor(dir, fake.EditorConfig{})
2324
if err != nil {
2425
b.Fatal(err)
2526
}

internal/lsp/fake/edit.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ func inText(p Pos, content []string) bool {
108108
// editContent implements a simplistic, inefficient algorithm for applying text
109109
// edits to our buffer representation. It returns an error if the edit is
110110
// invalid for the current content.
111+
//
112+
// TODO(rfindley): this function does not handle non-ascii text correctly.
111113
func editContent(content []string, edits []Edit) ([]string, error) {
112114
newEdits := make([]Edit, len(edits))
113115
copy(newEdits, edits)

0 commit comments

Comments
 (0)