| // 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 bench |
| |
| import ( |
| "flag" |
| "fmt" |
| "testing" |
| "time" |
| |
| "golang.org/x/tools/gopls/internal/hooks" |
| "golang.org/x/tools/internal/lsp/fake" |
| . "golang.org/x/tools/internal/lsp/regtest" |
| |
| "golang.org/x/tools/internal/lsp/protocol" |
| ) |
| |
| func TestMain(m *testing.M) { |
| Main(m, hooks.Options) |
| } |
| |
| func benchmarkOptions(dir string) []RunOption { |
| return []RunOption{ |
| // Run in an existing directory, since we're trying to simulate known cases |
| // that cause gopls memory problems. |
| InExistingDir(dir), |
| // Skip logs as they buffer up memory unnaturally. |
| SkipLogs(), |
| // The Debug server only makes sense if running in singleton mode. |
| Modes(Singleton), |
| // Set a generous timeout. Individual tests should control their own |
| // graceful termination. |
| Timeout(20 * time.Minute), |
| |
| // Use the actual proxy, since we want our builds to succeed. |
| GOPROXY("https://proxy.golang.org"), |
| } |
| } |
| |
| func printBenchmarkResults(result testing.BenchmarkResult) { |
| fmt.Println("Benchmark Statistics:") |
| fmt.Println(result.String()) |
| fmt.Println(result.MemString()) |
| } |
| |
| var iwlOptions struct { |
| workdir string |
| } |
| |
| func init() { |
| flag.StringVar(&iwlOptions.workdir, "iwl_workdir", "", "if set, run IWL benchmark in this directory") |
| } |
| |
| func TestBenchmarkIWL(t *testing.T) { |
| if iwlOptions.workdir == "" { |
| t.Skip("-iwl_workdir not configured") |
| } |
| |
| opts := stressTestOptions(iwlOptions.workdir) |
| // Don't skip hooks, so that we can wait for IWL. |
| opts = append(opts, SkipHooks(false)) |
| |
| results := testing.Benchmark(func(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| WithOptions(opts...).Run(t, "", func(t *testing.T, env *Env) {}) |
| } |
| }) |
| |
| printBenchmarkResults(results) |
| } |
| |
| var symbolOptions struct { |
| workdir, query, matcher, style string |
| printResults bool |
| } |
| |
| func init() { |
| flag.StringVar(&symbolOptions.workdir, "symbol_workdir", "", "if set, run symbol benchmark in this directory") |
| flag.StringVar(&symbolOptions.query, "symbol_query", "test", "symbol query to use in benchmark") |
| flag.StringVar(&symbolOptions.matcher, "symbol_matcher", "", "symbol matcher to use in benchmark") |
| flag.StringVar(&symbolOptions.style, "symbol_style", "", "symbol style to use in benchmark") |
| flag.BoolVar(&symbolOptions.printResults, "symbol_print_results", false, "whether to print symbol query results") |
| } |
| |
| func TestBenchmarkSymbols(t *testing.T) { |
| if symbolOptions.workdir == "" { |
| t.Skip("-symbol_workdir not configured") |
| } |
| |
| opts := stressTestOptions(symbolOptions.workdir) |
| conf := EditorConfig{} |
| if symbolOptions.matcher != "" { |
| conf.SymbolMatcher = &symbolOptions.matcher |
| } |
| if symbolOptions.style != "" { |
| conf.SymbolStyle = &symbolOptions.style |
| } |
| opts = append(opts, conf) |
| |
| WithOptions(opts...).Run(t, "", func(t *testing.T, env *Env) { |
| // We can't Await in this test, since we have disabled hooks. Instead, run |
| // one symbol request to completion to ensure all necessary cache entries |
| // are populated. |
| symbols, err := env.Editor.Server.Symbol(env.Ctx, &protocol.WorkspaceSymbolParams{ |
| Query: symbolOptions.query, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if symbolOptions.printResults { |
| fmt.Println("Results:") |
| for i := 0; i < len(symbols); i++ { |
| fmt.Printf("\t%d. %s (%s)\n", i, symbols[i].Name, symbols[i].ContainerName) |
| } |
| } |
| |
| results := testing.Benchmark(func(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| if _, err := env.Editor.Server.Symbol(env.Ctx, &protocol.WorkspaceSymbolParams{ |
| Query: symbolOptions.query, |
| }); err != nil { |
| t.Fatal(err) |
| } |
| } |
| }) |
| printBenchmarkResults(results) |
| }) |
| } |
| |
| var ( |
| benchDir = flag.String("didchange_dir", "", "If set, run benchmarks in this dir. Must also set regtest_bench_file.") |
| benchFile = flag.String("didchange_file", "", "The file to modify") |
| ) |
| |
| // TestBenchmarkDidChange benchmarks modifications of a single file by making |
| // synthetic modifications in a comment. It controls pacing by waiting for the |
| // server to actually start processing the didChange notification before |
| // proceeding. Notably it does not wait for diagnostics to complete. |
| // |
| // Run it by passing -didchange_dir and -didchange_file, where -didchange_dir |
| // is the path to a workspace root, and -didchange_file is the |
| // workspace-relative path to a file to modify. e.g.: |
| // |
| // go test -run=TestBenchmarkDidChange \ |
| // -didchange_dir=path/to/kubernetes \ |
| // -didchange_file=pkg/util/hash/hash.go |
| func TestBenchmarkDidChange(t *testing.T) { |
| if *benchDir == "" { |
| t.Skip("-didchange_dir is not set") |
| } |
| if *benchFile == "" { |
| t.Fatal("-didchange_file must be set if -didchange_dir is set") |
| } |
| |
| opts := benchmarkOptions(*benchDir) |
| WithOptions(opts...).Run(t, "", func(_ *testing.T, env *Env) { |
| env.OpenFile(*benchFile) |
| env.Await(env.DoneWithOpen()) |
| // Insert the text we'll be modifying at the top of the file. |
| env.EditBuffer(*benchFile, fake.Edit{Text: "// __REGTEST_PLACEHOLDER_0__\n"}) |
| result := testing.Benchmark(func(b *testing.B) { |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| env.EditBuffer(*benchFile, fake.Edit{ |
| Start: fake.Pos{Line: 0, Column: 0}, |
| End: fake.Pos{Line: 1, Column: 0}, |
| // Increment |
| Text: fmt.Sprintf("// __REGTEST_PLACEHOLDER_%d__\n", i+1), |
| }) |
| env.Await(StartedChange(uint64(i + 1))) |
| } |
| b.StopTimer() |
| }) |
| printBenchmarkResults(result) |
| }) |
| } |