diff --git a/assert/assertion_format.go b/assert/assertion_format.go index ad395233c..e98c4f7f5 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -16,6 +16,42 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo return Condition(t, comp, append([]interface{}{msg}, args...)...) } +// Consistentlyf asserts that given condition will be met for the entire +// duration of 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...)...) +} + // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index d90c65da9..6d80e5f5a 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -24,6 +24,78 @@ func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{} return Conditionf(a.t, comp, msg, args...) } +// Consistently asserts that given condition will be met for the entire +// duration of waitFor time, periodically checking target function each tick. +// +// a.Consistently(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Consistently(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Consistently(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// 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 +// }() +// a.ConsistentlyWithT(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 (a *Assertions) ConsistentlyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ConsistentlyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// 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 +// }() +// a.ConsistentlyWithTf(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 (a *Assertions) ConsistentlyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ConsistentlyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + +// Consistentlyf asserts that given condition will be met for the entire +// duration of waitFor time, periodically checking target function each tick. +// +// a.Consistentlyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Consistentlyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Consistentlyf(a.t, condition, waitFor, tick, msg, args...) +} + // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // 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..be51e8fbf 100644 --- a/require/require.go +++ b/require/require.go @@ -31,6 +31,90 @@ func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interfac t.FailNow() } +// 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{}) { + 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 +// }() +// 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 *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 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 *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 for the entire +// duration of 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() +} + // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // diff --git a/require/require_forward.go b/require/require_forward.go index 389de9abe..6fe5bf683 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -25,6 +25,78 @@ func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...inte Conditionf(a.t, comp, msg, args...) } +// Consistently asserts that given condition will be met for the entire +// duration of waitFor time, periodically checking target function each tick. +// +// a.Consistently(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Consistently(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Consistently(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// 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 +// }() +// a.ConsistentlyWithT(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 (a *Assertions) ConsistentlyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ConsistentlyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// 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 +// }() +// a.ConsistentlyWithTf(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 (a *Assertions) ConsistentlyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ConsistentlyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + +// Consistentlyf asserts that given condition will be met for the entire +// duration of waitFor time, periodically checking target function each tick. +// +// a.Consistentlyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Consistentlyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Consistentlyf(a.t, condition, waitFor, tick, msg, args...) +} + // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. //