Skip to content

Commit

Permalink
use testcase.T in T#Eventually's block
Browse files Browse the repository at this point in the history
After extensive testing, we discovered that constantly switching between using `assert.It` and `testcase.T` is risky
and prone to errors without adding any benefit.
Therefore, our next step is to streamline the API to simplify testing.

The core assert.Eventually is unafected by this change and remains the same as before.
The biggest added benefit that now accessing testcase varibles from a Eventually block becomes trivial.
  • Loading branch information
adamluzsi committed Jun 12, 2024
1 parent 182b6e3 commit 546059e
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 142 deletions.
12 changes: 10 additions & 2 deletions T.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,21 @@ var DefaultEventually = assert.Retry{Strategy: assert.Waiter{Timeout: 3 * time.S
// Calling multiple times the assertion function block content should be a safe and repeatable operation.
// For more, read the documentation of Eventually and Eventually.Assert.
// In case Spec doesn't have a configuration for how to retry Eventually, the DefaultEventually will be used.
func (t *T) Eventually(blk func(t assert.It), retryOpts ...interface{}) {
func (t *T) Eventually(blk func(t *T)) {
t.TB.Helper()
retry, ok := t.spec.lookupRetryEventually()
if !ok {
retry = DefaultEventually
}
retry.Assert(t, blk)
retry.Assert(t, func(it assert.It) {
// since we use pointers, copy should not cause issue here.
// our only goal here is to avoid that the original T's .It field changed instead of a copy T's
copyT := *t
nT := &copyT
nT.It = it
nT.TB = it
blk(nT)
})
}

type timerManager interface {
Expand Down
120 changes: 117 additions & 3 deletions T_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,13 +367,15 @@ func TestT_Random(t *testing.T) {
}

func TestT_Eventually(t *testing.T) {
rnd := random.New(random.CryptoSeed{})

t.Run(`with default eventually retry strategy`, func(t *testing.T) {
stub := &doubles.TB{}
s := testcase.NewSpec(stub)
s.HasSideEffect()
var eventuallyRan bool
s.Test(``, func(t *testcase.T) {
t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
eventuallyRan = true
it.Must.True(t.Random.Bool())
}) // eventually pass
Expand All @@ -392,10 +394,10 @@ func TestT_Eventually(t *testing.T) {
for condition() {
}
})
s := testcase.NewSpec(stub, testcase.RetryStrategyForEventually(strategy))
s := testcase.NewSpec(stub, testcase.WithRetryStrategy(strategy))
s.HasSideEffect()
s.Test(``, func(t *testcase.T) {
t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
it.Must.True(t.Random.Bool())
}) // eventually pass
})
Expand All @@ -404,6 +406,118 @@ func TestT_Eventually(t *testing.T) {
assert.Must(t).True(!stub.IsFailed, `expected to pass`)
assert.Must(t).True(strategyUsed, `retry strategy of the eventually call was used`)
})

t.Run("Eventually uses a testcase.T that allows its functionalities all from the the eventually block", func(t *testing.T) {
// After extensive testing, we discovered that constantly switching between using `assert.It` and `testcase.T` is risky
// and prone to errors without adding any benefit.
// Therefore, our next step is to streamline the API to simplify testing.

stub := &doubles.TB{}
s := testcase.NewSpec(stub)
s.HasSideEffect()

expTag := rnd.StringNC(5, random.CharsetAlpha())
s.Tag(expTag)

expVal := rnd.Int()
v := testcase.LetValue(s, expVal)

var ran bool
s.Test(``, func(tcT *testcase.T) {
tcT.Eventually(func(it *testcase.T) {
ran = true
assert.Equal(t, expVal, v.Get(it))
assert.Equal(t, tcT.Random, it.Random)
assert.True(t, tcT.HasTag(expTag))
assert.True(t, tcT.TB != it.TB)
})
})

stub.Finish()
s.Finish()

assert.Must(t).True(!stub.IsFailed, `expected to pass`)
assert.True(t, ran)
})

t.Run("smoke", func(t *testing.T) {
stub := &doubles.TB{}
s := testcase.NewSpec(stub)
s.HasSideEffect()

var ran bool
s.Test(``, func(tcT *testcase.T) {
var failed bool
tcT.Eventually(func(t *testcase.T) {
if !failed {
failed = true
t.FailNow()
}
// OK
ran = true
})
assert.False(t, tcT.Failed())
})

stub.Finish()
s.Finish()

assert.Must(t).True(!stub.IsFailed, `expected to pass`)
assert.True(t, ran)
})

t.Run("when failure occurs during the variable initialisation", func(t *testing.T) {
t.Run("permanently", func(t *testing.T) {
stub := &doubles.TB{}
s := testcase.NewSpec(stub, testcase.WithRetryStrategy(assert.RetryCount(3)))
s.HasSideEffect()

v := testcase.Let[int](s, func(t *testcase.T) int {
t.FailNow() // boom
return 42
})

s.Test(``, func(tcT *testcase.T) {
tcT.Eventually(func(it *testcase.T) { v.Get(it) })
})

stub.Finish()
s.Finish()

assert.Must(t).True(stub.IsFailed, `expected to fail`)
})
t.Run("temporarily", func(t *testing.T) {
stub := &doubles.TB{}
s := testcase.NewSpec(stub)
s.HasSideEffect()

failed := testcase.LetValue[bool](s, false)
counter := testcase.LetValue[int](s, 0)
expVal := rnd.Int()

v := testcase.Let[int](s, func(t *testcase.T) int {
counter.Set(t, counter.Get(t)+1)
if !failed.Get(t) {
failed.Set(t, true)
t.FailNow() // boom
}
return expVal
})

s.Test(``, func(tcT *testcase.T) {
tcT.Eventually(func(it *testcase.T) {
assert.Equal(t, v.Get(it), expVal)
})
assert.Equal(t, v.Get(tcT), expVal)
assert.Equal(t, counter.Get(tcT), 2, "it was expected that the variable init block only run twice, one for failure and one for success")
})

stub.Finish()
s.Finish()

assert.Must(t).False(stub.IsFailed, `expected to pass`)
})
})
}

func TestNewT(t *testing.T) {
Expand Down
33 changes: 7 additions & 26 deletions backward.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,6 @@ package testcase

import "go.llib.dev/testcase/assert"

type (
// Eventually
//
// DEPRECATED: use assert.Retry instead
Eventually = assert.Retry
// RetryStrategy
//
// DEPRECATED: use assert.RetryStrategy instead
RetryStrategy = assert.RetryStrategy
// RetryStrategyFunc
//
// DEPRECATED: use assert.RetryStrategyFunc instead
RetryStrategyFunc = assert.RetryStrategyFunc
// Waiter
//
// DEPRECATED: use assert.Waiter instead
Waiter = assert.Waiter
)

// RetryCount is moved from this package.
//
// DEPRECATED: use assert.RetryCount instead
func RetryCount(times int) assert.RetryStrategy {
return assert.RetryCount(times)
}

// Let is a method to provide backward compatibility with the existing testing suite.
// Due to how Go type parameters work, methods are not allowed to have type parameters,
// thus Let has moved to be a pkg-level function in the package.
Expand All @@ -50,3 +24,10 @@ func (spec *Spec) LetValue(varName string, value any) Var[any] {
//
// DEPRECATED: use VarInit type instead.
type VarInitFunc[V any] func(*T) V

// RetryStrategyForEventually
//
// DEPRECATED: use testcase.WithRetryStrategy instead
func RetryStrategyForEventually(strategy assert.RetryStrategy) SpecOption {
return WithRetryStrategy(strategy)
}
4 changes: 0 additions & 4 deletions backward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import (
"go.llib.dev/testcase/random"
)

func TestRetryCount(t *testing.T) {
_ = RetryCount(42)
}

func TestSpec_Let_andLetValue_backwardCompatibility(t *testing.T) {
s := NewSpec(t)

Expand Down
2 changes: 1 addition & 1 deletion clock/Clock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestNow(t *testing.T) {
s.Then("time is still moving forward", func(t *testcase.T) {
now := act(t)

t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
next := act(t)
it.Must.False(now.Equal(next))
it.Must.True(next.After(now))
Expand Down
2 changes: 1 addition & 1 deletion examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func ExampleT_Eventually() {
s := testcase.NewSpec(tb)
s.Test(``, func(t *testcase.T) {
// Eventually this will pass eventually
t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
it.Must.True(t.Random.Bool())
})
})
Expand Down
3 changes: 1 addition & 2 deletions let/person_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"testing"

"go.llib.dev/testcase"
"go.llib.dev/testcase/assert"
"go.llib.dev/testcase/let"
"go.llib.dev/testcase/random/sextype"
)
Expand All @@ -22,7 +21,7 @@ func TestPerson_smoke(t *testing.T) {
t.Must.NotEmpty(ln.Get(t))
t.Must.NotEmpty(mfn.Get(t))
t.Must.NotEmpty(em.Get(t))
t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
it.Must.Equal(t.Random.Contact(sextype.Male).FirstName, mfn.Get(t))
})
})
Expand Down
2 changes: 1 addition & 1 deletion let/std_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestSTD_smoke(t *testing.T) {
t.Must.True(TimeB.Get(t).After(time.Now().AddDate(-1, 0, -1)))
t.Must.NotEmpty(UUID.Get(t))
t.Must.NotEmpty(Element.Get(t))
t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
it.Must.True(Bool.Get(testcase.ToT(&t.TB)))
})
})
Expand Down
2 changes: 1 addition & 1 deletion opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func makeEventually(i any) (assert.Retry, bool) {
}
}

func RetryStrategyForEventually(strategy assert.RetryStrategy) SpecOption {
func WithRetryStrategy(strategy assert.RetryStrategy) SpecOption {
return specOptionFunc(func(s *Spec) {
s.eventually = &assert.Retry{Strategy: strategy}
})
Expand Down
4 changes: 2 additions & 2 deletions random/Factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ func TestFactory(t *testing.T) {
s.Then("default random will be used to make a random", func(t *testcase.T) {
var got = make(map[int]struct{})

t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
got[act(t).(int)] = struct{}{}

it.Must.True(len(got) > 1)
Expand All @@ -783,7 +783,7 @@ func TestFactory(t *testing.T) {
s.Then("random values are returned", func(t *testcase.T) {
var got = make(map[string]struct{})

t.Eventually(func(it assert.It) {
t.Eventually(func(it *testcase.T) {
got[act(t).(string)] = struct{}{}

it.Must.True(len(got) > 1)
Expand Down
Loading

0 comments on commit 546059e

Please sign in to comment.