Skip to content

Commit

Permalink
refactor assert.Eventually to be consistent with other assertion func…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
adamluzsi committed Oct 17, 2023
1 parent eb4560a commit f422352
Show file tree
Hide file tree
Showing 19 changed files with 201 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Race_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

func TestRace(t *testing.T) {
eventually := assert.Eventually{RetryStrategy: assert.Waiter{Timeout: time.Second}}
eventually := assert.Retry{Strategy: assert.Waiter{Timeout: time.Second}}

t.Run(`functions run in race against each other`, func(t *testing.T) {
eventually.Assert(t, func(it assert.It) {
Expand Down
12 changes: 6 additions & 6 deletions Spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ type Spec struct {
parallel bool
sequential bool
skipBenchmark bool
flaky *assert.Eventually
eventually *assert.Eventually
flaky *assert.Retry
eventually *assert.Retry
group *struct{ name string }
description string
tags []string
Expand Down Expand Up @@ -290,24 +290,24 @@ func (spec *Spec) isBenchAllowedToRun() bool {
return true
}

func (spec *Spec) lookupRetryFlaky() (assert.Eventually, bool) {
func (spec *Spec) lookupRetryFlaky() (assert.Retry, bool) {
spec.testingTB.Helper()
for _, context := range spec.specsFromParent() {
if context.flaky != nil {
return *context.flaky, true
}
}
return assert.Eventually{}, false
return assert.Retry{}, false
}

func (spec *Spec) lookupRetryEventually() (assert.Eventually, bool) {
func (spec *Spec) lookupRetryEventually() (assert.Retry, bool) {
spec.testingTB.Helper()
for _, context := range spec.specsFromParent() {
if context.eventually != nil {
return *context.eventually, true
}
}
return assert.Eventually{}, false
return assert.Retry{}, false
}

func (spec *Spec) printDescription(tb testing.TB) {
Expand Down
6 changes: 3 additions & 3 deletions Spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1047,8 +1047,8 @@ func TestSpec_Test_flakyByRetry_willRunAgainWithTheProvidedRetry(t *testing.T) {
s := testcase.NewSpec(t)

var retryUsed bool
retry := assert.Eventually{
RetryStrategy: assert.RetryStrategyFunc(func(condition func() bool) {
retry := assert.Retry{
Strategy: assert.RetryStrategyFunc(func(condition func() bool) {
retryUsed = true
for condition() {
}
Expand Down Expand Up @@ -1158,7 +1158,7 @@ func TestSpec_Parallel_testPrepareActionsExecutedInParallel(t *testing.T) {
}

func TestSpec_Context_nonParallelTestExecutionOrder_isRandom(t *testing.T) {
assert.Eventually{RetryStrategy: assert.Waiter{WaitDuration: time.Second}}.Assert(t, func(it assert.It) {
assert.Retry{Strategy: assert.Waiter{WaitDuration: time.Second}}.Assert(t, func(it assert.It) {
var m sync.Mutex
out := make([]int, 0)
testcase.NewSpec(it).Context("", func(s *testcase.Spec) {
Expand Down
2 changes: 1 addition & 1 deletion T.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (t *T) hasOnLetHookApplied(name string) bool {
return false
}

var DefaultEventually = assert.Eventually{RetryStrategy: assert.Waiter{Timeout: 3 * time.Second}}
var DefaultEventually = assert.Retry{Strategy: assert.Waiter{Timeout: 3 * time.Second}}

// Eventually helper allows you to write expectations to results that will only be eventually true.
// A common scenario where using Eventually will benefit you is testing concurrent operations.
Expand Down
2 changes: 1 addition & 1 deletion T_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func TestT_HasTag(t *testing.T) {

func TestT_Random(t *testing.T) {
randomGenerationWorks := func(t *testcase.T) {
assert.Eventually{RetryStrategy: assert.Waiter{WaitDuration: time.Second}}.Assert(t, func(it assert.It) {
assert.Retry{Strategy: assert.Waiter{WaitDuration: time.Second}}.Assert(t, func(it assert.It) {
it.Must.True(0 < t.Random.Int())
})
}
Expand Down
14 changes: 14 additions & 0 deletions assert/Asserter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,3 +1049,17 @@ func (a Asserter) within(timeout time.Duration, blk func(context.Context)) bool
}
return atomic.LoadUint32(&done) == 1
}

func (a Asserter) Eventually(durationOrCount any, blk func(it It)) {
a.TB.Helper()
var retry Retry
switch v := durationOrCount.(type) {
case time.Duration:
retry = Retry{Strategy: Waiter{Timeout: v}}
case int:
retry = Retry{Strategy: RetryCount(v)}
default:
a.TB.Fatalf("%T is neither a duration or the number of times to retry", durationOrCount)
}
retry.Assert(a.TB, blk)
}
69 changes: 67 additions & 2 deletions assert/Asserter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@ func TestAsserter_Within(t *testing.T) {
})
assert.True(t, dtb.IsFailed)

assert.EventuallyWithin(3*time.Second).Assert(t, func(it assert.It) {
assert.MakeRetry(3*time.Second).Assert(t, func(it assert.It) {
it.Must.True(atomic.LoadInt32(&isCancelled) == 1)
})
})
Expand Down Expand Up @@ -1292,7 +1292,7 @@ func TestAsserter_NotWithin(t *testing.T) {
})
assert.False(t, dtb.IsFailed)

assert.EventuallyWithin(3*time.Second).Assert(t, func(it assert.It) {
assert.MakeRetry(3*time.Second).Assert(t, func(it assert.It) {
it.Must.True(atomic.LoadInt32(&isCancelled) == 1)
})
})
Expand Down Expand Up @@ -1965,3 +1965,68 @@ func TestRegisterEqual(t *testing.T) {
})
assert.False(t, dtb.IsFailed)
}

func TestAsserter_Eventually(t *testing.T) {
t.Run("happy - n times", func(t *testing.T) {
dtb := &doubles.TB{}
var ran int
sandbox.Run(func() {
subject := asserter(dtb)
var ok bool
subject.Eventually(2, func(it assert.It) {
ran++
if ok {
return // OK
}
ok = true
it.FailNow()
})
})

assert.False(t, dtb.IsFailed, "eventually pass")
})
t.Run("happy - n time duration", func(t *testing.T) {
dtb := &doubles.TB{}
sandbox.Run(func() {
subject := asserter(dtb)
tries := 128
subject.Eventually(time.Minute, func(it assert.It) {
tries--
if tries <= 0 {
return // OK
}
it.FailNow()
})
})
assert.False(t, dtb.IsFailed, "eventually pass")
})
t.Run("rainy - n times", func(t *testing.T) {
dtb := &doubles.TB{}
var tried int
sandbox.Run(func() {
subject := asserter(dtb)
subject.Eventually(2, func(it assert.It) {
tried++
it.FailNow()
})
})
assert.True(t, dtb.IsFailed, "eventually fail")
assert.NotEqual(t, tried, 0)
})
t.Run("rainy - n duration", func(t *testing.T) {
dtb := &doubles.TB{}
sandbox.Run(func() {
subject := asserter(dtb)
var ok bool
subject.Eventually(100*time.Millisecond, func(it assert.It) {
if ok {
return // OK which will never happen
}
ok = true
time.Sleep(150 * time.Millisecond)
it.FailNow()
})
})
assert.True(t, dtb.IsFailed, "eventually fail")
})
}
18 changes: 9 additions & 9 deletions assert/Eventually.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ import (
"go.llib.dev/testcase/internal/doubles"
)

func EventuallyWithin[T time.Duration | int](durationOrCount T) Eventually {
func MakeRetry[T time.Duration | int](durationOrCount T) Retry {
switch v := any(durationOrCount).(type) {
case time.Duration:
return Eventually{RetryStrategy: Waiter{Timeout: v}}
return Retry{Strategy: Waiter{Timeout: v}}
case int:
return Eventually{RetryStrategy: RetryCount(v)}
return Retry{Strategy: RetryCount(v)}
default:
panic("invalid usage")
panic("impossible usage")
}
}

// Eventually Automatically retries operations whose failure is expected under certain defined conditions.
// Retry Automatically retries operations whose failure is expected under certain defined conditions.
// This pattern enables fault-tolerance.
//
// A common scenario where using Eventually will benefit you is testing concurrent operations.
// A common scenario where using Retry will benefit you is testing concurrent operations.
// Due to the nature of async operations, one might need to wait
// and observe the system with multiple tries before the outcome can be seen.
type Eventually struct{ RetryStrategy RetryStrategy }
type Retry struct{ Strategy RetryStrategy }

type RetryStrategy interface {
// While implements the retry strategy looping part.
Expand All @@ -43,12 +43,12 @@ func (fn RetryStrategyFunc) While(condition func() bool) { fn(condition) }
// In case expectations are failed, it will retry the assertion block using the RetryStrategy.
// The last failed assertion results would be published to the received testing.TB.
// Calling multiple times the assertion function block content should be a safe and repeatable operation.
func (r Eventually) Assert(tb testing.TB, blk func(it It)) {
func (r Retry) Assert(tb testing.TB, blk func(it It)) {
tb.Helper()
var lastRecorder *doubles.RecorderTB

isFailed := tb.Failed()
r.RetryStrategy.While(func() bool {
r.Strategy.While(func() bool {
tb.Helper()
lastRecorder = &doubles.RecorderTB{TB: tb}
ro := sandbox.Run(func() {
Expand Down
32 changes: 16 additions & 16 deletions assert/Eventually_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ import (
"go.llib.dev/testcase"
)

func TestEventually(t *testing.T) {
SpecEventually(t)
func TestRetry(t *testing.T) {
SpecRetry(t)
}

func BenchmarkEventually(b *testing.B) {
SpecEventually(b)
SpecRetry(b)
}

func SpecEventually(tb testing.TB) {
func SpecRetry(tb testing.TB) {
s := testcase.NewSpec(tb)

var (
strategyWillRetry = testcase.Var[bool]{ID: `retry strategy will retry`}
strategyStub = testcase.Let(s, func(t *testcase.T) *stubRetryStrategy {
return &stubRetryStrategy{ShouldRetry: strategyWillRetry.Get(t)}
})
helper = testcase.Let(s, func(t *testcase.T) *assert.Eventually {
return &assert.Eventually{
RetryStrategy: strategyStub.Get(t),
helper = testcase.Let(s, func(t *testcase.T) *assert.Retry {
return &assert.Retry{
Strategy: strategyStub.Get(t),
}
})
)
Expand Down Expand Up @@ -353,8 +353,8 @@ func SpecEventually(tb testing.TB) {
}

func TestRetry_Assert_failsOnceButThenPass(t *testing.T) {
w := assert.Eventually{
RetryStrategy: assert.Waiter{
w := assert.Retry{
Strategy: assert.Waiter{
WaitDuration: 0,
Timeout: 42 * time.Second,
},
Expand Down Expand Up @@ -388,8 +388,8 @@ func TestRetry_Assert_failsOnceButThenPass(t *testing.T) {

func TestRetry_Assert_panic(t *testing.T) {
rnd := random.New(random.CryptoSeed{})
w := assert.Eventually{
RetryStrategy: assert.RetryStrategyFunc(func(condition func() bool) {
w := assert.Retry{
Strategy: assert.RetryStrategyFunc(func(condition func() bool) {
for condition() {
}
}),
Expand Down Expand Up @@ -488,11 +488,11 @@ func TestRetryCount_While(t *testing.T) {
})
}

func TestEventuallyWithin(t *testing.T) {
func TestMakeRetry(t *testing.T) {
t.Run("time.Duration", func(t *testing.T) {
t.Run("on timeout", func(t *testing.T) {
it := assert.MakeIt(t)
e := assert.EventuallyWithin(time.Millisecond)
e := assert.MakeRetry(time.Millisecond)
dtb := &doubles.TB{}

t1 := time.Now()
Expand All @@ -506,7 +506,7 @@ func TestEventuallyWithin(t *testing.T) {
})
t.Run("within the time", func(t *testing.T) {
it := assert.MakeIt(t)
e := assert.EventuallyWithin(time.Millisecond)
e := assert.MakeRetry(time.Millisecond)
dtb := &doubles.TB{}

t1 := time.Now()
Expand All @@ -522,7 +522,7 @@ func TestEventuallyWithin(t *testing.T) {
t.Run("retry count", func(t *testing.T) {
t.Run("out of count", func(t *testing.T) {
it := assert.MakeIt(t)
e := assert.EventuallyWithin(3)
e := assert.MakeRetry(3)
dtb := &doubles.TB{}

e.Assert(dtb, func(it assert.It) {
Expand All @@ -534,7 +534,7 @@ func TestEventuallyWithin(t *testing.T) {
t.Run("within the count", func(t *testing.T) {
it := assert.MakeIt(t)

e := assert.EventuallyWithin(3)
e := assert.MakeRetry(3)
dtb := &doubles.TB{}

n := 3
Expand Down
Loading

0 comments on commit f422352

Please sign in to comment.