From 0c696c0b1f7955b308c9a22c6bccbd396635bcb4 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Mon, 26 Jan 2026 15:56:01 -0600 Subject: [PATCH] fix regression introduced in 1.11.x Signed-off-by: Cassandra Coyle --- assert/assertions.go | 21 ++---------- assert/assertions_test.go | 70 ++++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 6950636d3..7ca683342 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2018,12 +2018,7 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t ticker := time.NewTicker(tick) defer ticker.Stop() - var tickC <-chan time.Time - - // Check the condition once first on the initial call. - go checkCond() - - for { + for tickC := ticker.C; ; { select { case <-timer.C: return Fail(t, "Condition never satisfied", msgAndArgs...) @@ -2121,12 +2116,7 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time ticker := time.NewTicker(tick) defer ticker.Stop() - var tickC <-chan time.Time - - // Check the condition once first on the initial call. - go checkCond() - - for { + for tickC := ticker.C; ; { select { case <-timer.C: for _, err := range lastFinishedTickErrs { @@ -2165,12 +2155,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D ticker := time.NewTicker(tick) defer ticker.Stop() - var tickC <-chan time.Time - - // Check the condition once first on the initial call. - go checkCond() - - for { + for tickC := ticker.C; ; { select { case <-timer.C: return true diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 4975f5e41..a9a4913bd 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -14,6 +14,7 @@ import ( "regexp" "runtime" "strings" + "sync/atomic" "testing" "time" ) @@ -3491,6 +3492,54 @@ func TestEventuallyWithTTrue(t *testing.T) { Equal(t, 2, counter, "Condition is expected to be called 2 times") } +func TestEventuallyWithT_AvoidsPanic_NilDeref(t *testing.T) { + t.Parallel() + + var p atomic.Value + go func() { + time.Sleep(5 * time.Millisecond) + value := 1 + p.Store(&value) + }() + + // If the condition runs before the first tick, this panics on *p.Load() + True(t, EventuallyWithT(t, func(c *CollectT) { + Equal(c, 1, *p.Load().(*int)) + }, 200*time.Millisecond, 50*time.Millisecond)) +} + +func TestEventually_AvoidsPanic_NilDeref(t *testing.T) { + t.Parallel() + + var p atomic.Value + go func() { + time.Sleep(5 * time.Millisecond) + value := 1 + p.Store(&value) + }() + + // If the condition runs before the first tick, this panics on *p.Load() + True(t, Eventually(t, func() bool { + return *p.Load().(*int) == 1 + }, 200*time.Millisecond, 50*time.Millisecond)) +} + +func TestNever_AvoidsPanic_NilDeref(t *testing.T) { + t.Parallel() + + var p atomic.Value + go func() { + time.Sleep(5 * time.Millisecond) + value := 0 + p.Store(&value) + }() + + // If the condition runs before the first tick, this panics on *p.Load() + True(t, Never(t, func() bool { + return *p.Load().(*int) == 1 // set to 0 above, so this should never == 1 + }, 200*time.Millisecond, 50*time.Millisecond)) +} + func TestEventuallyWithT_ConcurrencySafe(t *testing.T) { t.Parallel() @@ -3568,28 +3617,26 @@ func TestEventuallyTimeout(t *testing.T) { }) } -func TestEventuallySucceedQuickly(t *testing.T) { +func TestEventually_NoImmediateCheckBeforeFirstTick(t *testing.T) { t.Parallel() mockT := new(testing.T) condition := func() bool { return true } - // By making the tick longer than the total duration, we expect that this test would fail if - // we didn't check the condition before the first tick elapses. - True(t, Eventually(mockT, condition, 100*time.Millisecond, time.Second)) + // By making the tick longer than the total duration, the condition should not be checked. + False(t, Eventually(mockT, condition, 100*time.Millisecond, time.Second)) } -func TestEventuallyWithTSucceedQuickly(t *testing.T) { +func TestEventuallyWithT_NoImmediateCheckBeforeFirstTick(t *testing.T) { t.Parallel() mockT := new(testing.T) condition := func(t *CollectT) {} - // By making the tick longer than the total duration, we expect that this test would fail if - // we didn't check the condition before the first tick elapses. - True(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, time.Second)) + // By making the tick longer than the total duration, the condition should not be checked. + False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, time.Second)) } func TestNeverFalse(t *testing.T) { @@ -3623,15 +3670,14 @@ func TestNeverTrue(t *testing.T) { False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) } -func TestNeverFailQuickly(t *testing.T) { +func TestNever_NoImmediateCheckBeforeFirstTick(t *testing.T) { t.Parallel() mockT := new(testing.T) - // By making the tick longer than the total duration, we expect that this test would fail if - // we didn't check the condition before the first tick elapses. + // By making the tick longer than the total duration, the condition should not be checked. condition := func() bool { return true } - False(t, Never(mockT, condition, 100*time.Millisecond, time.Second)) + True(t, Never(mockT, condition, 100*time.Millisecond, time.Second)) } func Test_validateEqualArgs(t *testing.T) {