--- /dev/null
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package regtest
+
+import (
+ "flag"
+ "fmt"
+ "runtime"
+ "strings"
+ "testing"
+
+ "golang.org/x/tools/internal/lsp/fake"
+)
+
+// dummyCompletionFunction to test manually configured completion using CLI.
+func dummyCompletionFunction() { const s = "placeholder"; fmt.Printf("%s", s) }
+
+type completionBenchOptions struct {
+ workdir, file, locationRegexp string
+ printResults bool
+ // hook to run edits before initial completion, not supported for manually
+ // configured completions.
+ preCompletionEdits func(*Env)
+}
+
+var completionOptions = completionBenchOptions{}
+
+func init() {
+ flag.StringVar(&completionOptions.workdir, "completion_workdir", "", "directory to run completion benchmarks in")
+ flag.StringVar(&completionOptions.file, "completion_file", "", "relative path to the file to complete in")
+ flag.StringVar(&completionOptions.locationRegexp, "completion_regexp", "", "regexp location to complete at")
+ flag.BoolVar(&completionOptions.printResults, "completion_print_results", false, "whether to print completion results")
+}
+
+func benchmarkCompletion(options completionBenchOptions, t *testing.T) {
+ if completionOptions.workdir == "" {
+ t.Skip("-completion_workdir not configured, skipping benchmark")
+ }
+
+ opts := stressTestOptions(options.workdir)
+
+ // Completion gives bad results if IWL is not yet complete, so we must await
+ // it first (and therefore need hooks).
+ opts = append(opts, SkipHooks(false))
+
+ withOptions(opts...).run(t, "", func(t *testing.T, env *Env) {
+ env.OpenFile(options.file)
+
+ // Run edits required for this completion.
+ if options.preCompletionEdits != nil {
+ options.preCompletionEdits(env)
+ }
+
+ // Add a comment as a marker at the start of the file, we'll replace
+ // this in every iteration to trigger type checking and hence emulate
+ // a more real world scenario.
+ env.EditBuffer(options.file, fake.Edit{Text: "// 0\n"})
+
+ // Run a completion to make sure the system is warm.
+ pos := env.RegexpSearch(options.file, options.locationRegexp)
+ completions := env.Completion(options.file, pos)
+
+ if options.printResults {
+ fmt.Println("Results:")
+ for i := 0; i < len(completions.Items); i++ {
+ fmt.Printf("\t%d. %v\n", i, completions.Items[i])
+ }
+ }
+
+ results := testing.Benchmark(func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ env.RegexpReplace(options.file, `\/\/ \d*`, fmt.Sprintf("// %d", i))
+
+ // explicitly garbage collect since we don't want to count this
+ // time in completion benchmarks.
+ if i%10 == 0 {
+ runtime.GC()
+ }
+ b.StartTimer()
+
+ env.Completion(options.file, pos)
+ }
+ })
+
+ printBenchmarkResults(results)
+ })
+}
+
+// endPosInBuffer returns the position for last character in the buffer for
+// the given file.
+func endPosInBuffer(env *Env, name string) fake.Pos {
+ buffer := env.Editor.BufferText(name)
+ lines := strings.Split(buffer, "\n")
+ numLines := len(lines)
+
+ return fake.Pos{
+ Line: numLines - 1,
+ Column: len([]rune(lines[numLines-1])),
+ }
+}
+
+// Benchmark completion at a specified file and location. When no CLI options
+// are specified, this test is skipped.
+// To Run (from x/tools/gopls) against the dummy function above:
+// go test -v ./internal/regtest -run=TestBenchmarkConfiguredCompletion
+// -completion_workdir="$HOME/Developer/tools"
+// -completion_file="gopls/internal/regtest/completion_bench_test.go"
+// -completion_regexp="dummyCompletionFunction.*fmt\.Printf\(\"%s\", s(\))"
+func TestBenchmarkConfiguredCompletion(t *testing.T) {
+ benchmarkCompletion(completionOptions, t)
+}
+
+// To run (from x/tools/gopls):
+// go test -v ./internal/regtest -run TestBenchmark<>Completion
+// -completion_workdir="$HOME/Developer/tools"
+// where <> is one of the tests below. completion_workdir should be path to
+// x/tools on your system.
+
+// Benchmark struct completion in tools codebase.
+func TestBenchmarkStructCompletion(t *testing.T) {
+ file := "internal/lsp/cache/session.go"
+
+ preCompletionEdits := func(env *Env) {
+ env.OpenFile(file)
+ originalBuffer := env.Editor.BufferText(file)
+ env.EditBuffer(file, fake.Edit{
+ End: endPosInBuffer(env, file),
+ Text: originalBuffer + "\nvar testVariable map[string]bool = Session{}.\n",
+ })
+ }
+
+ benchmarkCompletion(completionBenchOptions{
+ workdir: completionOptions.workdir,
+ file: file,
+ locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`,
+ preCompletionEdits: preCompletionEdits,
+ printResults: completionOptions.printResults,
+ }, t)
+}
+
+// Benchmark import completion in tools codebase.
+func TestBenchmarkImportCompletion(t *testing.T) {
+ benchmarkCompletion(completionBenchOptions{
+ workdir: completionOptions.workdir,
+ file: "internal/lsp/source/completion/completion.go",
+ locationRegexp: `go\/()`,
+ printResults: completionOptions.printResults,
+ }, t)
+}
+
+// Benchmark slice completion in tools codebase.
+func TestBenchmarkSliceCompletion(t *testing.T) {
+ file := "internal/lsp/cache/session.go"
+
+ preCompletionEdits := func(env *Env) {
+ env.OpenFile(file)
+ originalBuffer := env.Editor.BufferText(file)
+ env.EditBuffer(file, fake.Edit{
+ End: endPosInBuffer(env, file),
+ Text: originalBuffer + "\nvar testVariable []byte = \n",
+ })
+ }
+
+ benchmarkCompletion(completionBenchOptions{
+ workdir: completionOptions.workdir,
+ file: file,
+ locationRegexp: `var testVariable \[\]byte (=)`,
+ preCompletionEdits: preCompletionEdits,
+ printResults: completionOptions.printResults,
+ }, t)
+}
+
+// Benchmark deep completion in function call in tools codebase.
+func TestBenchmarkFuncDeepCompletion(t *testing.T) {
+ file := "internal/lsp/source/completion/completion.go"
+ fileContent := `
+func (c *completer) _() {
+ c.inference.kindMatches(c.)
+}
+`
+ preCompletionEdits := func(env *Env) {
+ env.OpenFile(file)
+ originalBuffer := env.Editor.BufferText(file)
+ env.EditBuffer(file, fake.Edit{
+ End: endPosInBuffer(env, file),
+ Text: originalBuffer + fileContent,
+ })
+ }
+
+ benchmarkCompletion(completionBenchOptions{
+ workdir: completionOptions.workdir,
+ file: file,
+ locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
+ preCompletionEdits: preCompletionEdits,
+ printResults: completionOptions.printResults,
+ }, t)
+}