mirror of https://github.com/golang/go.git
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>
This commit is contained in:
parent
81c7dc4e4e
commit
06d96ee8fc
|
|
@ -133,7 +133,7 @@ func benchmarkEnv(tb testing.TB) *Env {
|
|||
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 @@ func benchmarkEnv(tb testing.TB) *Env {
|
|||
|
||||
// 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 @@ func connectEditor(dir string) (*fake.Sandbox, *fake.Editor, *regtest.Awaiter, e
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ import (
|
|||
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 @@ func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
|
|||
|
||||
// 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 @@ func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
|
|||
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 @@ func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
|
|||
// 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 endPosInBuffer(env *Env, name string) fake.Pos {
|
|||
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 @@ func BenchmarkStructCompletion(b *testing.B) {
|
|||
}
|
||||
|
||||
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 BenchmarkImportCompletion(b *testing.B) {
|
|||
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 @@ func BenchmarkSliceCompletion(b *testing.B) {
|
|||
}
|
||||
|
||||
benchmarkCompletion(completionBenchOptions{
|
||||
file: file,
|
||||
locationRegexp: `var testVariable \[\]byte (=)`,
|
||||
preCompletionEdits: preCompletionEdits,
|
||||
file: file,
|
||||
locationRegexp: `var testVariable \[\]byte (=)`,
|
||||
setup: setup,
|
||||
}, b)
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +153,7 @@ func (c *completer) _() {
|
|||
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 @@ func (c *completer) _() {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/fake"
|
||||
. "golang.org/x/tools/internal/lsp/regtest"
|
||||
)
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ func BenchmarkIWL(b *testing.B) {
|
|||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ func inText(p Pos, content []string) bool {
|
|||
// 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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue