Skip to content

Commit 01fe5e6

Browse files
johnstcnmafredri
andauthored
chore: add testutil.Eventually and friends (#3389)
This PR adds a `testutil` function aimed to replace `require.Eventually`. Before: ```go require.Eventually(t, func() bool { ... }, testutil.WaitShort, testutil.IntervalFast) ``` After: ```go require.True(t, testutil.EventuallyShort(t, func(ctx context.Context) bool { ... })) // or the full incantation if you need more control ctx, cancel := context.WithTimeout(ctx.Background(), testutil.WaitLong) require.True(t, testutil.Eventually(t, ctx, func(ctx context.Context) bool { ... }, testutil.IntervalSlow)) ``` Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
1 parent 46d64c6 commit 01fe5e6

File tree

5 files changed

+150
-12
lines changed

5 files changed

+150
-12
lines changed

coderd/coderdtest/coderdtest.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,11 @@ func AwaitTemplateVersionJob(t *testing.T, client *codersdk.Client, version uuid
411411

412412
t.Logf("waiting for template version job %s", version)
413413
var templateVersion codersdk.TemplateVersion
414-
require.Eventually(t, func() bool {
414+
require.True(t, testutil.EventuallyShort(t, func(ctx context.Context) bool {
415415
var err error
416-
templateVersion, err = client.TemplateVersion(context.Background(), version)
416+
templateVersion, err = client.TemplateVersion(ctx, version)
417417
return assert.NoError(t, err) && templateVersion.Job.CompletedAt != nil
418-
}, testutil.WaitShort, testutil.IntervalFast)
418+
}))
419419
return templateVersion
420420
}
421421

@@ -425,11 +425,10 @@ func AwaitWorkspaceBuildJob(t *testing.T, client *codersdk.Client, build uuid.UU
425425

426426
t.Logf("waiting for workspace build job %s", build)
427427
var workspaceBuild codersdk.WorkspaceBuild
428-
require.Eventually(t, func() bool {
429-
var err error
430-
workspaceBuild, err = client.WorkspaceBuild(context.Background(), build)
428+
require.True(t, testutil.EventuallyShort(t, func(ctx context.Context) bool {
429+
workspaceBuild, err := client.WorkspaceBuild(ctx, build)
431430
return assert.NoError(t, err) && workspaceBuild.Job.CompletedAt != nil
432-
}, testutil.WaitShort, testutil.IntervalFast)
431+
}))
433432
return workspaceBuild
434433
}
435434

@@ -439,21 +438,22 @@ func AwaitWorkspaceAgents(t *testing.T, client *codersdk.Client, build uuid.UUID
439438

440439
t.Logf("waiting for workspace agents (build %s)", build)
441440
var resources []codersdk.WorkspaceResource
442-
require.Eventually(t, func() bool {
441+
require.True(t, testutil.EventuallyLong(t, func(ctx context.Context) bool {
443442
var err error
444-
resources, err = client.WorkspaceResourcesByBuild(context.Background(), build)
443+
resources, err = client.WorkspaceResourcesByBuild(ctx, build)
445444
if !assert.NoError(t, err) {
446445
return false
447446
}
448447
for _, resource := range resources {
449448
for _, agent := range resource.Agents {
450449
if agent.Status != codersdk.WorkspaceAgentConnected {
450+
t.Logf("agent %s not connected yet", agent.Name)
451451
return false
452452
}
453453
}
454454
}
455455
return true
456-
}, testutil.WaitLong, testutil.IntervalMedium)
456+
}))
457457
return resources
458458
}
459459

coderd/coderdtest/coderdtest_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestNew(t *testing.T) {
1919
})
2020
user := coderdtest.CreateFirstUser(t, client)
2121
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
22-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
22+
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
2323
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
2424
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
2525
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)

scripts/rules.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func useStandardTimeoutsAndDelaysInTests(m dsl.Matcher) {
9191
m.Import("github.com/coder/coder/testutil")
9292

9393
m.Match(`context.WithTimeout($ctx, $duration)`).
94-
Where(m.File().Imports("testing") && !m["duration"].Text.Matches("^testutil\\.")).
94+
Where(m.File().Imports("testing") && !m.File().PkgPath.Matches("testutil$") && !m["duration"].Text.Matches("^testutil\\.")).
9595
At(m["duration"]).
9696
Report("Do not use magic numbers in test timeouts and delays. Use the standard testutil.Wait* or testutil.Interval* constants instead.")
9797

testutil/eventually.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package testutil
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
// Eventually is like require.Eventually except it allows passing
12+
// a context into the condition. It is safe to use with `require.*`.
13+
//
14+
// If ctx times out, the test will fail, but not immediately.
15+
// It is the caller's responsibility to exit early if required.
16+
//
17+
// It is the caller's responsibility to ensure that ctx has a
18+
// deadline or timeout set. Eventually will panic if this is not
19+
// the case in order to avoid potentially waiting forever.
20+
//
21+
// condition is not run in a goroutine; use the provided
22+
// context argument for cancellation if required.
23+
func Eventually(ctx context.Context, t testing.TB, condition func(context.Context) bool, tick time.Duration) bool {
24+
t.Helper()
25+
26+
if _, ok := ctx.Deadline(); !ok {
27+
panic("developer error: must set deadline or timeout on ctx")
28+
}
29+
30+
ticker := time.NewTicker(tick)
31+
defer ticker.Stop()
32+
for tick := ticker.C; ; {
33+
select {
34+
case <-ctx.Done():
35+
assert.NoError(t, ctx.Err(), "Eventually timed out")
36+
return false
37+
case <-tick:
38+
assert.NoError(t, ctx.Err(), "Eventually timed out")
39+
if condition(ctx) {
40+
return true
41+
}
42+
}
43+
}
44+
}
45+
46+
// EventuallyShort is a convenience function that runs Eventually with
47+
// IntervalFast and times out after WaitShort.
48+
func EventuallyShort(t testing.TB, condition func(context.Context) bool) bool {
49+
ctx, cancel := context.WithTimeout(context.Background(), WaitShort)
50+
defer cancel()
51+
return Eventually(ctx, t, condition, IntervalFast)
52+
}
53+
54+
// EventuallyMedium is a convenience function that runs Eventually with
55+
// IntervalMedium and times out after WaitMedium.
56+
func EventuallyMedium(t testing.TB, condition func(context.Context) bool) bool {
57+
ctx, cancel := context.WithTimeout(context.Background(), WaitMedium)
58+
defer cancel()
59+
return Eventually(ctx, t, condition, IntervalMedium)
60+
}
61+
62+
// EventuallyLong is a convenience function that runs Eventually with
63+
// IntervalSlow and times out after WaitLong.
64+
func EventuallyLong(t testing.TB, condition func(context.Context) bool) bool {
65+
ctx, cancel := context.WithTimeout(context.Background(), WaitLong)
66+
defer cancel()
67+
return Eventually(ctx, t, condition, IntervalSlow)
68+
}

testutil/eventually_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package testutil_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"go.uber.org/goleak"
9+
10+
"github.com/coder/coder/testutil"
11+
)
12+
13+
func TestMain(m *testing.M) {
14+
goleak.VerifyTestMain(m)
15+
}
16+
17+
func TestEventually(t *testing.T) {
18+
t.Parallel()
19+
t.Run("OK", func(t *testing.T) {
20+
t.Parallel()
21+
state := 0
22+
condition := func(_ context.Context) bool {
23+
defer func() {
24+
state++
25+
}()
26+
return state > 2
27+
}
28+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
29+
defer cancel()
30+
testutil.Eventually(ctx, t, condition, testutil.IntervalFast)
31+
})
32+
33+
t.Run("Timeout", func(t *testing.T) {
34+
t.Parallel()
35+
condition := func(_ context.Context) bool {
36+
return false
37+
}
38+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
39+
defer cancel()
40+
mockT := new(testing.T)
41+
testutil.Eventually(ctx, mockT, condition, testutil.IntervalFast)
42+
assert.True(t, mockT.Failed())
43+
})
44+
45+
t.Run("Panic", func(t *testing.T) {
46+
t.Parallel()
47+
48+
panicky := func() {
49+
mockT := new(testing.T)
50+
condition := func(_ context.Context) bool { return true }
51+
testutil.Eventually(context.Background(), mockT, condition, testutil.IntervalFast)
52+
}
53+
assert.Panics(t, panicky)
54+
})
55+
56+
t.Run("Short", func(t *testing.T) {
57+
t.Parallel()
58+
testutil.EventuallyShort(t, func(_ context.Context) bool { return true })
59+
})
60+
61+
t.Run("Medium", func(t *testing.T) {
62+
t.Parallel()
63+
testutil.EventuallyMedium(t, func(_ context.Context) bool { return true })
64+
})
65+
66+
t.Run("Long", func(t *testing.T) {
67+
t.Parallel()
68+
testutil.EventuallyLong(t, func(_ context.Context) bool { return true })
69+
})
70+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy