Skip to content

Commit 41a65bd

Browse files
committed
internal/lsp: avoid flake in TestDebouncer
TestDebouncer inherently depends on the timing of events. Specifically, it can fail if the pause between two subsequent events is more than the debouncing delay. When this has proved flaky in the past I just increased the debouncing delay. However, tests are still occasionally flaking. Rather than just increase the delay arbitrarily, attempt to make the test more robust by retrying up to three times if the base assumption (that goroutines are scheduled within a reasonable amount of time) is not met. Fixes golang/go#45085 Change-Id: Ifa32e695d64ae4bcfe9600a0413bf6358dff9b7a Reviewed-on: https://go-review.googlesource.com/c/tools/+/309276 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 1c9019e commit 41a65bd

File tree

1 file changed

+68
-21
lines changed

1 file changed

+68
-21
lines changed

internal/lsp/debounce_test.go

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55
package lsp
66

77
import (
8+
"fmt"
9+
"strings"
810
"sync"
911
"testing"
1012
"time"
13+
14+
errors "golang.org/x/xerrors"
1115
)
1216

1317
func TestDebouncer(t *testing.T) {
1418
t.Parallel()
19+
20+
const evtWait = 30 * time.Millisecond
21+
const initialDelay = 100 * time.Millisecond
22+
1523
type event struct {
1624
key string
1725
order uint64
@@ -58,30 +66,69 @@ func TestDebouncer(t *testing.T) {
5866
test := test
5967
t.Run(test.label, func(t *testing.T) {
6068
t.Parallel()
61-
d := newDebouncer()
62-
var wg sync.WaitGroup
63-
for i, e := range test.events {
64-
wg.Add(1)
65-
go func(e *event) {
66-
d.debounce(e.key, e.order, 500*time.Millisecond, func() {
67-
e.fired = true
68-
})
69-
wg.Done()
70-
}(e)
71-
// For a bit more fidelity, sleep to try to make things actually
72-
// execute in order. This doesn't have to be perfect, but could be done
73-
// properly using fake timers.
74-
if i < len(test.events)-1 {
75-
time.Sleep(10 * time.Millisecond)
69+
70+
try := func(delay time.Duration) (bool, error) {
71+
d := newDebouncer()
72+
var wg sync.WaitGroup
73+
valid := true
74+
for i, e := range test.events {
75+
wg.Add(1)
76+
start := time.Now()
77+
go func(e *event) {
78+
if time.Since(start) > evtWait {
79+
// Due to slow scheduling this event is likely to have fired out
80+
// of order, so mark this attempt as invalid.
81+
valid = false
82+
}
83+
d.debounce(e.key, e.order, delay, func() {
84+
e.fired = true
85+
})
86+
wg.Done()
87+
}(e)
88+
// For a bit more fidelity, sleep to try to make things actually
89+
// execute in order. This shouldn't have to be perfect: as long as we
90+
// don't have extreme pauses the test should still pass.
91+
if i < len(test.events)-1 {
92+
time.Sleep(evtWait)
93+
}
7694
}
77-
}
78-
wg.Wait()
79-
for _, event := range test.events {
80-
if event.fired != event.wantFired {
81-
t.Errorf("(key: %q, order: %d): fired = %t, want %t",
82-
event.key, event.order, event.fired, event.wantFired)
95+
wg.Wait()
96+
var errs []string
97+
for _, event := range test.events {
98+
if event.fired != event.wantFired {
99+
msg := fmt.Sprintf("(key: %q, order: %d): fired = %t, want %t",
100+
event.key, event.order, event.fired, event.wantFired)
101+
errs = append(errs, msg)
102+
}
103+
}
104+
var err error
105+
if len(errs) > 0 {
106+
err = errors.New(strings.Join(errs, "\n"))
83107
}
108+
// If the test took less than maxwait, no event before the
109+
return valid, err
110+
}
111+
112+
if err := retryInvalid(100*time.Millisecond, try); err != nil {
113+
t.Error(err)
84114
}
85115
})
86116
}
87117
}
118+
119+
// retryInvalid runs the try func up to three times with exponential back-off
120+
// in its duration argument, starting with the provided initial duration. Calls
121+
// to try are retried if their first result indicates that they may be invalid,
122+
// and their second result is a non-nil error.
123+
func retryInvalid(initial time.Duration, try func(time.Duration) (valid bool, err error)) (err error) {
124+
delay := initial
125+
for attempts := 3; attempts > 0; attempts-- {
126+
var valid bool
127+
valid, err = try(delay)
128+
if err == nil || valid {
129+
return err
130+
}
131+
delay += delay
132+
}
133+
return err
134+
}

0 commit comments

Comments
 (0)