1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
14 . "golang.org/x/tools/gopls/internal/regtest"
16 "golang.org/x/tools/internal/lsp/fake"
19 // dummyCompletionFunction to test manually configured completion using CLI.
20 func dummyCompletionFunction() { const s = "placeholder"; fmt.Printf("%s", s) }
22 type completionBenchOptions struct {
23 workdir, file, locationRegexp string
25 // hook to run edits before initial completion, not supported for manually
26 // configured completions.
27 preCompletionEdits func(*Env)
30 var completionOptions = completionBenchOptions{}
33 flag.StringVar(&completionOptions.workdir, "completion_workdir", "", "directory to run completion benchmarks in")
34 flag.StringVar(&completionOptions.file, "completion_file", "", "relative path to the file to complete in")
35 flag.StringVar(&completionOptions.locationRegexp, "completion_regexp", "", "regexp location to complete at")
36 flag.BoolVar(&completionOptions.printResults, "completion_print_results", false, "whether to print completion results")
39 func benchmarkCompletion(options completionBenchOptions, t *testing.T) {
40 if completionOptions.workdir == "" {
41 t.Skip("-completion_workdir not configured, skipping benchmark")
44 opts := stressTestOptions(options.workdir)
46 // Completion gives bad results if IWL is not yet complete, so we must await
47 // it first (and therefore need hooks).
48 opts = append(opts, SkipHooks(false))
50 WithOptions(opts...).Run(t, "", func(t *testing.T, env *Env) {
51 env.OpenFile(options.file)
53 // Run edits required for this completion.
54 if options.preCompletionEdits != nil {
55 options.preCompletionEdits(env)
58 // Add a comment as a marker at the start of the file, we'll replace
59 // this in every iteration to trigger type checking and hence emulate
60 // a more real world scenario.
61 env.EditBuffer(options.file, fake.Edit{Text: "// 0\n"})
63 // Run a completion to make sure the system is warm.
64 pos := env.RegexpSearch(options.file, options.locationRegexp)
65 completions := env.Completion(options.file, pos)
67 if options.printResults {
68 fmt.Println("Results:")
69 for i := 0; i < len(completions.Items); i++ {
70 fmt.Printf("\t%d. %v\n", i, completions.Items[i])
74 results := testing.Benchmark(func(b *testing.B) {
75 for i := 0; i < b.N; i++ {
77 env.RegexpReplace(options.file, `\/\/ \d*`, fmt.Sprintf("// %d", i))
79 // explicitly garbage collect since we don't want to count this
80 // time in completion benchmarks.
86 env.Completion(options.file, pos)
90 printBenchmarkResults(results)
94 // endPosInBuffer returns the position for last character in the buffer for
96 func endPosInBuffer(env *Env, name string) fake.Pos {
97 buffer := env.Editor.BufferText(name)
98 lines := strings.Split(buffer, "\n")
99 numLines := len(lines)
103 Column: len([]rune(lines[numLines-1])),
107 // Benchmark completion at a specified file and location. When no CLI options
108 // are specified, this test is skipped.
109 // To Run (from x/tools/gopls) against the dummy function above:
110 // go test -v ./internal/regtest -run=TestBenchmarkConfiguredCompletion
111 // -completion_workdir="$HOME/Developer/tools"
112 // -completion_file="gopls/internal/regtest/completion_bench_test.go"
113 // -completion_regexp="dummyCompletionFunction.*fmt\.Printf\(\"%s\", s(\))"
114 func TestBenchmarkConfiguredCompletion(t *testing.T) {
115 benchmarkCompletion(completionOptions, t)
118 // To run (from x/tools/gopls):
119 // go test -v ./internal/regtest -run TestBenchmark<>Completion
120 // -completion_workdir="$HOME/Developer/tools"
121 // where <> is one of the tests below. completion_workdir should be path to
122 // x/tools on your system.
124 // Benchmark struct completion in tools codebase.
125 func TestBenchmarkStructCompletion(t *testing.T) {
126 file := "internal/lsp/cache/session.go"
128 preCompletionEdits := func(env *Env) {
130 originalBuffer := env.Editor.BufferText(file)
131 env.EditBuffer(file, fake.Edit{
132 End: endPosInBuffer(env, file),
133 Text: originalBuffer + "\nvar testVariable map[string]bool = Session{}.\n",
137 benchmarkCompletion(completionBenchOptions{
138 workdir: completionOptions.workdir,
140 locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`,
141 preCompletionEdits: preCompletionEdits,
142 printResults: completionOptions.printResults,
146 // Benchmark import completion in tools codebase.
147 func TestBenchmarkImportCompletion(t *testing.T) {
148 benchmarkCompletion(completionBenchOptions{
149 workdir: completionOptions.workdir,
150 file: "internal/lsp/source/completion/completion.go",
151 locationRegexp: `go\/()`,
152 printResults: completionOptions.printResults,
156 // Benchmark slice completion in tools codebase.
157 func TestBenchmarkSliceCompletion(t *testing.T) {
158 file := "internal/lsp/cache/session.go"
160 preCompletionEdits := func(env *Env) {
162 originalBuffer := env.Editor.BufferText(file)
163 env.EditBuffer(file, fake.Edit{
164 End: endPosInBuffer(env, file),
165 Text: originalBuffer + "\nvar testVariable []byte = \n",
169 benchmarkCompletion(completionBenchOptions{
170 workdir: completionOptions.workdir,
172 locationRegexp: `var testVariable \[\]byte (=)`,
173 preCompletionEdits: preCompletionEdits,
174 printResults: completionOptions.printResults,
178 // Benchmark deep completion in function call in tools codebase.
179 func TestBenchmarkFuncDeepCompletion(t *testing.T) {
180 file := "internal/lsp/source/completion/completion.go"
182 func (c *completer) _() {
183 c.inference.kindMatches(c.)
186 preCompletionEdits := func(env *Env) {
188 originalBuffer := env.Editor.BufferText(file)
189 env.EditBuffer(file, fake.Edit{
190 End: endPosInBuffer(env, file),
191 Text: originalBuffer + fileContent,
195 benchmarkCompletion(completionBenchOptions{
196 workdir: completionOptions.workdir,
198 locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`,
199 preCompletionEdits: preCompletionEdits,
200 printResults: completionOptions.printResults,