File: didchange_test.go

package info (click to toggle)
golang-golang-x-tools 1%3A0.25.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 22,724 kB
  • sloc: javascript: 2,027; asm: 1,645; sh: 166; yacc: 155; makefile: 49; ansic: 8
file content (142 lines) | stat: -rw-r--r-- 4,765 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2022 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 (
	"fmt"
	"sync/atomic"
	"testing"
	"time"

	"golang.org/x/tools/gopls/internal/protocol"
	"golang.org/x/tools/gopls/internal/test/integration/fake"
)

// Use a global edit counter as bench function may execute multiple times, and
// we want to avoid cache hits. Use time.Now to also avoid cache hits from the
// shared file cache.
var editID int64 = time.Now().UnixNano()

type changeTest struct {
	repo    string
	file    string
	canSave bool
}

var didChangeTests = []changeTest{
	{"google-cloud-go", "internal/annotate.go", true},
	{"istio", "pkg/fuzz/util.go", true},
	{"kubernetes", "pkg/controller/lookup_cache.go", true},
	{"kuma", "api/generic/insights.go", true},
	{"oracle", "dataintegration/data_type.go", false}, // diagnoseSave fails because this package is generated
	{"pkgsite", "internal/frontend/server.go", true},
	{"starlark", "starlark/eval.go", true},
	{"tools", "internal/lsp/cache/snapshot.go", true},
}

// BenchmarkDidChange 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.
func BenchmarkDidChange(b *testing.B) {
	for _, test := range didChangeTests {
		b.Run(test.repo, func(b *testing.B) {
			env := getRepo(b, test.repo).sharedEnv(b)
			env.OpenFile(test.file)
			defer closeBuffer(b, env, test.file)

			// Insert the text we'll be modifying at the top of the file.
			env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"})
			env.AfterChange()
			b.ResetTimer()

			if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "didchange")); stopAndRecord != nil {
				defer stopAndRecord()
			}

			for i := 0; i < b.N; i++ {
				edits := atomic.AddInt64(&editID, 1)
				env.EditBuffer(test.file, protocol.TextEdit{
					Range: protocol.Range{
						Start: protocol.Position{Line: 0, Character: 0},
						End:   protocol.Position{Line: 1, Character: 0},
					},
					// Increment the placeholder text, to ensure cache misses.
					NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits),
				})
				env.Await(env.StartedChange())
			}
		})
	}
}

func BenchmarkDiagnoseChange(b *testing.B) {
	for _, test := range didChangeTests {
		runChangeDiagnosticsBenchmark(b, test, false, "diagnoseChange")
	}
}

// TODO(rfindley): add a benchmark for with a metadata-affecting change, when
// this matters.
func BenchmarkDiagnoseSave(b *testing.B) {
	for _, test := range didChangeTests {
		runChangeDiagnosticsBenchmark(b, test, true, "diagnoseSave")
	}
}

// runChangeDiagnosticsBenchmark runs a benchmark to edit the test file and
// await the resulting diagnostics pass. If save is set, the file is also saved.
func runChangeDiagnosticsBenchmark(b *testing.B, test changeTest, save bool, operation string) {
	b.Run(test.repo, func(b *testing.B) {
		if !test.canSave {
			b.Skipf("skipping as %s cannot be saved", test.file)
		}
		sharedEnv := getRepo(b, test.repo).sharedEnv(b)
		config := fake.EditorConfig{
			Env: map[string]string{
				"GOPATH": sharedEnv.Sandbox.GOPATH(),
			},
			Settings: map[string]interface{}{
				"diagnosticsDelay": "0s",
			},
		}
		// Use a new env to avoid the diagnostic delay: we want to measure how
		// long it takes to produce the diagnostics.
		env := getRepo(b, test.repo).newEnv(b, config, operation, false)
		defer env.Close()
		env.OpenFile(test.file)
		// Insert the text we'll be modifying at the top of the file.
		env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"})
		if save {
			env.SaveBuffer(test.file)
		}
		env.AfterChange()
		b.ResetTimer()

		// We must use an extra subtest layer here, so that we only set up the
		// shared env once (otherwise we pay additional overhead and the profiling
		// flags don't work).
		b.Run("diagnose", func(b *testing.B) {
			if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, operation)); stopAndRecord != nil {
				defer stopAndRecord()
			}
			for i := 0; i < b.N; i++ {
				edits := atomic.AddInt64(&editID, 1)
				env.EditBuffer(test.file, protocol.TextEdit{
					Range: protocol.Range{
						Start: protocol.Position{Line: 0, Character: 0},
						End:   protocol.Position{Line: 1, Character: 0},
					},
					// Increment the placeholder text, to ensure cache misses.
					NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits),
				})
				if save {
					env.SaveBuffer(test.file)
				}
				env.AfterChange()
			}
		})
	})
}