5
5
"time"
6
6
)
7
7
8
+ type failNowPanic struct {}
9
+
8
10
type retryableT struct {
9
11
testing.TB
10
12
@@ -16,39 +18,42 @@ type retryableT struct {
16
18
}
17
19
18
20
func (r * retryableT ) Cleanup (func ()) {
19
- // keep track of fuctions to run at the end of the current attempt
21
+ // TODO: keep track of fuctions to run at the end of the current attempt
20
22
}
21
23
24
+ // TODO: should honor Skips too
25
+
22
26
func (r * retryableT ) Error (args ... any ) {
23
- // write to ttb .Log
24
- // set r failed to true
27
+ r .Log ( args ... )
28
+ r . Fail ()
25
29
}
26
30
27
31
func (r * retryableT ) Errorf (format string , args ... any ) {
28
- // write to ttb .Logf
29
- // set r failed to true
32
+ r .Logf ( format , args ... )
33
+ r . Fail ()
30
34
}
31
35
32
36
func (r * retryableT ) Fail () {
33
- // set r failed to true
37
+ r . failed = true
34
38
}
35
39
36
40
func (r * retryableT ) FailNow () {
37
- // panic with a special error type
41
+ r .failed = true
42
+ panic (failNowPanic {})
38
43
}
39
44
40
45
func (r * retryableT ) Failed () bool {
41
46
return r .failed
42
47
}
43
48
44
49
func (r * retryableT ) Fatal (args ... any ) {
45
- // write to ttb .Log
46
- // panic with a special error type
50
+ r .Log ( args ... )
51
+ r . FailNow ()
47
52
}
48
53
49
54
func (r * retryableT ) Fatalf (format string , args ... any ) {
50
- // write to ttb .Logf
51
- // panic with a special error type
55
+ r .Logf ( format , args ... )
56
+ r . FailNow ()
52
57
}
53
58
54
59
type Option func (* retryableT )
@@ -72,7 +77,63 @@ func WithMaxAttempts(attempts int) Option {
72
77
}
73
78
74
79
func Must (t testing.TB , f func (t testing.TB ), options ... Option ) {
80
+ keepTrying (t , f , t .Fatalf , options ... )
75
81
}
76
82
77
83
func Should (t testing.TB , f func (t testing.TB ), options ... Option ) {
84
+ keepTrying (t , f , t .Errorf , options ... )
85
+ }
86
+
87
+ func keepTrying (t testing.TB , f func (t testing.TB ), failf func (format string , args ... any ), options ... Option ) {
88
+ retryable := & retryableT {
89
+ TB : t ,
90
+ }
91
+
92
+ for _ , option := range options {
93
+ option (retryable )
94
+ }
95
+
96
+ start := time .Now ()
97
+ attempts := 0
98
+
99
+ for {
100
+ if attempts >= retryable .maxAttempts && retryable .maxAttempts > 0 {
101
+ // max attempts reached
102
+ failf ("eventually: max attempts reached" )
103
+ return
104
+ }
105
+ attempts ++
106
+
107
+ retryable .run (f )
108
+
109
+ // test passed
110
+ if ! retryable .failed {
111
+ break
112
+ }
113
+
114
+ if time .Since (start ) >= retryable .timeout && retryable .timeout > 0 {
115
+ // timeout reached
116
+ failf ("eventually: timeout reached" )
117
+ return
118
+ }
119
+
120
+ // test failed, wait for interval
121
+ time .Sleep (retryable .interval )
122
+ }
123
+ }
124
+
125
+ func (r * retryableT ) run (f func (t testing.TB )) {
126
+ r .failed = false
127
+
128
+ defer func () {
129
+ if err := recover (); err != nil {
130
+ if _ , ok := err .(failNowPanic ); ok {
131
+ return
132
+ }
133
+
134
+ panic (err )
135
+ }
136
+ }()
137
+
138
+ f (r )
78
139
}
0 commit comments