Skip to content

Commit 1633613

Browse files
committed
feat: add testutil.Eventually
1 parent fb9fca8 commit 1633613

File tree

4 files changed

+143
-9
lines changed

4 files changed

+143
-9
lines changed

coderd/coderdtest/coderdtest.go

Lines changed: 8 additions & 8 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() bool {
415415
var err error
416416
templateVersion, err = client.TemplateVersion(context.Background(), 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() bool {
429+
workspaceBuild, err := client.WorkspaceBuild(context.Background(), build)
431430
return assert.NoError(t, err) && workspaceBuild.Job.CompletedAt != nil
432-
}, testutil.WaitShort, testutil.IntervalFast)
431+
}))
433432
return workspaceBuild
434433
}
435434

@@ -439,7 +438,7 @@ 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.EventuallyShort(t, func() bool {
443442
var err error
444443
resources, err = client.WorkspaceResourcesByBuild(context.Background(), build)
445444
if !assert.NoError(t, err) {
@@ -448,12 +447,13 @@ func AwaitWorkspaceAgents(t *testing.T, client *codersdk.Client, build uuid.UUID
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)

testutil/eventually.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package testutil
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
)
8+
9+
// Eventually is like require.Eventually except it takes a context.
10+
// If ctx times out, the test will fail.
11+
// ctx must have a deadline set or this will panic.
12+
func Eventually(ctx context.Context, t testing.TB, condition func() bool, tick time.Duration) bool {
13+
t.Helper()
14+
15+
if _, ok := ctx.Deadline(); !ok {
16+
panic("developer error: must set deadline on ctx")
17+
}
18+
19+
ch := make(chan bool, 1)
20+
ticker := time.NewTicker(tick)
21+
defer ticker.Stop()
22+
for tick := ticker.C; ; {
23+
select {
24+
case <-ctx.Done():
25+
t.Errorf("Await timed out")
26+
return false
27+
case <-tick:
28+
tick = nil
29+
go func() { ch <- condition() }()
30+
case v := <-ch:
31+
if v {
32+
return true
33+
}
34+
tick = ticker.C
35+
}
36+
}
37+
}
38+
39+
// EventuallyShort is a convenience function that runs Eventually with
40+
// IntervalFast and times out after WaitShort.
41+
func EventuallyShort(t testing.TB, condition func() bool) bool {
42+
//nolint: gocritic
43+
ctx, cancel := context.WithTimeout(context.Background(), WaitShort)
44+
defer cancel()
45+
return Eventually(ctx, t, condition, IntervalFast)
46+
}
47+
48+
// EventuallyMedium is a convenience function that runs Eventually with
49+
// IntervalMedium and times out after WaitMedium.
50+
func EventuallyMedium(t testing.TB, condition func() bool) bool {
51+
//nolint: gocritic
52+
ctx, cancel := context.WithTimeout(context.Background(), WaitMedium)
53+
defer cancel()
54+
return Eventually(ctx, t, condition, IntervalMedium)
55+
}
56+
57+
// EventuallyLong is a convenience function that runs Eventually with
58+
// IntervalSlow and times out after WaitLong.
59+
func EventuallyLong(t testing.TB, condition func() bool) bool {
60+
//nolint: gocritic
61+
ctx, cancel := context.WithTimeout(context.Background(), WaitLong)
62+
defer cancel()
63+
return Eventually(ctx, t, condition, IntervalSlow)
64+
}

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() 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+
assert.True(t, testutil.Eventually(ctx, t, condition, testutil.IntervalFast))
31+
})
32+
33+
t.Run("Timeout", func(t *testing.T) {
34+
t.Parallel()
35+
condition := func() bool {
36+
return false
37+
}
38+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
39+
defer cancel()
40+
mockT := new(testing.T)
41+
assert.False(t, 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() bool { return true }
51+
assert.False(t, 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+
assert.True(t, testutil.EventuallyShort(t, func() bool { return true }))
59+
})
60+
61+
t.Run("Medium", func(t *testing.T) {
62+
t.Parallel()
63+
assert.True(t, testutil.EventuallyMedium(t, func() bool { return true }))
64+
})
65+
66+
t.Run("Long", func(t *testing.T) {
67+
t.Parallel()
68+
assert.True(t, testutil.EventuallyLong(t, func() bool { return true }))
69+
})
70+
}

0 commit comments

Comments
 (0)