golang.org/x/tools/gopls@v0.15.3/internal/test/integration/bench/completion_test.go (about) 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 5 package bench 6 7 import ( 8 "flag" 9 "fmt" 10 "sync/atomic" 11 "testing" 12 13 "golang.org/x/tools/gopls/internal/protocol" 14 . "golang.org/x/tools/gopls/internal/test/integration" 15 "golang.org/x/tools/gopls/internal/test/integration/fake" 16 ) 17 18 var completionGOPATH = flag.String("completion_gopath", "", "if set, use this GOPATH for BenchmarkCompletion") 19 20 type completionBenchOptions struct { 21 file, locationRegexp string 22 23 // Hooks to run edits before initial completion 24 setup func(*Env) // run before the benchmark starts 25 beforeCompletion func(*Env) // run before each completion 26 } 27 28 // Deprecated: new tests should be expressed in BenchmarkCompletion. 29 func benchmarkCompletion(options completionBenchOptions, b *testing.B) { 30 repo := getRepo(b, "tools") 31 _ = repo.sharedEnv(b) // ensure cache is warm 32 env := repo.newEnv(b, fake.EditorConfig{}, "completion", false) 33 defer env.Close() 34 35 // Run edits required for this completion. 36 if options.setup != nil { 37 options.setup(env) 38 } 39 40 // Run a completion to make sure the system is warm. 41 loc := env.RegexpSearch(options.file, options.locationRegexp) 42 completions := env.Completion(loc) 43 44 if testing.Verbose() { 45 fmt.Println("Results:") 46 for i := 0; i < len(completions.Items); i++ { 47 fmt.Printf("\t%d. %v\n", i, completions.Items[i]) 48 } 49 } 50 51 b.Run("tools", func(b *testing.B) { 52 if stopAndRecord := startProfileIfSupported(b, env, qualifiedName("tools", "completion")); stopAndRecord != nil { 53 defer stopAndRecord() 54 } 55 56 for i := 0; i < b.N; i++ { 57 if options.beforeCompletion != nil { 58 options.beforeCompletion(env) 59 } 60 env.Completion(loc) 61 } 62 }) 63 } 64 65 // endRangeInBuffer returns the position for last character in the buffer for 66 // the given file. 67 func endRangeInBuffer(env *Env, name string) protocol.Range { 68 buffer := env.BufferText(name) 69 m := protocol.NewMapper("", []byte(buffer)) 70 rng, err := m.OffsetRange(len(buffer), len(buffer)) 71 if err != nil { 72 env.T.Fatal(err) 73 } 74 return rng 75 } 76 77 // Benchmark struct completion in tools codebase. 78 func BenchmarkStructCompletion(b *testing.B) { 79 file := "internal/lsp/cache/session.go" 80 81 setup := func(env *Env) { 82 env.OpenFile(file) 83 env.EditBuffer(file, protocol.TextEdit{ 84 Range: endRangeInBuffer(env, file), 85 NewText: "\nvar testVariable map[string]bool = Session{}.\n", 86 }) 87 } 88 89 benchmarkCompletion(completionBenchOptions{ 90 file: file, 91 locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`, 92 setup: setup, 93 }, b) 94 } 95 96 // Benchmark import completion in tools codebase. 97 func BenchmarkImportCompletion(b *testing.B) { 98 const file = "internal/lsp/source/completion/completion.go" 99 benchmarkCompletion(completionBenchOptions{ 100 file: file, 101 locationRegexp: `go\/()`, 102 setup: func(env *Env) { env.OpenFile(file) }, 103 }, b) 104 } 105 106 // Benchmark slice completion in tools codebase. 107 func BenchmarkSliceCompletion(b *testing.B) { 108 file := "internal/lsp/cache/session.go" 109 110 setup := func(env *Env) { 111 env.OpenFile(file) 112 env.EditBuffer(file, protocol.TextEdit{ 113 Range: endRangeInBuffer(env, file), 114 NewText: "\nvar testVariable []byte = \n", 115 }) 116 } 117 118 benchmarkCompletion(completionBenchOptions{ 119 file: file, 120 locationRegexp: `var testVariable \[\]byte (=)`, 121 setup: setup, 122 }, b) 123 } 124 125 // Benchmark deep completion in function call in tools codebase. 126 func BenchmarkFuncDeepCompletion(b *testing.B) { 127 file := "internal/lsp/source/completion/completion.go" 128 fileContent := ` 129 func (c *completer) _() { 130 c.inference.kindMatches(c.) 131 } 132 ` 133 setup := func(env *Env) { 134 env.OpenFile(file) 135 originalBuffer := env.BufferText(file) 136 env.EditBuffer(file, protocol.TextEdit{ 137 Range: endRangeInBuffer(env, file), 138 // TODO(rfindley): this is a bug: it should just be fileContent. 139 NewText: originalBuffer + fileContent, 140 }) 141 } 142 143 benchmarkCompletion(completionBenchOptions{ 144 file: file, 145 locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`, 146 setup: setup, 147 }, b) 148 } 149 150 type completionTest struct { 151 repo string 152 name string 153 file string // repo-relative file to create 154 content string // file content 155 locationRegexp string // regexp for completion 156 } 157 158 var completionTests = []completionTest{ 159 { 160 "tools", 161 "selector", 162 "internal/lsp/source/completion/completion2.go", 163 ` 164 package completion 165 166 func (c *completer) _() { 167 c.inference.kindMatches(c.) 168 } 169 `, 170 `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`, 171 }, 172 { 173 "tools", 174 "unimportedident", 175 "internal/lsp/source/completion/completion2.go", 176 ` 177 package completion 178 179 func (c *completer) _() { 180 lo 181 } 182 `, 183 `lo()`, 184 }, 185 { 186 "tools", 187 "unimportedselector", 188 "internal/lsp/source/completion/completion2.go", 189 ` 190 package completion 191 192 func (c *completer) _() { 193 log. 194 } 195 `, 196 `log\.()`, 197 }, 198 { 199 "kubernetes", 200 "selector", 201 "pkg/kubelet/kubelet2.go", 202 ` 203 package kubelet 204 205 func (kl *Kubelet) _() { 206 kl. 207 } 208 `, 209 `kl\.()`, 210 }, 211 { 212 "kubernetes", 213 "identifier", 214 "pkg/kubelet/kubelet2.go", 215 ` 216 package kubelet 217 218 func (kl *Kubelet) _() { 219 k // here 220 } 221 `, 222 `k() // here`, 223 }, 224 { 225 "oracle", 226 "selector", 227 "dataintegration/pivot2.go", 228 ` 229 package dataintegration 230 231 func (p *Pivot) _() { 232 p. 233 } 234 `, 235 `p\.()`, 236 }, 237 } 238 239 // Benchmark completion following an arbitrary edit. 240 // 241 // Edits force type-checked packages to be invalidated, so we want to measure 242 // how long it takes before completion results are available. 243 func BenchmarkCompletion(b *testing.B) { 244 for _, test := range completionTests { 245 b.Run(fmt.Sprintf("%s_%s", test.repo, test.name), func(b *testing.B) { 246 for _, followingEdit := range []bool{true, false} { 247 b.Run(fmt.Sprintf("edit=%v", followingEdit), func(b *testing.B) { 248 for _, completeUnimported := range []bool{true, false} { 249 b.Run(fmt.Sprintf("unimported=%v", completeUnimported), func(b *testing.B) { 250 for _, budget := range []string{"0s", "100ms"} { 251 b.Run(fmt.Sprintf("budget=%s", budget), func(b *testing.B) { 252 runCompletion(b, test, followingEdit, completeUnimported, budget) 253 }) 254 } 255 }) 256 } 257 }) 258 } 259 }) 260 } 261 } 262 263 // For optimizing unimported completion, it can be useful to benchmark with a 264 // huge GOMODCACHE. 265 var gomodcache = flag.String("gomodcache", "", "optional GOMODCACHE for unimported completion benchmarks") 266 267 func runCompletion(b *testing.B, test completionTest, followingEdit, completeUnimported bool, budget string) { 268 repo := getRepo(b, test.repo) 269 gopath := *completionGOPATH 270 if gopath == "" { 271 // use a warm GOPATH 272 sharedEnv := repo.sharedEnv(b) 273 gopath = sharedEnv.Sandbox.GOPATH() 274 } 275 envvars := map[string]string{ 276 "GOPATH": gopath, 277 } 278 279 if *gomodcache != "" { 280 envvars["GOMODCACHE"] = *gomodcache 281 } 282 283 env := repo.newEnv(b, fake.EditorConfig{ 284 Env: envvars, 285 Settings: map[string]interface{}{ 286 "completeUnimported": completeUnimported, 287 "completionBudget": budget, 288 }, 289 }, "completion", false) 290 defer env.Close() 291 292 env.CreateBuffer(test.file, "// __TEST_PLACEHOLDER_0__\n"+test.content) 293 editPlaceholder := func() { 294 edits := atomic.AddInt64(&editID, 1) 295 env.EditBuffer(test.file, protocol.TextEdit{ 296 Range: protocol.Range{ 297 Start: protocol.Position{Line: 0, Character: 0}, 298 End: protocol.Position{Line: 1, Character: 0}, 299 }, 300 // Increment the placeholder text, to ensure cache misses. 301 NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), 302 }) 303 } 304 env.AfterChange() 305 306 // Run a completion to make sure the system is warm. 307 loc := env.RegexpSearch(test.file, test.locationRegexp) 308 completions := env.Completion(loc) 309 310 if testing.Verbose() { 311 fmt.Println("Results:") 312 for i, item := range completions.Items { 313 fmt.Printf("\t%d. %v\n", i, item) 314 } 315 } 316 317 b.ResetTimer() 318 319 if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "completion")); stopAndRecord != nil { 320 defer stopAndRecord() 321 } 322 323 for i := 0; i < b.N; i++ { 324 if followingEdit { 325 editPlaceholder() 326 } 327 loc := env.RegexpSearch(test.file, test.locationRegexp) 328 env.Completion(loc) 329 } 330 }