internal/test/integration: materialize startedWork and completedWork
While benchmarking, I noticed that the didChange benchmarks become much
slower on successive runs in the same session (-count=10), despite using
the same amount of server-side CPU per operation. Investigation revealed
that this was due to the time spent client side repeatedly counting
started work items. That predicate initially assumed a small number of
operations, but for benchmarks there will be tens or hundreds of
thousands of work items, as the didChange benchmarks run fast and share
a common session.
Fix this by materializing the startedWork and completedWork maps.
Change-Id: I4e96648cfd0f5af2285a35891f43a77143798856
Reviewed-on: https://go-review.googlesource.com/c/tools/+/586455
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/test/integration/env.go b/gopls/internal/test/integration/env.go
index 1d9091c..b8a695f 100644
--- a/gopls/internal/test/integration/env.go
+++ b/gopls/internal/test/integration/env.go
@@ -55,8 +55,10 @@
return &Awaiter{
workdir: workdir,
state: State{
- diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
- work: make(map[protocol.ProgressToken]*workProgress),
+ diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
+ work: make(map[protocol.ProgressToken]*workProgress),
+ startedWork: make(map[string]uint64),
+ completedWork: make(map[string]uint64),
},
waiters: make(map[int]*condition),
}
@@ -91,35 +93,16 @@
unregistrations []*protocol.UnregistrationParams
// outstandingWork is a map of token->work summary. All tokens are assumed to
- // be string, though the spec allows for numeric tokens as well. When work
- // completes, it is deleted from this map.
- work map[protocol.ProgressToken]*workProgress
-}
-
-// completedWork counts complete work items by title.
-func (s State) completedWork() map[string]uint64 {
- completed := make(map[string]uint64)
- for _, work := range s.work {
- if work.complete {
- completed[work.title]++
- }
- }
- return completed
-}
-
-// startedWork counts started (and possibly complete) work items.
-func (s State) startedWork() map[string]uint64 {
- started := make(map[string]uint64)
- for _, work := range s.work {
- started[work.title]++
- }
- return started
+ // be string, though the spec allows for numeric tokens as well.
+ work map[protocol.ProgressToken]*workProgress
+ startedWork map[string]uint64 // title -> count of 'begin'
+ completedWork map[string]uint64 // title -> count of 'end'
}
type workProgress struct {
title, msg, endMsg string
percent float64
- complete bool // seen 'end'.
+ complete bool // seen 'end'
}
// This method, provided for debugging, accesses mutable fields without a lock,
@@ -157,7 +140,7 @@
fmt.Fprintf(&b, "\t%s: %.2f\n", name, state.percent)
}
b.WriteString("#### completed work:\n")
- for name, count := range s.completedWork() {
+ for name, count := range s.completedWork {
fmt.Fprintf(&b, "\t%s: %d\n", name, count)
}
return b.String()
@@ -236,6 +219,7 @@
switch kind := v["kind"]; kind {
case "begin":
work.title = v["title"].(string)
+ a.state.startedWork[work.title]++
if msg, ok := v["message"]; ok {
work.msg = msg.(string)
}
@@ -248,6 +232,7 @@
}
case "end":
work.complete = true
+ a.state.completedWork[work.title]++
if msg, ok := v["message"]; ok {
work.endMsg = msg.(string)
}
diff --git a/gopls/internal/test/integration/env_test.go b/gopls/internal/test/integration/env_test.go
index 02bacd0..32203f7 100644
--- a/gopls/internal/test/integration/env_test.go
+++ b/gopls/internal/test/integration/env_test.go
@@ -15,7 +15,9 @@
func TestProgressUpdating(t *testing.T) {
a := &Awaiter{
state: State{
- work: make(map[protocol.ProgressToken]*workProgress),
+ work: make(map[protocol.ProgressToken]*workProgress),
+ startedWork: make(map[string]uint64),
+ completedWork: make(map[string]uint64),
},
}
ctx := context.Background()
@@ -55,8 +57,8 @@
t.Fatal(err)
}
}
- if !a.state.work["foo"].complete {
- t.Error("work entry \"foo\" is incomplete, want complete")
+ if got, want := a.state.completedWork["foo work"], uint64(1); got != want {
+ t.Errorf(`completedWork["foo work"] = %d, want %d`, got, want)
}
got := *a.state.work["bar"]
want := workProgress{title: "bar work", percent: 42}
diff --git a/gopls/internal/test/integration/expectation.go b/gopls/internal/test/integration/expectation.go
index b749800..fcc8459 100644
--- a/gopls/internal/test/integration/expectation.go
+++ b/gopls/internal/test/integration/expectation.go
@@ -407,7 +407,7 @@
// See CompletedWork.
func StartedWork(title string, atLeast uint64) Expectation {
check := func(s State) Verdict {
- if s.startedWork()[title] >= atLeast {
+ if s.startedWork[title] >= atLeast {
return Met
}
return Unmet
@@ -424,8 +424,8 @@
// progress notification title to identify the work we expect to be completed.
func CompletedWork(title string, count uint64, atLeast bool) Expectation {
check := func(s State) Verdict {
- completed := s.completedWork()
- if completed[title] == count || atLeast && completed[title] > count {
+ completed := s.completedWork[title]
+ if completed == count || atLeast && completed > count {
return Met
}
return Unmet