|
5 | 5 | package lsp
|
6 | 6 |
|
7 | 7 | import (
|
| 8 | + "fmt" |
| 9 | + "strings" |
8 | 10 | "sync"
|
9 | 11 | "testing"
|
10 | 12 | "time"
|
| 13 | + |
| 14 | + errors "golang.org/x/xerrors" |
11 | 15 | )
|
12 | 16 |
|
13 | 17 | func TestDebouncer(t *testing.T) {
|
14 | 18 | t.Parallel()
|
| 19 | + |
| 20 | + const evtWait = 30 * time.Millisecond |
| 21 | + const initialDelay = 100 * time.Millisecond |
| 22 | + |
15 | 23 | type event struct {
|
16 | 24 | key string
|
17 | 25 | order uint64
|
@@ -58,30 +66,69 @@ func TestDebouncer(t *testing.T) {
|
58 | 66 | test := test
|
59 | 67 | t.Run(test.label, func(t *testing.T) {
|
60 | 68 | 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 | + } |
76 | 94 | }
|
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")) |
83 | 107 | }
|
| 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) |
84 | 114 | }
|
85 | 115 | })
|
86 | 116 | }
|
87 | 117 | }
|
| 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