blob: 0712d6f284e3a1dc126c37405b310b68c4b0f86d [file] [log] [blame]
// 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 lsp
import (
"fmt"
"strings"
"sync"
"testing"
"time"
errors "golang.org/x/xerrors"
)
func TestDebouncer(t *testing.T) {
t.Parallel()
const evtWait = 30 * time.Millisecond
const initialDelay = 400 * time.Millisecond
type event struct {
key string
order uint64
fired bool
wantFired bool
}
tests := []struct {
label string
events []*event
}{
{
label: "overridden",
events: []*event{
{key: "a", order: 1, wantFired: false},
{key: "a", order: 2, wantFired: true},
},
},
{
label: "distinct labels",
events: []*event{
{key: "a", order: 1, wantFired: true},
{key: "b", order: 2, wantFired: true},
},
},
{
label: "reverse order",
events: []*event{
{key: "a", order: 2, wantFired: true},
{key: "a", order: 1, wantFired: false},
},
},
{
label: "multiple overrides",
events: []*event{
{key: "a", order: 1, wantFired: false},
{key: "a", order: 2, wantFired: false},
{key: "a", order: 3, wantFired: false},
{key: "a", order: 4, wantFired: false},
{key: "a", order: 5, wantFired: true},
},
},
}
for _, test := range tests {
test := test
t.Run(test.label, func(t *testing.T) {
try := func(delay time.Duration) (bool, error) {
d := newDebouncer()
var wg sync.WaitGroup
var validMu sync.Mutex
valid := true
prev := -1
for i, e := range test.events {
wg.Add(1)
go func(i int, e *event) {
// Check if goroutines were not scheduled in the intended order.
validMu.Lock()
if prev != i-1 {
valid = false
}
prev = i
validMu.Unlock()
d.debounce(e.key, e.order, delay, func() {
e.fired = true
})
wg.Done()
}(i, e)
// For a bit more fidelity, sleep to try to make things actually
// execute in order. This shouldn't have to be perfect: as long as we
// don't have extreme pauses the test should still pass.
if i < len(test.events)-1 {
time.Sleep(evtWait)
}
}
wg.Wait()
var errs []string
for _, event := range test.events {
if event.fired != event.wantFired {
msg := fmt.Sprintf("(key: %q, order: %d): fired = %t, want %t",
event.key, event.order, event.fired, event.wantFired)
errs = append(errs, msg)
}
}
var err error
if len(errs) > 0 {
err = errors.New(strings.Join(errs, "\n"))
}
return valid, err
}
if err := retryInvalid(initialDelay, try); err != nil {
t.Error(err)
}
})
}
}
// retryInvalid runs the try func up to three times with exponential back-off
// in its duration argument, starting with the provided initial duration. Calls
// to try are retried if their first result indicates that they may be invalid,
// and their second result is a non-nil error.
func retryInvalid(initial time.Duration, try func(time.Duration) (valid bool, err error)) (err error) {
delay := initial
for attempts := 3; attempts > 0; attempts-- {
var valid bool
valid, err = try(delay)
if err == nil || valid {
return err
}
delay += delay
}
return err
}