golang.org/x/tools/gopls@v0.15.3/internal/test/integration/bench/didchange_test.go (about) 1 // Copyright 2022 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 "fmt" 9 "sync/atomic" 10 "testing" 11 "time" 12 13 "golang.org/x/tools/gopls/internal/protocol" 14 "golang.org/x/tools/gopls/internal/test/integration/fake" 15 ) 16 17 // Use a global edit counter as bench function may execute multiple times, and 18 // we want to avoid cache hits. Use time.Now to also avoid cache hits from the 19 // shared file cache. 20 var editID int64 = time.Now().UnixNano() 21 22 type changeTest struct { 23 repo string 24 file string 25 canSave bool 26 } 27 28 var didChangeTests = []changeTest{ 29 {"google-cloud-go", "internal/annotate.go", true}, 30 {"istio", "pkg/fuzz/util.go", true}, 31 {"kubernetes", "pkg/controller/lookup_cache.go", true}, 32 {"kuma", "api/generic/insights.go", true}, 33 {"oracle", "dataintegration/data_type.go", false}, // diagnoseSave fails because this package is generated 34 {"pkgsite", "internal/frontend/server.go", true}, 35 {"starlark", "starlark/eval.go", true}, 36 {"tools", "internal/lsp/cache/snapshot.go", true}, 37 } 38 39 // BenchmarkDidChange benchmarks modifications of a single file by making 40 // synthetic modifications in a comment. It controls pacing by waiting for the 41 // server to actually start processing the didChange notification before 42 // proceeding. Notably it does not wait for diagnostics to complete. 43 func BenchmarkDidChange(b *testing.B) { 44 for _, test := range didChangeTests { 45 b.Run(test.repo, func(b *testing.B) { 46 env := getRepo(b, test.repo).sharedEnv(b) 47 env.OpenFile(test.file) 48 defer closeBuffer(b, env, test.file) 49 50 // Insert the text we'll be modifying at the top of the file. 51 env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"}) 52 env.AfterChange() 53 b.ResetTimer() 54 55 if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "didchange")); stopAndRecord != nil { 56 defer stopAndRecord() 57 } 58 59 for i := 0; i < b.N; i++ { 60 edits := atomic.AddInt64(&editID, 1) 61 env.EditBuffer(test.file, protocol.TextEdit{ 62 Range: protocol.Range{ 63 Start: protocol.Position{Line: 0, Character: 0}, 64 End: protocol.Position{Line: 1, Character: 0}, 65 }, 66 // Increment the placeholder text, to ensure cache misses. 67 NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), 68 }) 69 env.Await(env.StartedChange()) 70 } 71 }) 72 } 73 } 74 75 func BenchmarkDiagnoseChange(b *testing.B) { 76 for _, test := range didChangeTests { 77 runChangeDiagnosticsBenchmark(b, test, false, "diagnoseChange") 78 } 79 } 80 81 // TODO(rfindley): add a benchmark for with a metadata-affecting change, when 82 // this matters. 83 func BenchmarkDiagnoseSave(b *testing.B) { 84 for _, test := range didChangeTests { 85 runChangeDiagnosticsBenchmark(b, test, true, "diagnoseSave") 86 } 87 } 88 89 // runChangeDiagnosticsBenchmark runs a benchmark to edit the test file and 90 // await the resulting diagnostics pass. If save is set, the file is also saved. 91 func runChangeDiagnosticsBenchmark(b *testing.B, test changeTest, save bool, operation string) { 92 b.Run(test.repo, func(b *testing.B) { 93 if !test.canSave { 94 b.Skipf("skipping as %s cannot be saved", test.file) 95 } 96 sharedEnv := getRepo(b, test.repo).sharedEnv(b) 97 config := fake.EditorConfig{ 98 Env: map[string]string{ 99 "GOPATH": sharedEnv.Sandbox.GOPATH(), 100 }, 101 Settings: map[string]interface{}{ 102 "diagnosticsDelay": "0s", 103 }, 104 } 105 // Use a new env to avoid the diagnostic delay: we want to measure how 106 // long it takes to produce the diagnostics. 107 env := getRepo(b, test.repo).newEnv(b, config, operation, false) 108 defer env.Close() 109 env.OpenFile(test.file) 110 // Insert the text we'll be modifying at the top of the file. 111 env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"}) 112 if save { 113 env.SaveBuffer(test.file) 114 } 115 env.AfterChange() 116 b.ResetTimer() 117 118 // We must use an extra subtest layer here, so that we only set up the 119 // shared env once (otherwise we pay additional overhead and the profiling 120 // flags don't work). 121 b.Run("diagnose", func(b *testing.B) { 122 if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, operation)); stopAndRecord != nil { 123 defer stopAndRecord() 124 } 125 for i := 0; i < b.N; i++ { 126 edits := atomic.AddInt64(&editID, 1) 127 env.EditBuffer(test.file, protocol.TextEdit{ 128 Range: protocol.Range{ 129 Start: protocol.Position{Line: 0, Character: 0}, 130 End: protocol.Position{Line: 1, Character: 0}, 131 }, 132 // Increment the placeholder text, to ensure cache misses. 133 NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), 134 }) 135 if save { 136 env.SaveBuffer(test.file) 137 } 138 env.AfterChange() 139 } 140 }) 141 }) 142 }