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 bench 6 7import ( 8 "flag" 9 "fmt" 10 "testing" 11 "time" 12 13 "golang.org/x/tools/gopls/internal/hooks" 14 "golang.org/x/tools/internal/lsp/fake" 15 . "golang.org/x/tools/internal/lsp/regtest" 16 17 "golang.org/x/tools/internal/lsp/protocol" 18) 19 20func TestMain(m *testing.M) { 21 Main(m, hooks.Options) 22} 23 24func benchmarkOptions(dir string) []RunOption { 25 return []RunOption{ 26 // Run in an existing directory, since we're trying to simulate known cases 27 // that cause gopls memory problems. 28 InExistingDir(dir), 29 // Skip logs as they buffer up memory unnaturally. 30 SkipLogs(), 31 // The Debug server only makes sense if running in singleton mode. 32 Modes(Singleton), 33 // Set a generous timeout. Individual tests should control their own 34 // graceful termination. 35 Timeout(20 * time.Minute), 36 37 // Use the actual proxy, since we want our builds to succeed. 38 GOPROXY("https://proxy.golang.org"), 39 } 40} 41 42func printBenchmarkResults(result testing.BenchmarkResult) { 43 fmt.Println("Benchmark Statistics:") 44 fmt.Println(result.String()) 45 fmt.Println(result.MemString()) 46} 47 48var iwlOptions struct { 49 workdir string 50} 51 52func init() { 53 flag.StringVar(&iwlOptions.workdir, "iwl_workdir", "", "if set, run IWL benchmark in this directory") 54} 55 56func TestBenchmarkIWL(t *testing.T) { 57 if iwlOptions.workdir == "" { 58 t.Skip("-iwl_workdir not configured") 59 } 60 61 opts := stressTestOptions(iwlOptions.workdir) 62 // Don't skip hooks, so that we can wait for IWL. 63 opts = append(opts, SkipHooks(false)) 64 65 results := testing.Benchmark(func(b *testing.B) { 66 for i := 0; i < b.N; i++ { 67 WithOptions(opts...).Run(t, "", func(t *testing.T, env *Env) {}) 68 } 69 }) 70 71 printBenchmarkResults(results) 72} 73 74var symbolOptions struct { 75 workdir, query, matcher, style string 76 printResults bool 77} 78 79func init() { 80 flag.StringVar(&symbolOptions.workdir, "symbol_workdir", "", "if set, run symbol benchmark in this directory") 81 flag.StringVar(&symbolOptions.query, "symbol_query", "test", "symbol query to use in benchmark") 82 flag.StringVar(&symbolOptions.matcher, "symbol_matcher", "", "symbol matcher to use in benchmark") 83 flag.StringVar(&symbolOptions.style, "symbol_style", "", "symbol style to use in benchmark") 84 flag.BoolVar(&symbolOptions.printResults, "symbol_print_results", false, "whether to print symbol query results") 85} 86 87func TestBenchmarkSymbols(t *testing.T) { 88 if symbolOptions.workdir == "" { 89 t.Skip("-symbol_workdir not configured") 90 } 91 92 opts := stressTestOptions(symbolOptions.workdir) 93 conf := EditorConfig{} 94 if symbolOptions.matcher != "" { 95 conf.SymbolMatcher = &symbolOptions.matcher 96 } 97 if symbolOptions.style != "" { 98 conf.SymbolStyle = &symbolOptions.style 99 } 100 opts = append(opts, conf) 101 102 WithOptions(opts...).Run(t, "", func(t *testing.T, env *Env) { 103 // We can't Await in this test, since we have disabled hooks. Instead, run 104 // one symbol request to completion to ensure all necessary cache entries 105 // are populated. 106 symbols, err := env.Editor.Server.Symbol(env.Ctx, &protocol.WorkspaceSymbolParams{ 107 Query: symbolOptions.query, 108 }) 109 if err != nil { 110 t.Fatal(err) 111 } 112 113 if symbolOptions.printResults { 114 fmt.Println("Results:") 115 for i := 0; i < len(symbols); i++ { 116 fmt.Printf("\t%d. %s (%s)\n", i, symbols[i].Name, symbols[i].ContainerName) 117 } 118 } 119 120 results := testing.Benchmark(func(b *testing.B) { 121 for i := 0; i < b.N; i++ { 122 if _, err := env.Editor.Server.Symbol(env.Ctx, &protocol.WorkspaceSymbolParams{ 123 Query: symbolOptions.query, 124 }); err != nil { 125 t.Fatal(err) 126 } 127 } 128 }) 129 printBenchmarkResults(results) 130 }) 131} 132 133var ( 134 benchDir = flag.String("didchange_dir", "", "If set, run benchmarks in this dir. Must also set regtest_bench_file.") 135 benchFile = flag.String("didchange_file", "", "The file to modify") 136) 137 138// TestBenchmarkDidChange benchmarks modifications of a single file by making 139// synthetic modifications in a comment. It controls pacing by waiting for the 140// server to actually start processing the didChange notification before 141// proceeding. Notably it does not wait for diagnostics to complete. 142// 143// Run it by passing -didchange_dir and -didchange_file, where -didchange_dir 144// is the path to a workspace root, and -didchange_file is the 145// workspace-relative path to a file to modify. e.g.: 146// 147// go test -run=TestBenchmarkDidChange \ 148// -didchange_dir=path/to/kubernetes \ 149// -didchange_file=pkg/util/hash/hash.go 150func TestBenchmarkDidChange(t *testing.T) { 151 if *benchDir == "" { 152 t.Skip("-didchange_dir is not set") 153 } 154 if *benchFile == "" { 155 t.Fatal("-didchange_file must be set if -didchange_dir is set") 156 } 157 158 opts := benchmarkOptions(*benchDir) 159 WithOptions(opts...).Run(t, "", func(_ *testing.T, env *Env) { 160 env.OpenFile(*benchFile) 161 env.Await(env.DoneWithOpen()) 162 // Insert the text we'll be modifying at the top of the file. 163 env.EditBuffer(*benchFile, fake.Edit{Text: "// __REGTEST_PLACEHOLDER_0__\n"}) 164 result := testing.Benchmark(func(b *testing.B) { 165 b.ResetTimer() 166 for i := 0; i < b.N; i++ { 167 env.EditBuffer(*benchFile, fake.Edit{ 168 Start: fake.Pos{Line: 0, Column: 0}, 169 End: fake.Pos{Line: 1, Column: 0}, 170 // Increment 171 Text: fmt.Sprintf("// __REGTEST_PLACEHOLDER_%d__\n", i+1), 172 }) 173 env.Await(StartedChange(uint64(i + 1))) 174 } 175 b.StopTimer() 176 }) 177 printBenchmarkResults(result) 178 }) 179} 180