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. 4 5package regtest 6 7import ( 8 "flag" 9 "fmt" 10 "runtime" 11 "strings" 12 "testing" 13 14 "golang.org/x/tools/internal/lsp/fake" 15) 16 17// dummyCompletionFunction to test manually configured completion using CLI. 18func dummyCompletionFunction() { const s = "placeholder"; fmt.Printf("%s", s) } 19 20type completionBenchOptions struct { 21 workdir, file, locationRegexp string 22 printResults bool 23 // hook to run edits before initial completion, not supported for manually 24 // configured completions. 25 preCompletionEdits func(*Env) 26} 27 28var completionOptions = completionBenchOptions{} 29 30func init() { 31 flag.StringVar(&completionOptions.workdir, "completion_workdir", "", "directory to run completion benchmarks in") 32 flag.StringVar(&completionOptions.file, "completion_file", "", "relative path to the file to complete in") 33 flag.StringVar(&completionOptions.locationRegexp, "completion_regexp", "", "regexp location to complete at") 34 flag.BoolVar(&completionOptions.printResults, "completion_print_results", false, "whether to print completion results") 35} 36 37func benchmarkCompletion(options completionBenchOptions, t *testing.T) { 38 if completionOptions.workdir == "" { 39 t.Skip("-completion_workdir not configured, skipping benchmark") 40 } 41 42 opts := stressTestOptions(options.workdir) 43 44 // Completion gives bad results if IWL is not yet complete, so we must await 45 // it first (and therefore need hooks). 46 opts = append(opts, SkipHooks(false)) 47 48 withOptions(opts...).run(t, "", func(t *testing.T, env *Env) { 49 env.OpenFile(options.file) 50 51 // Run edits required for this completion. 52 if options.preCompletionEdits != nil { 53 options.preCompletionEdits(env) 54 } 55 56 // Add a comment as a marker at the start of the file, we'll replace 57 // this in every iteration to trigger type checking and hence emulate 58 // a more real world scenario. 59 env.EditBuffer(options.file, fake.Edit{Text: "// 0\n"}) 60 61 // Run a completion to make sure the system is warm. 62 pos := env.RegexpSearch(options.file, options.locationRegexp) 63 completions := env.Completion(options.file, pos) 64 65 if options.printResults { 66 fmt.Println("Results:") 67 for i := 0; i < len(completions.Items); i++ { 68 fmt.Printf("\t%d. %v\n", i, completions.Items[i]) 69 } 70 } 71 72 results := testing.Benchmark(func(b *testing.B) { 73 for i := 0; i < b.N; i++ { 74 b.StopTimer() 75 env.RegexpReplace(options.file, `\/\/ \d*`, fmt.Sprintf("// %d", i)) 76 77 // explicitly garbage collect since we don't want to count this 78 // time in completion benchmarks. 79 if i%10 == 0 { 80 runtime.GC() 81 } 82 b.StartTimer() 83 84 env.Completion(options.file, pos) 85 } 86 }) 87 88 printBenchmarkResults(results) 89 }) 90} 91 92// endPosInBuffer returns the position for last character in the buffer for 93// the given file. 94func endPosInBuffer(env *Env, name string) fake.Pos { 95 buffer := env.Editor.BufferText(name) 96 lines := strings.Split(buffer, "\n") 97 numLines := len(lines) 98 99 return fake.Pos{ 100 Line: numLines - 1, 101 Column: len([]rune(lines[numLines-1])), 102 } 103} 104 105// Benchmark completion at a specified file and location. When no CLI options 106// are specified, this test is skipped. 107// To Run (from x/tools/gopls) against the dummy function above: 108// go test -v ./internal/regtest -run=TestBenchmarkConfiguredCompletion 109// -completion_workdir="$HOME/Developer/tools" 110// -completion_file="gopls/internal/regtest/completion_bench_test.go" 111// -completion_regexp="dummyCompletionFunction.*fmt\.Printf\(\"%s\", s(\))" 112func TestBenchmarkConfiguredCompletion(t *testing.T) { 113 benchmarkCompletion(completionOptions, t) 114} 115 116// To run (from x/tools/gopls): 117// go test -v ./internal/regtest -run TestBenchmark<>Completion 118// -completion_workdir="$HOME/Developer/tools" 119// where <> is one of the tests below. completion_workdir should be path to 120// x/tools on your system. 121 122// Benchmark struct completion in tools codebase. 123func TestBenchmarkStructCompletion(t *testing.T) { 124 file := "internal/lsp/cache/session.go" 125 126 preCompletionEdits := func(env *Env) { 127 env.OpenFile(file) 128 originalBuffer := env.Editor.BufferText(file) 129 env.EditBuffer(file, fake.Edit{ 130 End: endPosInBuffer(env, file), 131 Text: originalBuffer + "\nvar testVariable map[string]bool = Session{}.\n", 132 }) 133 } 134 135 benchmarkCompletion(completionBenchOptions{ 136 workdir: completionOptions.workdir, 137 file: file, 138 locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`, 139 preCompletionEdits: preCompletionEdits, 140 printResults: completionOptions.printResults, 141 }, t) 142} 143 144// Benchmark import completion in tools codebase. 145func TestBenchmarkImportCompletion(t *testing.T) { 146 benchmarkCompletion(completionBenchOptions{ 147 workdir: completionOptions.workdir, 148 file: "internal/lsp/source/completion/completion.go", 149 locationRegexp: `go\/()`, 150 printResults: completionOptions.printResults, 151 }, t) 152} 153 154// Benchmark slice completion in tools codebase. 155func TestBenchmarkSliceCompletion(t *testing.T) { 156 file := "internal/lsp/cache/session.go" 157 158 preCompletionEdits := func(env *Env) { 159 env.OpenFile(file) 160 originalBuffer := env.Editor.BufferText(file) 161 env.EditBuffer(file, fake.Edit{ 162 End: endPosInBuffer(env, file), 163 Text: originalBuffer + "\nvar testVariable []byte = \n", 164 }) 165 } 166 167 benchmarkCompletion(completionBenchOptions{ 168 workdir: completionOptions.workdir, 169 file: file, 170 locationRegexp: `var testVariable \[\]byte (=)`, 171 preCompletionEdits: preCompletionEdits, 172 printResults: completionOptions.printResults, 173 }, t) 174} 175 176// Benchmark deep completion in function call in tools codebase. 177func TestBenchmarkFuncDeepCompletion(t *testing.T) { 178 file := "internal/lsp/source/completion/completion.go" 179 fileContent := ` 180func (c *completer) _() { 181 c.inference.kindMatches(c.) 182} 183` 184 preCompletionEdits := func(env *Env) { 185 env.OpenFile(file) 186 originalBuffer := env.Editor.BufferText(file) 187 env.EditBuffer(file, fake.Edit{ 188 End: endPosInBuffer(env, file), 189 Text: originalBuffer + fileContent, 190 }) 191 } 192 193 benchmarkCompletion(completionBenchOptions{ 194 workdir: completionOptions.workdir, 195 file: file, 196 locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`, 197 preCompletionEdits: preCompletionEdits, 198 printResults: completionOptions.printResults, 199 }, t) 200} 201