Skip to content

Commit a80a4da

Browse files
committed
Add implementation
1 parent 7ddc5a0 commit a80a4da

File tree

2 files changed

+96
-15
lines changed

2 files changed

+96
-15
lines changed

eventually.go

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"time"
66
)
77

8+
type failNowPanic struct{}
9+
810
type retryableT struct {
911
testing.TB
1012

@@ -16,39 +18,42 @@ type retryableT struct {
1618
}
1719

1820
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
2022
}
2123

24+
// TODO: should honor Skips too
25+
2226
func (r *retryableT) Error(args ...any) {
23-
// write to ttb.Log
24-
// set r failed to true
27+
r.Log(args...)
28+
r.Fail()
2529
}
2630

2731
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()
3034
}
3135

3236
func (r *retryableT) Fail() {
33-
// set r failed to true
37+
r.failed = true
3438
}
3539

3640
func (r *retryableT) FailNow() {
37-
// panic with a special error type
41+
r.failed = true
42+
panic(failNowPanic{})
3843
}
3944

4045
func (r *retryableT) Failed() bool {
4146
return r.failed
4247
}
4348

4449
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()
4752
}
4853

4954
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()
5257
}
5358

5459
type Option func(*retryableT)
@@ -72,7 +77,63 @@ func WithMaxAttempts(attempts int) Option {
7277
}
7378

7479
func Must(t testing.TB, f func(t testing.TB), options ...Option) {
80+
keepTrying(t, f, t.Fatalf, options...)
7581
}
7682

7783
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)
78139
}

eventually_test.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ func (t *test) FailNow() {
2424
t.halted = true
2525
}
2626

27+
func (t *test) Fatal(args ...interface{}) {
28+
t.Log(args...)
29+
t.FailNow()
30+
}
31+
32+
func (t *test) Fatalf(format string, args ...interface{}) {
33+
t.Logf(format, args...)
34+
t.FailNow()
35+
}
36+
37+
func (t *test) Error(args ...interface{}) {
38+
t.Log(args...)
39+
t.Fail()
40+
}
41+
42+
func (t *test) Errorf(format string, args ...interface{}) {
43+
t.Logf(format, args...)
44+
t.Fail()
45+
}
46+
2747
func (t *test) Log(args ...any) {
2848
t.logs = append(t.logs, fmt.Sprintln(args...))
2949
}
@@ -91,8 +111,8 @@ func TestMust(t *testing.T) {
91111
t.Fatalf("logs should contain 1 line, contained %d", len(tt.logs))
92112
}
93113

94-
if tt.logs[0] != "log" {
95-
t.Errorf("logs should contain 'log', contained %s", tt.logs[0])
114+
if tt.logs[0] != "log\n" {
115+
t.Errorf("logs should contain 'log', contained %q", tt.logs[0])
96116
}
97117
})
98118
}
@@ -156,8 +176,8 @@ func TestShould(t *testing.T) {
156176
t.Fatalf("logs should contain 1 line, contained %d", len(tt.logs))
157177
}
158178

159-
if tt.logs[0] != "log" {
160-
t.Errorf("logs should contain 'log', contained %s", tt.logs[0])
179+
if tt.logs[0] != "log\n" {
180+
t.Errorf("logs should contain 'log', contained %q", tt.logs[0])
161181
}
162182
})
163183
}

0 commit comments

Comments
 (0)