| // 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) |
| } |