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 <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/gopls/internal/regtest/bench/bench_test.go b/gopls/internal/regtest/bench/bench_test.go
index a3780f0..cfe4db6 100644
--- a/gopls/internal/regtest/bench/bench_test.go
+++ b/gopls/internal/regtest/bench/bench_test.go
@@ -133,7 +133,7 @@
dir := benchmarkDir()
var err error
- sandbox, editor, awaiter, err = connectEditor(dir)
+ sandbox, editor, awaiter, err = connectEditor(dir, fake.EditorConfig{})
if err != nil {
log.Fatalf("connecting editor: %v", err)
}
@@ -154,7 +154,7 @@
// connectEditor connects a fake editor session in the given dir, using the
// given editor config.
-func connectEditor(dir string) (*fake.Sandbox, *fake.Editor, *regtest.Awaiter, error) {
+func connectEditor(dir string, config fake.EditorConfig) (*fake.Sandbox, *fake.Editor, *regtest.Awaiter, error) {
s, err := fake.NewSandbox(&fake.SandboxConfig{
Workdir: dir,
GOPROXY: "https://proxy.golang.org",
@@ -165,7 +165,7 @@
a := regtest.NewAwaiter(s.Workdir)
ts := getServer()
- e, err := fake.NewEditor(s, fake.EditorConfig{}).Connect(context.Background(), ts, a.Hooks())
+ e, err := fake.NewEditor(s, config).Connect(context.Background(), ts, a.Hooks())
if err != nil {
return nil, nil, nil, err
}
diff --git a/gopls/internal/regtest/bench/completion_test.go b/gopls/internal/regtest/bench/completion_test.go
index cdafb08..a8725ce 100644
--- a/gopls/internal/regtest/bench/completion_test.go
+++ b/gopls/internal/regtest/bench/completion_test.go
@@ -18,8 +18,9 @@
type completionBenchOptions struct {
file, locationRegexp string
- // hook to run edits before initial completion
- preCompletionEdits func(*Env)
+ // Hooks to run edits before initial completion
+ setup func(*Env) // run before the benchmark starts
+ beforeCompletion func(*Env) // run before each completion
}
func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
@@ -27,7 +28,11 @@
// Use a new environment for each test, to avoid any existing state from the
// previous session.
- sandbox, editor, awaiter, err := connectEditor(dir)
+ sandbox, editor, awaiter, err := connectEditor(dir, fake.EditorConfig{
+ Settings: map[string]interface{}{
+ "completionBudget": "1m", // arbitrary long completion budget
+ },
+ })
if err != nil {
b.Fatal(err)
}
@@ -45,11 +50,10 @@
Sandbox: sandbox,
Awaiter: awaiter,
}
- env.OpenFile(options.file)
// Run edits required for this completion.
- if options.preCompletionEdits != nil {
- options.preCompletionEdits(env)
+ if options.setup != nil {
+ options.setup(env)
}
// Run a completion to make sure the system is warm.
@@ -70,6 +74,9 @@
// initialization).
b.Run("completion", func(b *testing.B) {
for i := 0; i < b.N; i++ {
+ if options.beforeCompletion != nil {
+ options.beforeCompletion(env)
+ }
env.Completion(options.file, pos)
}
})
@@ -92,7 +99,7 @@
func BenchmarkStructCompletion(b *testing.B) {
file := "internal/lsp/cache/session.go"
- preCompletionEdits := func(env *Env) {
+ setup := func(env *Env) {
env.OpenFile(file)
originalBuffer := env.Editor.BufferText(file)
env.EditBuffer(file, fake.Edit{
@@ -102,17 +109,19 @@
}
benchmarkCompletion(completionBenchOptions{
- file: file,
- locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`,
- preCompletionEdits: preCompletionEdits,
+ file: file,
+ locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`,
+ setup: setup,
}, b)
}
// Benchmark import completion in tools codebase.
func BenchmarkImportCompletion(b *testing.B) {
+ const file = "internal/lsp/source/completion/completion.go"
benchmarkCompletion(completionBenchOptions{
- file: "internal/lsp/source/completion/completion.go",
+ file: file,
locationRegexp: `go\/()`,
+ setup: func(env *Env) { env.OpenFile(file) },
}, b)
}
@@ -120,7 +129,7 @@
func BenchmarkSliceCompletion(b *testing.B) {
file := "internal/lsp/cache/session.go"
- preCompletionEdits := func(env *Env) {
+ setup := func(env *Env) {
env.OpenFile(file)
originalBuffer := env.Editor.BufferText(file)
env.EditBuffer(file, fake.Edit{
@@ -130,9 +139,9 @@
}
benchmarkCompletion(completionBenchOptions{
- file: file,
- locationRegexp: `var testVariable \[\]byte (=)`,
- preCompletionEdits: preCompletionEdits,
+ file: file,
+ locationRegexp: `var testVariable \[\]byte (=)`,
+ setup: setup,
}, b)
}
@@ -144,7 +153,7 @@
c.inference.kindMatches(c.)
}
`
- preCompletionEdits := func(env *Env) {
+ setup := func(env *Env) {
env.OpenFile(file)
originalBuffer := env.Editor.BufferText(file)
env.EditBuffer(file, fake.Edit{
@@ -154,8 +163,42 @@
}
benchmarkCompletion(completionBenchOptions{
- file: file,
- locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
- preCompletionEdits: preCompletionEdits,
+ file: file,
+ locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
+ setup: setup,
+ }, b)
+}
+
+// Benchmark completion following an arbitrary edit.
+//
+// Edits force type-checked packages to be invalidated, so we want to measure
+// how long it takes before completion results are available.
+func BenchmarkCompletionFollowingEdit(b *testing.B) {
+ file := "internal/lsp/source/completion/completion2.go"
+ fileContent := `
+package completion
+
+func (c *completer) _() {
+ c.inference.kindMatches(c.)
+ // __MAGIC_STRING_1
+}
+`
+ setup := func(env *Env) {
+ env.CreateBuffer(file, fileContent)
+ }
+
+ n := 1
+ beforeCompletion := func(env *Env) {
+ old := fmt.Sprintf("__MAGIC_STRING_%d", n)
+ new := fmt.Sprintf("__MAGIC_STRING_%d", n+1)
+ n++
+ env.RegexpReplace(file, old, new)
+ }
+
+ benchmarkCompletion(completionBenchOptions{
+ file: file,
+ locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
+ setup: setup,
+ beforeCompletion: beforeCompletion,
}, b)
}
diff --git a/gopls/internal/regtest/bench/iwl_test.go b/gopls/internal/regtest/bench/iwl_test.go
index e262a39..b223e33 100644
--- a/gopls/internal/regtest/bench/iwl_test.go
+++ b/gopls/internal/regtest/bench/iwl_test.go
@@ -8,6 +8,7 @@
"context"
"testing"
+ "golang.org/x/tools/internal/lsp/fake"
. "golang.org/x/tools/internal/lsp/regtest"
)
@@ -19,7 +20,7 @@
ctx := context.Background()
for i := 0; i < b.N; i++ {
- _, editor, awaiter, err := connectEditor(dir)
+ _, editor, awaiter, err := connectEditor(dir, fake.EditorConfig{})
if err != nil {
b.Fatal(err)
}
diff --git a/internal/lsp/fake/edit.go b/internal/lsp/fake/edit.go
index 8b04c39..579c3a1 100644
--- a/internal/lsp/fake/edit.go
+++ b/internal/lsp/fake/edit.go
@@ -108,6 +108,8 @@
// editContent implements a simplistic, inefficient algorithm for applying text
// edits to our buffer representation. It returns an error if the edit is
// invalid for the current content.
+//
+// TODO(rfindley): this function does not handle non-ascii text correctly.
func editContent(content []string, edits []Edit) ([]string, error) {
newEdits := make([]Edit, len(edits))
copy(newEdits, edits)