From 68f35d264053856231a655a904361e83ec95a845 Mon Sep 17 00:00:00 2001 From: cszczepaniak Date: Sat, 15 Jul 2023 13:34:22 -0500 Subject: [PATCH 1/7] return early in Eventually and EventuallyWithT --- assert/assertions.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assert/assertions.go b/assert/assertions.go index 104a0c936..cce5fe5c5 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1930,6 +1930,10 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t h.Helper() } + if condition() { + return true + } + ch := make(chan bool, 1) timer := time.NewTimer(waitFor) From cd4dc2864cb197f712c73a81e3517d3080aa07c1 Mon Sep 17 00:00:00 2001 From: cszczepaniak Date: Sat, 22 Jul 2023 11:39:00 -0500 Subject: [PATCH 2/7] respect the timeout on the initial condition check --- assert/assertions.go | 49 ++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index cce5fe5c5..e294b329c 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1930,11 +1930,8 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t h.Helper() } - if condition() { - return true - } - ch := make(chan bool, 1) + checkCond := func() { ch <- condition() } timer := time.NewTimer(waitFor) defer timer.Stop() @@ -1942,18 +1939,23 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t ticker := time.NewTicker(tick) defer ticker.Stop() - for tick := ticker.C; ; { + var tickC <-chan time.Time + + // Check the condition once first on the initial call. + go checkCond() + + for { select { case <-timer.C: return Fail(t, "Condition never satisfied", msgAndArgs...) - case <-tick: - tick = nil - go func() { ch <- condition() }() + case <-tickC: + tickC = nil + go checkCond() case v := <-ch: if v { return true } - tick = ticker.C + tickC = ticker.C } } } @@ -2023,35 +2025,42 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time var lastFinishedTickErrs []error ch := make(chan *CollectT, 1) + checkCond := func() { + collect := new(CollectT) + defer func() { + ch <- collect + }() + condition(collect) + } + timer := time.NewTimer(waitFor) defer timer.Stop() ticker := time.NewTicker(tick) defer ticker.Stop() - for tick := ticker.C; ; { + var tickC <-chan time.Time + + // Check the condition once first on the initial call. + go checkCond() + + for { select { case <-timer.C: for _, err := range lastFinishedTickErrs { t.Errorf("%v", err) } return Fail(t, "Condition never satisfied", msgAndArgs...) - case <-tick: - tick = nil - go func() { - collect := new(CollectT) - defer func() { - ch <- collect - }() - condition(collect) - }() + case <-tickC: + tickC = nil + go checkCond() case collect := <-ch: if !collect.failed() { return true } // Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached. lastFinishedTickErrs = collect.errors - tick = ticker.C + tickC = ticker.C } } } From f96316432bbdb775725e3a2fe9b8ff47917cf621 Mon Sep 17 00:00:00 2001 From: cszczepaniak Date: Sat, 22 Jul 2023 11:55:14 -0500 Subject: [PATCH 3/7] test that we succeed quickly --- assert/assertions_test.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 064b92f4a..3ada50f65 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3030,6 +3030,42 @@ func TestEventuallyTimeout(t *testing.T) { }) } +func TestEventuallySucceedQuickly(t *testing.T) { + mockT := new(testing.T) + + condition := func() bool { <-time.After(time.Millisecond); return true } + + done := make(chan struct{}) + go func() { + defer close(done) + True(t, Eventually(mockT, condition, 1000*time.Millisecond, 100*time.Millisecond)) + }() + + select { + case <-done: + case <-time.After(10 * time.Millisecond): + Fail(t, `condition not satisfied quickly enough`) + } +} + +func TestEventuallyWithTSucceedQuickly(t *testing.T) { + mockT := new(testing.T) + + condition := func(t *CollectT) { <-time.After(time.Millisecond) } + + done := make(chan struct{}) + go func() { + defer close(done) + True(t, EventuallyWithT(mockT, condition, 1000*time.Millisecond, 100*time.Millisecond)) + }() + + select { + case <-done: + case <-time.After(10 * time.Millisecond): + Fail(t, `condition not satisfied quickly enough`) + } +} + func Test_validateEqualArgs(t *testing.T) { if validateEqualArgs(func() {}, func() {}) == nil { t.Error("non-nil functions should error") From e4e93dd77cc7f860f3dbdc11ee2501b1cfad0c6f Mon Sep 17 00:00:00 2001 From: Connor Szczepaniak Date: Sat, 8 Jun 2024 19:00:08 -0500 Subject: [PATCH 4/7] update Never to also check the condition initially --- assert/assertions.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index e294b329c..538f2e26d 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2075,6 +2075,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D } ch := make(chan bool, 1) + checkCond := func() { ch <- condition() } timer := time.NewTimer(waitFor) defer timer.Stop() @@ -2082,18 +2083,23 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D ticker := time.NewTicker(tick) defer ticker.Stop() - for tick := ticker.C; ; { + var tickC <-chan time.Time + + // Check the condition once first on the initial call. + go checkCond() + + for { select { case <-timer.C: return true - case <-tick: - tick = nil - go func() { ch <- condition() }() + case <-tickC: + tickC = nil + go checkCond() case v := <-ch: if v { return Fail(t, "Condition satisfied", msgAndArgs...) } - tick = ticker.C + tickC = ticker.C } } } From ab114f88b1f16d804f2f00fb301bfde80da920d5 Mon Sep 17 00:00:00 2001 From: Connor Szczepaniak Date: Sat, 8 Jun 2024 19:01:54 -0500 Subject: [PATCH 5/7] test never when it fails quickly --- assert/assertions_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 3ada50f65..4c20a6612 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3007,6 +3007,24 @@ func TestNeverTrue(t *testing.T) { False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) } +func TestNeverFailQuickly(t *testing.T) { + mockT := new(testing.T) + + condition := func() bool { <-time.After(time.Millisecond); return true } + + done := make(chan struct{}) + go func() { + defer close(done) + False(t, Never(mockT, condition, 1000*time.Millisecond, 100*time.Millisecond)) + }() + + select { + case <-done: + case <-time.After(10 * time.Millisecond): + Fail(t, `condition not satisfied quickly enough`) + } +} + // Check that a long running condition doesn't block Eventually. // See issue 805 (and its long tail of following issues) func TestEventuallyTimeout(t *testing.T) { From bf2c747ccaa7018462210d77f5a8fd1a3a558217 Mon Sep 17 00:00:00 2001 From: Connor Szczepaniak Date: Sat, 8 Jun 2024 19:06:31 -0500 Subject: [PATCH 6/7] simplify tests --- assert/assertions_test.go | 49 ++++++++++----------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 4c20a6612..0e02274e0 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3010,19 +3010,10 @@ func TestNeverTrue(t *testing.T) { func TestNeverFailQuickly(t *testing.T) { mockT := new(testing.T) - condition := func() bool { <-time.After(time.Millisecond); return true } - - done := make(chan struct{}) - go func() { - defer close(done) - False(t, Never(mockT, condition, 1000*time.Millisecond, 100*time.Millisecond)) - }() - - select { - case <-done: - case <-time.After(10 * time.Millisecond): - Fail(t, `condition not satisfied quickly enough`) - } + // 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. + condition := func() bool { return true } + False(t, Never(mockT, condition, 100*time.Millisecond, time.Second)) } // Check that a long running condition doesn't block Eventually. @@ -3051,37 +3042,21 @@ func TestEventuallyTimeout(t *testing.T) { func TestEventuallySucceedQuickly(t *testing.T) { mockT := new(testing.T) - condition := func() bool { <-time.After(time.Millisecond); return true } - - done := make(chan struct{}) - go func() { - defer close(done) - True(t, Eventually(mockT, condition, 1000*time.Millisecond, 100*time.Millisecond)) - }() + condition := func() bool { return true } - select { - case <-done: - case <-time.After(10 * time.Millisecond): - Fail(t, `condition not satisfied quickly enough`) - } + // 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)) } func TestEventuallyWithTSucceedQuickly(t *testing.T) { mockT := new(testing.T) - condition := func(t *CollectT) { <-time.After(time.Millisecond) } + condition := func(t *CollectT) {} - done := make(chan struct{}) - go func() { - defer close(done) - True(t, EventuallyWithT(mockT, condition, 1000*time.Millisecond, 100*time.Millisecond)) - }() - - select { - case <-done: - case <-time.After(10 * time.Millisecond): - Fail(t, `condition not satisfied quickly enough`) - } + // 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)) } func Test_validateEqualArgs(t *testing.T) { From bae586f140e3578be864304c3a0cb037303cdfb4 Mon Sep 17 00:00:00 2001 From: Connor Szczepaniak Date: Sat, 8 Jun 2024 19:08:49 -0500 Subject: [PATCH 7/7] colocate never/eventually tests --- assert/assertions_test.go | 72 +++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 0e02274e0..dc7c5bde3 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -2980,42 +2980,6 @@ func TestEventuallyWithTFailNow(t *testing.T) { Len(t, mockT.errors, 1) } -func TestNeverFalse(t *testing.T) { - condition := func() bool { - return false - } - - True(t, Never(t, condition, 100*time.Millisecond, 20*time.Millisecond)) -} - -// TestNeverTrue checks Never with a condition that returns true on second call. -func TestNeverTrue(t *testing.T) { - mockT := new(testing.T) - - // A list of values returned by condition. - // Channel protects against concurrent access. - returns := make(chan bool, 2) - returns <- false - returns <- true - defer close(returns) - - // Will return true on second call. - condition := func() bool { - return <-returns - } - - False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) -} - -func TestNeverFailQuickly(t *testing.T) { - 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. - condition := func() bool { return true } - False(t, Never(mockT, condition, 100*time.Millisecond, time.Second)) -} - // Check that a long running condition doesn't block Eventually. // See issue 805 (and its long tail of following issues) func TestEventuallyTimeout(t *testing.T) { @@ -3059,6 +3023,42 @@ func TestEventuallyWithTSucceedQuickly(t *testing.T) { True(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, time.Second)) } +func TestNeverFalse(t *testing.T) { + condition := func() bool { + return false + } + + True(t, Never(t, condition, 100*time.Millisecond, 20*time.Millisecond)) +} + +// TestNeverTrue checks Never with a condition that returns true on second call. +func TestNeverTrue(t *testing.T) { + mockT := new(testing.T) + + // A list of values returned by condition. + // Channel protects against concurrent access. + returns := make(chan bool, 2) + returns <- false + returns <- true + defer close(returns) + + // Will return true on second call. + condition := func() bool { + return <-returns + } + + False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) +} + +func TestNeverFailQuickly(t *testing.T) { + 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. + condition := func() bool { return true } + False(t, Never(mockT, condition, 100*time.Millisecond, time.Second)) +} + func Test_validateEqualArgs(t *testing.T) { if validateEqualArgs(func() {}, func() {}) == nil { t.Error("non-nil functions should error")