diff --git a/assert/assertion_format.go b/assert/assertion_format.go index ad395233c..10026b399 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -194,6 +194,42 @@ func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor tim return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) } +// Consistentlyf asserts that given condition will be met for the entire +// waitFor time, periodically checking target function each tick. +// +// assert.Consistentlyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Consistentlyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Consistently(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) +} + +// ConsistentlyWithTf asserts that given condition will be met for the entire +// waitFor time, periodically checking target function each tick. In contrast +// to Consistently, it supplies a CollectT to the condition function, so that +// the condition function can use the CollectT to call other assertions. The +// condition is considered "met" if no errors are raised across all ticks. The +// supplied CollectT collects all errors from one tick (if there are any). If +// the condition is not met once before waitFor, the collected error of the +// failing tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.ConsistentlyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func ConsistentlyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ConsistentlyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) +} + // Exactlyf asserts that two objects are equal in value and type. // // assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") diff --git a/assert/assertions.go b/assert/assertions.go index 7f16cb82f..06d3060af 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2038,6 +2038,93 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time } } +// Consistently asserts that given condition will be met for the entire +// duration of waitFor time, periodically checking target function each tick. +// +// assert.Consistently(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Consistently(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + ch := make(chan bool, 1) + + timer := time.NewTimer(waitFor) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for tick := ticker.C; ; { + select { + case <-timer.C: + return true + case <-tick: + tick = nil + go func() { ch <- condition() }() + case v := <-ch: + if !v { + return Fail(t, "Condition never satisfied", msgAndArgs...) + } + tick = ticker.C + } + } +} + +// ConsistentlyWithT asserts that given condition will be met for the entire +// waitFor time, periodically checking target function each tick. In contrast +// to Consistently, it supplies a CollectT to the condition function, so that +// the condition function can use the CollectT to call other assertions. The +// condition is considered "met" if no errors are raised across all ticks. The +// supplied CollectT collects all errors from one tick (if there are any). If +// the condition is not met once before waitFor, the collected error of the +// failing tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.ConsistentlyWithT(t, func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func ConsistentlyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + ch := make(chan []error, 1) + + timer := time.NewTimer(waitFor) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for tick := ticker.C; ; { + select { + case <-timer.C: + return true + case <-tick: + tick = nil + go func() { + collect := new(CollectT) + defer func() { + ch <- collect.errors + }() + condition(collect) + }() + case errs := <-ch: + if len(errs) > 0 { + return Fail(t, "Condition never satisfied", msgAndArgs...) + } + + tick = ticker.C + } + } +} + // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 8b8772713..0844b7137 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -2898,6 +2898,29 @@ func TestEventuallyTrue(t *testing.T) { True(t, Eventually(t, condition, 100*time.Millisecond, 20*time.Millisecond)) } +func TestConsistentlyTrue(t *testing.T) { + condition := func() bool { + return true + } + + True(t, Consistently(t, condition, 100*time.Millisecond, 20*time.Millisecond)) +} + +func TestConsistentlyFalse(t *testing.T) { + mockT := new(testing.T) + + state := 0 + condition := func() bool { + defer func() { + state += 1 + }() + + return state != 2 + } + + False(t, Consistently(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) +} + // errorsCapturingT is a mock implementation of TestingT that captures errors reported with Errorf. type errorsCapturingT struct { errors []error @@ -2970,6 +2993,32 @@ func TestEventuallyWithT_ReturnsTheLatestFinishedConditionErrors(t *testing.T) { Len(t, mockT.errors, 2) } +func TestConsistentlyWithTTrue(t *testing.T) { + mockT := new(errorsCapturingT) + + condition := func(collect *CollectT) { + True(collect, true) + } + + True(t, ConsistentlyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) + Len(t, mockT.errors, 0) +} + +func TestConsistentlyWithTFalse(t *testing.T) { + mockT := new(errorsCapturingT) + + state := 0 + condition := func(collect *CollectT) { + defer func() { + state += 1 + }() + False(collect, state == 2) + } + + False(t, ConsistentlyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) + Len(t, mockT.errors, 1) +} + func TestNeverFalse(t *testing.T) { condition := func() bool { return false diff --git a/require/require.go b/require/require.go index 59b87c8e3..a94918ae7 100644 --- a/require/require.go +++ b/require/require.go @@ -471,6 +471,90 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick t.FailNow() } +// Consistently asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Consistently(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Consistently(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Consistently(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// ConsistentlyWithT asserts that given condition will be met for the entire +// waitFor time, periodically checking target function each tick. In contrast +// to Consistently, it supplies a CollectT to the condition function, so that +// the condition function can use the CollectT to call other assertions. The +// condition is considered "met" if no errors are raised across all ticks. The +// supplied CollectT collects all errors from one tick (if there are any). If +// the condition is not met once before waitFor, the collected error of the +// failing tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// require.ConsistentlyWithT(t, func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func ConsistentlyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ConsistentlyWithT(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// ConsistentlyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Consistently, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// require.ConsistentlyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func ConsistentlyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ConsistentlyWithTf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Consistentlyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Consistentlyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Consistentlyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Consistentlyf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + // Exactly asserts that two objects are equal in value and type. // // assert.Exactly(t, int32(123), int64(123))