-
Notifications
You must be signed in to change notification settings - Fork 894
chore: add testutil.Eventually and friends #3389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
1633613
d5f209b
32b578e
1dca2c3
5eb64ec
41f678b
7593802
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package testutil | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// Eventually is like require.Eventually except it allows passing | ||
// a context into the condition. It is safe to use with `require.*`. | ||
// | ||
// If ctx times out, the test will fail, but not immediately. | ||
// It is the caller's responsibility to exit early if required. | ||
// | ||
// It is the caller's responsibility to ensure that ctx has a | ||
// deadline or timeout set. Eventually will panic if this is not | ||
// the case in order to avoid potentially waiting forever. | ||
// | ||
// condition is not run in a goroutine; use the provided | ||
// context argument for cancellation if required. | ||
func Eventually(ctx context.Context, t testing.TB, condition func(context.Context) bool, tick time.Duration) bool { | ||
t.Helper() | ||
|
||
if _, ok := ctx.Deadline(); !ok { | ||
panic("developer error: must set deadline or timeout on ctx") | ||
} | ||
|
||
ticker := time.NewTicker(tick) | ||
defer ticker.Stop() | ||
for tick := ticker.C; ; { | ||
select { | ||
case <-ctx.Done(): | ||
assert.NoError(t, ctx.Err(), "Eventually timed out") | ||
return false | ||
case <-tick: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My head must've been background processing this PR review, just realized there's a race here between |
||
if condition(ctx) { | ||
return true | ||
} | ||
tick = ticker.C | ||
johnstcn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
|
||
// EventuallyShort is a convenience function that runs Eventually with | ||
// IntervalFast and times out after WaitShort. | ||
func EventuallyShort(t testing.TB, condition func(context.Context) bool) bool { | ||
ctx, cancel := context.WithTimeout(context.Background(), WaitShort) | ||
defer cancel() | ||
return Eventually(ctx, t, condition, IntervalFast) | ||
} | ||
|
||
// EventuallyMedium is a convenience function that runs Eventually with | ||
// IntervalMedium and times out after WaitMedium. | ||
func EventuallyMedium(t testing.TB, condition func(context.Context) bool) bool { | ||
ctx, cancel := context.WithTimeout(context.Background(), WaitMedium) | ||
defer cancel() | ||
return Eventually(ctx, t, condition, IntervalMedium) | ||
} | ||
|
||
// EventuallyLong is a convenience function that runs Eventually with | ||
// IntervalSlow and times out after WaitLong. | ||
func EventuallyLong(t testing.TB, condition func(context.Context) bool) bool { | ||
ctx, cancel := context.WithTimeout(context.Background(), WaitLong) | ||
defer cancel() | ||
return Eventually(ctx, t, condition, IntervalSlow) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package testutil_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"go.uber.org/goleak" | ||
|
||
"github.com/coder/coder/testutil" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
goleak.VerifyTestMain(m) | ||
} | ||
|
||
func TestEventually(t *testing.T) { | ||
t.Parallel() | ||
t.Run("OK", func(t *testing.T) { | ||
t.Parallel() | ||
state := 0 | ||
condition := func(_ context.Context) bool { | ||
defer func() { | ||
state++ | ||
}() | ||
return state > 2 | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) | ||
defer cancel() | ||
testutil.Eventually(ctx, t, condition, testutil.IntervalFast) | ||
}) | ||
|
||
t.Run("Timeout", func(t *testing.T) { | ||
t.Parallel() | ||
condition := func(_ context.Context) bool { | ||
return false | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) | ||
defer cancel() | ||
mockT := new(testing.T) | ||
testutil.Eventually(ctx, mockT, condition, testutil.IntervalFast) | ||
assert.True(t, mockT.Failed()) | ||
}) | ||
|
||
t.Run("Panic", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
panicky := func() { | ||
mockT := new(testing.T) | ||
condition := func(_ context.Context) bool { return true } | ||
testutil.Eventually(context.Background(), mockT, condition, testutil.IntervalFast) | ||
} | ||
assert.Panics(t, panicky) | ||
}) | ||
|
||
t.Run("Short", func(t *testing.T) { | ||
t.Parallel() | ||
testutil.EventuallyShort(t, func(_ context.Context) bool { return true }) | ||
}) | ||
|
||
t.Run("Medium", func(t *testing.T) { | ||
t.Parallel() | ||
testutil.EventuallyMedium(t, func(_ context.Context) bool { return true }) | ||
}) | ||
|
||
t.Run("Long", func(t *testing.T) { | ||
t.Parallel() | ||
testutil.EventuallyLong(t, func(_ context.Context) bool { return true }) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great docs ❤️!