diff --git a/assert/assertions.go b/assert/assertions.go index 44b854da6..136a28f16 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1932,13 +1932,15 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t ch := make(chan bool, 1) + ticker := time.NewTicker(tick) + defer ticker.Stop() + timer := time.NewTimer(waitFor) defer timer.Stop() - ticker := time.NewTicker(tick) - defer ticker.Stop() + go func() { ch <- condition() }() - for tick := ticker.C; ; { + for tick := (<-chan time.Time)(nil); ; { select { case <-timer.C: return Fail(t, "Condition never satisfied", msgAndArgs...) @@ -2019,13 +2021,22 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time var lastFinishedTickErrs []error ch := make(chan *CollectT, 1) + ticker := time.NewTicker(tick) + defer ticker.Stop() + timer := time.NewTimer(waitFor) defer timer.Stop() - ticker := time.NewTicker(tick) - defer ticker.Stop() + testCondition := func() { + collect := new(CollectT) + defer func() { + ch <- collect + }() + condition(collect) + } + go testCondition() - for tick := ticker.C; ; { + for tick := (<-chan time.Time)(nil); ; { select { case <-timer.C: for _, err := range lastFinishedTickErrs { @@ -2034,13 +2045,7 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time return Fail(t, "Condition never satisfied", msgAndArgs...) case <-tick: tick = nil - go func() { - collect := new(CollectT) - defer func() { - ch <- collect - }() - condition(collect) - }() + go testCondition() case collect := <-ch: if !collect.failed() { return true diff --git a/assert/assertions_test.go b/assert/assertions_test.go index e158688f2..1f5259047 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3012,6 +3012,9 @@ func TestNeverTrue(t *testing.T) { func TestEventuallyTimeout(t *testing.T) { mockT := new(testing.T) + timeout := time.NewTimer(time.Second) + defer timeout.Stop() + NotPanics(t, func() { done, done2 := make(chan struct{}), make(chan struct{}) @@ -3026,7 +3029,65 @@ func TestEventuallyTimeout(t *testing.T) { False(t, Eventually(mockT, condition, time.Millisecond, time.Microsecond)) close(done) - <-done2 + select { + case <-done2: + case <-timeout.C: + panic("test timed out") + } + }) +} + +// If waitFor is smaller than tick or only slightly larger than tick it was +// possible for Eventually to return a failure having never run condition. See +// issue 1652. Condition should always be run once. +func TestEventuallyTickLessThanWaitFor(t *testing.T) { + mockT := new(testing.T) + + timeout := time.NewTimer(time.Second) + defer timeout.Stop() + + NotPanics(t, func() { + done, done2 := make(chan struct{}), make(chan struct{}) + + condition := func() bool { + <-done + close(done2) + return true + } + + False(t, Eventually(mockT, condition, time.Microsecond, time.Millisecond)) + + close(done) + select { + case <-done2: + case <-timeout.C: + panic("test timed out") + } + }) +} + +func TestEventuallyWithTTickLessThanWaitFor(t *testing.T) { + mockT := new(testing.T) + + timeout := time.NewTimer(time.Second) + defer timeout.Stop() + + NotPanics(t, func() { + done, done2 := make(chan struct{}), make(chan struct{}) + + condition := func(t *CollectT) { + <-done + close(done2) + } + + False(t, EventuallyWithT(mockT, condition, time.Microsecond, time.Millisecond)) + + close(done) + select { + case <-done2: + case <-timeout.C: + panic("test timed out") + } }) }