sdk/retry: remove the need to pass args to NextOr

This commit is contained in:
Daniel Nephin 2021-04-23 16:47:22 -04:00
parent c932833acb
commit 034c5c5f8c
2 changed files with 45 additions and 24 deletions

View File

@ -120,15 +120,7 @@ func dedup(a []string) string {
func run(r Retryer, t Failer, f func(r *R)) { func run(r Retryer, t Failer, f func(r *R)) {
t.Helper() t.Helper()
rr := &R{} rr := &R{}
fail := func() { for r.Continue() {
t.Helper()
out := dedup(rr.output)
if out != "" {
t.Log(out)
}
t.FailNow()
}
for r.NextOr(t, fail) {
func() { func() {
defer func() { defer func() {
if p := recover(); p != nil && p != runFailed { if p := recover(); p != nil && p != runFailed {
@ -142,6 +134,12 @@ func run(r Retryer, t Failer, f func(r *R)) {
} }
rr.fail = false rr.fail = false
} }
out := dedup(rr.output)
if out != "" {
t.Log(out)
}
t.FailNow()
} }
// DefaultFailer provides default retry.Run() behavior for unit tests. // DefaultFailer provides default retry.Run() behavior for unit tests.
@ -162,9 +160,9 @@ func ThreeTimes() *Counter {
// Retryer provides an interface for repeating operations // Retryer provides an interface for repeating operations
// until they succeed or an exit condition is met. // until they succeed or an exit condition is met.
type Retryer interface { type Retryer interface {
// NextOr returns true if the operation should be repeated. // NextOr returns true if the operation should be repeated, otherwise it
// Otherwise, it calls fail and returns false. // returns false to indicate retrying should stop.
NextOr(t Failer, fail func()) bool Continue() bool
} }
// Counter repeats an operation a given number of // Counter repeats an operation a given number of
@ -176,10 +174,8 @@ type Counter struct {
count int count int
} }
func (r *Counter) NextOr(t Failer, fail func()) bool { func (r *Counter) Continue() bool {
t.Helper()
if r.count == r.Count { if r.count == r.Count {
fail()
return false return false
} }
if r.count > 0 { if r.count > 0 {
@ -200,14 +196,12 @@ type Timer struct {
stop time.Time stop time.Time
} }
func (r *Timer) NextOr(t Failer, fail func()) bool { func (r *Timer) Continue() bool {
t.Helper()
if r.stop.IsZero() { if r.stop.IsZero() {
r.stop = time.Now().Add(r.Timeout) r.stop = time.Now().Add(r.Timeout)
return true return true
} }
if time.Now().After(r.stop) { if time.Now().After(r.stop) {
fail()
return false return false
} }
time.Sleep(r.Wait) time.Sleep(r.Wait)

View File

@ -3,6 +3,8 @@ package retry
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
) )
// delta defines the time band a test run should complete in. // delta defines the time band a test run should complete in.
@ -19,19 +21,15 @@ func TestRetryer(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
var iters, fails int var iters int
fail := func() { fails++ }
start := time.Now() start := time.Now()
for tt.r.NextOr(t, fail) { for tt.r.Continue() {
iters++ iters++
} }
dur := time.Since(start) dur := time.Since(start)
if got, want := iters, 3; got != want { if got, want := iters, 3; got != want {
t.Fatalf("got %d retries want %d", got, want) t.Fatalf("got %d retries want %d", got, want)
} }
if got, want := fails, 1; got != want {
t.Fatalf("got %d FailNow calls want %d", got, want)
}
// since the first iteration happens immediately // since the first iteration happens immediately
// the retryer waits only twice for three iterations. // the retryer waits only twice for three iterations.
// order of events: (true, (wait) true, (wait) true, false) // order of events: (true, (wait) true, (wait) true, false)
@ -41,3 +39,32 @@ func TestRetryer(t *testing.T) {
}) })
} }
} }
func TestRunWith(t *testing.T) {
t.Run("calls FailNow after exceeding retries", func(t *testing.T) {
ft := &fakeT{}
iter := 0
RunWith(&Counter{Count: 3, Wait: time.Millisecond}, ft, func(r *R) {
iter++
r.FailNow()
})
require.Equal(t, 3, iter)
require.Equal(t, 1, ft.fails)
})
}
type fakeT struct {
fails int
}
func (f *fakeT) Helper() {}
func (f *fakeT) Log(args ...interface{}) {
}
func (f *fakeT) FailNow() {
f.fails++
}
var _ Failer = &fakeT{}