Skip to content

Commit

Permalink
instroduce assert.NoneOf
Browse files Browse the repository at this point in the history
  • Loading branch information
adamluzsi committed Sep 22, 2024
1 parent 2ef18e1 commit 21a0594
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 21 deletions.
66 changes: 65 additions & 1 deletion assert/AnyOf.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ func (ao *A) Case(blk func(t It)) {
ao.mutex.Lock()
defer ao.mutex.Unlock()
ao.passed = true
return
}

// Test is an alias for A.Case
Expand Down Expand Up @@ -90,3 +89,68 @@ func (ao *A) OK() bool {
defer ao.mutex.Unlock()
return ao.passed
}

// OneOf function checks a list of values and matches an expectation against each element of the list.
// If any slice element meets the assertion, it is considered passed.
func OneOf[T any](tb testing.TB, vs []T, blk func(t It, got T), msg ...Message) {
tb.Helper()
Must(tb).AnyOf(func(a *A) {
a.name = "OneOf"
a.cause = "None of the element matched the expectations"
for _, v := range vs {
a.Case(func(it It) { blk(it, v) })
if a.OK() {
break
}
}
}, msg...)
}

// NoneOf function checks a list of values and matches an expectation against each element of the list.
// If any slice element meets the assertion, it is considered failed.
func NoneOf[T any](tb testing.TB, vs []T, blk func(t It, got T), msg ...Message) {
tb.Helper()

var check = func(v T) bool {
tb.Helper()
dtb := &doubles.RecorderTB{TB: tb}
sandbox.Run(func() {
tb.Helper()
blk(MakeIt(dtb), v)
})

assertFailed := dtb.IsFailed
dtb.IsFailed = false // reset IsFailed for Cleanup

sandbox.Run(func() {
tb.Helper()
dtb.CleanupNow()
})
if hasCleanupFailed := dtb.IsFailed; hasCleanupFailed {
dtb.Forward()
}

return assertFailed
}

for i, v := range vs {
if !check(v) {
tb.Log(fmterror.Message{
Method: "NoneOf",
Cause: "One of the element matched the expectations",
Message: toMsg(msg),
Values: []fmterror.Value{
{
Label: "index",
Value: i,
},
{
Label: "value",
Value: v,
},
},
})
tb.FailNow()
}
}
}
147 changes: 144 additions & 3 deletions assert/AnyOf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func TestOneOf(t *testing.T) {
t.Must.False(stub.Get(t).IsFailed)
})

s.Then("execution context is not killed", func(t *testcase.T) {
s.Then("testing runtime is not killed", func(t *testcase.T) {
t.Must.True(act(t).OK)
})

Expand All @@ -187,7 +187,7 @@ func TestOneOf(t *testing.T) {
t.Must.True(stub.Get(t).IsFailed)
})

s.Then("execution context is interrupted with FailNow", func(t *testcase.T) {
s.Then("testing runtime is interrupted with FailNow", func(t *testcase.T) {
out := act(t)
t.Must.False(out.OK)
t.Must.True(out.Goexit)
Expand Down Expand Up @@ -221,7 +221,7 @@ func TestOneOf(t *testing.T) {
t.Must.False(stub.Get(t).IsFailed)
})

s.Then("execution context is not killed", func(t *testcase.T) {
s.Then("testing runtime is not killed", func(t *testcase.T) {
t.Must.True(act(t).OK)
})

Expand All @@ -233,6 +233,147 @@ func TestOneOf(t *testing.T) {
})
}

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

stub := testcase.Let(s, func(t *testcase.T) *doubles.TB {
return &doubles.TB{}
})
vs := testcase.Let(s, func(t *testcase.T) []string {
return random.Slice(t.Random.IntBetween(3, 7), func() string {
return t.Random.String()
})
})

const msg = "optional assertion explanation"
blk := testcase.LetValue[func(assert.It, string)](s, nil)
act := func(t *testcase.T) sandbox.RunOutcome {
return sandbox.Run(func() {
assert.NoneOf(stub.Get(t), vs.Get(t), blk.Get(t), msg)
})
}

s.When("passed block has no issue", func(s *testcase.Spec) {
blk.Let(s, func(t *testcase.T) func(assert.It, string) {
return func(it assert.It, s string) {}
})

s.Then("testing.TB is failed", func(t *testcase.T) {
act(t)

t.Must.True(stub.Get(t).IsFailed)
})

s.Then("testing runtime is not killed", func(t *testcase.T) {
t.Must.False(act(t).OK)
})

s.Then("assert message explanation is not logged", func(t *testcase.T) {
act(t)

t.Must.Contain(stub.Get(t).Logs.String(), msg)
})
})

s.When("passed keeps failing with testing.TB#FailNow", func(s *testcase.Spec) {
blk.Let(s, func(t *testcase.T) func(assert.It, string) {
return func(it assert.It, s string) { it.FailNow() }
})

s.Then("testing.TB is not failed as all the assertion failed as expected", func(t *testcase.T) {
act(t)

t.Must.False(stub.Get(t).IsFailed)
})

s.Then("testing runtime is not interrupted with FailNow", func(t *testcase.T) {
out := act(t)
t.Must.True(out.OK)
t.Must.False(out.Goexit)
})

s.Then("assert message explanation is not logged", func(t *testcase.T) {
act(t)

t.Must.NotContain(stub.Get(t).Logs.String(), msg)
})
})

s.When("the assertion would fail, but the cleanup fails as well", func(s *testcase.Spec) {
blk.Let(s, func(t *testcase.T) func(assert.It, string) {
return func(i assert.It, s string) {
i.Cleanup(func() {
i.Log("cleanup-failed")
i.FailNow()
})

i.FailNow()
}
})

s.Then("the assertion fails because we don't expect failure in the cleanup", func(t *testcase.T) {
out := act(t)
t.Must.False(out.OK)
t.Must.True(out.Goexit)
assert.Contain(t, stub.Get(t).Logs.String(), "cleanup-failed")
})
})

s.When("cleanup is part of the assertion block", func(s *testcase.Spec) {
cleanupOK := testcase.LetValue(s, false)

blk.Let(s, func(t *testcase.T) func(assert.It, string) {
return func(i assert.It, s string) {
i.Cleanup(func() { cleanupOK.Set(t, true) })

if t.Random.Bool() {
i.FailNow()
}
}
})

s.Then("cleanup is done", func(t *testcase.T) {
act(t)

assert.True(t, cleanupOK.Get(t))
})
})

s.When("assertions pass for at least one of the slice value", func(s *testcase.Spec) {
blk.Let(s, func(t *testcase.T) func(assert.It, string) {
expected := t.Random.SliceElement(vs.Get(t)).(string)
return func(it assert.It, got string) {
it.Must.Equal(expected, got)
}
})

s.Then("testing.TB is marked as failed", func(t *testcase.T) {
act(t)

t.Must.True(stub.Get(t).IsFailed)
})

s.Then("testing runtime is interrupted", func(t *testcase.T) {
out := act(t)
t.Must.False(out.OK)
t.Must.True(out.Goexit)
})

s.Then("assert message explanation is logged", func(t *testcase.T) {
act(t)

t.Must.Contain(stub.Get(t).Logs.String(), msg)
})

s.Then("assertion failure message includes the assertion helper name", func(t *testcase.T) {
act(t)

t.Must.Contain(stub.Get(t).Logs.String(), "NoneOf")
t.Must.Contain(stub.Get(t).Logs.String(), "One of the element matched the expectations")
})
})
}

func TestA_Test_smoke(t *testing.T) {
assert.AnyOf(t, func(a *assert.A) {
a.Test(func(t assert.It) {
Expand Down
16 changes: 15 additions & 1 deletion assert/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ func ExampleAsserter_NotWithin() {
a := assert.Must(tb)

a.NotWithin(time.Second, func(ctx context.Context) {
return // FAIL
// FAIL
})

a.NotWithin(time.Nanosecond, func(ctx context.Context) {
Expand All @@ -723,6 +723,20 @@ func ExampleOneOf() {
}, "optional assertion explanation")
}

func ExampleNoneOf() {
var tb testing.TB
values := []string{"foo", "bar", "baz"}

assert.NoneOf(tb, values, func(t assert.It, got string) {
assert.NotEmpty(t, got)
assert.True(t, strings.HasPrefix(got, "b"))
assert.True(t, strings.HasSuffix(got, "z"))
// at this point, our assertion passed for "baz",
// and NoneOf will report the failure
// that this value passed while it was not expected to.
}, "optional assertion explanation")
}

func ExampleAsserter_OneOf() {
var tb testing.TB
values := []string{"foo", "bar", "baz"}
Expand Down
16 changes: 0 additions & 16 deletions assert/pkgfunc.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,6 @@ func Eventually[T time.Duration | int](tb testing.TB, durationOrCount T, blk fun
Must(tb).Eventually(durationOrCount, blk)
}

// OneOf function checks a list of values and matches an expectation against each element of the list.
// If any of the elements pass the assertion, then the assertion helper function does not fail the test.
func OneOf[V any](tb testing.TB, vs []V, blk func(t It, got V), msg ...Message) {
tb.Helper()
Must(tb).AnyOf(func(a *A) {
a.name = "OneOf"
a.cause = "None of the element matched the expectations"
for _, v := range vs {
a.Case(func(it It) { blk(it, v) })
if a.OK() {
break
}
}
}, msg...)
}

// AnyOf is an assertion helper that deems the test successful
// if any of the declared assertion cases pass.
// This is commonly used when multiple valid formats are acceptable
Expand Down
1 change: 1 addition & 0 deletions internal/doubles/RecorderTB.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (rtb *RecorderTB) Forward() {
}

func (rtb *RecorderTB) CleanupNow() {
rtb.TB.Helper()
defer rtb.withPassthrough()()
td := &teardown.Teardown{}
for _, event := range rtb.records {
Expand Down

0 comments on commit 21a0594

Please sign in to comment.