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
|
package test
import (
"errors"
"math/rand"
"testing"
"time"
)
type flaky struct {
t testing.TB
o *FlakyOptions
}
type FlakyOptions struct {
InitialBackoff time.Duration
MaxAttempts int
Jitter float64
}
func NewFlaky(t testing.TB, o *FlakyOptions) *flaky {
if o.InitialBackoff <= 0 {
o.InitialBackoff = 500 * time.Millisecond
}
if o.MaxAttempts <= 0 {
o.MaxAttempts = 3
}
if o.Jitter < 0 {
o.Jitter = 0
}
return &flaky{t: t, o: o}
}
func (f *flaky) Run(fn func(t testing.TB)) {
var last error
for attempt := 1; attempt <= f.o.MaxAttempts; attempt++ {
f.t.Logf("attempt %d of %d", attempt, f.o.MaxAttempts)
r := &recorder{
TB: f.t,
fail: func(s string) { last = errors.New(s) },
fatal: func(s string) { last = errors.New(s) },
}
func() {
defer func() {
if v := recover(); v != nil {
if code, ok := v.(int); ok && code != RecorderFailNow {
panic(v)
}
}
}()
fn(r)
}()
if !r.Failed() {
return
}
if attempt < f.o.MaxAttempts {
backoff := f.o.InitialBackoff * time.Duration(1<<uint(attempt-1))
time.Sleep(applyJitter(backoff, f.o.Jitter))
}
}
f.t.Fatalf("[%s] test failed after %d attempts: %v", f.t.Name(), f.o.MaxAttempts, last)
}
func applyJitter(d time.Duration, jitter float64) time.Duration {
if jitter == 0 {
return d
}
maxJitter := float64(d) * jitter
delta := maxJitter * (rand.Float64()*2 - 1)
return time.Duration(float64(d) + delta)
}
|