Skip to content

Commit 0802b32

Browse files
authored
Retry task tests (#9)
1 parent 952d245 commit 0802b32

File tree

5 files changed

+302
-2
lines changed

5 files changed

+302
-2
lines changed

.github/workflows/main.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ jobs:
2929
- name: Sanity check version
3030
run: ./dist/retry -version
3131

32+
test:
33+
name: Test
34+
runs-on: ubuntu-latest
35+
36+
steps:
37+
- name: Checkout code
38+
uses: actions/checkout@v1
39+
40+
- name: Run tests
41+
run: go test -v -race -cover ./...
42+
3243
golang-ci-lint:
3344
name: Lint
3445
runs-on: ubuntu-latest

.golangci.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
linters:
22
enable-all: true
33
disable:
4-
- lll
5-
- wsl
4+
- lll
5+
- wsl
6+
7+
issues:
8+
exclude:
9+
# Triggered by table tests calling t.Run. See
10+
# https://github.com/kyoh86/scopelint/issues/4 for more information.
11+
- Using the variable on range scope `test` in function literal
12+
# Triggered by long table tests.
13+
- Function 'Test\w+' is too long

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/joshdk/retry
22

33
go 1.12
4+
5+
require github.com/stretchr/testify v1.4.0

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
6+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
8+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
12+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

retry/retry_test.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package retry
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestRetry(t *testing.T) {
14+
tests := []struct {
15+
title string
16+
task Task
17+
spec Spec
18+
results []result
19+
failed bool
20+
duration time.Duration
21+
}{
22+
{
23+
title: "succeed fast",
24+
task: NewExecTask("true"),
25+
spec: Spec{
26+
Attempts: 3,
27+
},
28+
results: []result{
29+
{elapsed: 0, failed: false},
30+
},
31+
failed: false,
32+
duration: 0,
33+
},
34+
{
35+
title: "fail fast",
36+
task: NewExecTask("false"),
37+
spec: Spec{
38+
Attempts: 3,
39+
},
40+
results: []result{
41+
{elapsed: 0, failed: true},
42+
{elapsed: 0, failed: true},
43+
{elapsed: 0, failed: true},
44+
},
45+
failed: true,
46+
duration: 0,
47+
},
48+
{
49+
title: "succeed slow with task time",
50+
task: NewExecTask("sleep", "2"),
51+
spec: Spec{
52+
Attempts: 3,
53+
TaskTime: 3 * time.Second,
54+
},
55+
results: []result{
56+
{elapsed: 2 * time.Second, failed: false},
57+
},
58+
failed: false,
59+
duration: 2 * time.Second,
60+
},
61+
{
62+
title: "succeed slow consecutively with task time",
63+
task: NewExecTask("sleep", "2"),
64+
spec: Spec{
65+
Attempts: 3,
66+
TaskTime: 3 * time.Second,
67+
Consecutive: 3,
68+
},
69+
results: []result{
70+
{elapsed: 2 * time.Second, failed: false},
71+
{elapsed: 2 * time.Second, failed: false},
72+
{elapsed: 2 * time.Second, failed: false},
73+
},
74+
failed: false,
75+
duration: 6 * time.Second,
76+
},
77+
{
78+
title: "fail slow with task time",
79+
task: NewExecTask("sleep", "600"),
80+
spec: Spec{
81+
Attempts: 3,
82+
TaskTime: 3 * time.Second,
83+
},
84+
results: []result{
85+
{elapsed: 3 * time.Second, failed: true},
86+
{elapsed: 3 * time.Second, failed: true},
87+
{elapsed: 3 * time.Second, failed: true},
88+
},
89+
failed: true,
90+
duration: 9 * time.Second,
91+
},
92+
{
93+
title: "fail slow with task time and sleep",
94+
task: NewExecTask("sleep", "600"),
95+
spec: Spec{
96+
Attempts: 3,
97+
TaskTime: 3 * time.Second,
98+
Sleep: 3 * time.Second,
99+
},
100+
results: []result{
101+
{elapsed: 3 * time.Second, failed: true},
102+
{elapsed: 3 * time.Second, failed: true},
103+
{elapsed: 3 * time.Second, failed: true},
104+
},
105+
failed: true,
106+
duration: 15 * time.Second,
107+
},
108+
{
109+
title: "fail slow with task time, sleep, and backoff",
110+
task: NewExecTask("sleep", "600"),
111+
spec: Spec{
112+
Attempts: 3,
113+
TaskTime: 3 * time.Second,
114+
Sleep: 3 * time.Second,
115+
Backoff: true,
116+
},
117+
results: []result{
118+
{elapsed: 3 * time.Second, failed: true},
119+
{elapsed: 3 * time.Second, failed: true},
120+
{elapsed: 3 * time.Second, failed: true},
121+
},
122+
failed: true,
123+
duration: 18 * time.Second,
124+
},
125+
{
126+
title: "fail slow with task time, sleep, backoff, and total time",
127+
task: NewExecTask("sleep", "600"),
128+
spec: Spec{
129+
Attempts: 3,
130+
TaskTime: 3 * time.Second,
131+
Sleep: 3 * time.Second,
132+
Backoff: true,
133+
TotalTime: 12 * time.Second,
134+
},
135+
results: []result{
136+
{elapsed: 3 * time.Second, failed: true},
137+
{elapsed: 3 * time.Second, failed: true},
138+
},
139+
failed: true,
140+
duration: 12 * time.Second,
141+
},
142+
{
143+
title: "http url",
144+
task: NewHTTPTask("http://www.google.com"),
145+
spec: Spec{
146+
Attempts: 1,
147+
},
148+
results: []result{
149+
{elapsed: 0, failed: false},
150+
},
151+
failed: false,
152+
duration: 0,
153+
},
154+
{
155+
title: "https url",
156+
task: NewHTTPTask("https://www.google.com"),
157+
spec: Spec{
158+
Attempts: 1,
159+
},
160+
results: []result{
161+
{elapsed: 0, failed: false},
162+
},
163+
failed: false,
164+
duration: 0,
165+
},
166+
{
167+
title: "bad url",
168+
task: NewHTTPTask("https://fake.example.com"),
169+
spec: Spec{
170+
Attempts: 1,
171+
},
172+
results: []result{
173+
{elapsed: 0, failed: true},
174+
},
175+
failed: true,
176+
duration: 0,
177+
},
178+
}
179+
180+
for index, test := range tests {
181+
name := fmt.Sprintf("%d %s", index, test.title)
182+
t.Run(name, func(t *testing.T) {
183+
test := test
184+
t.Parallel()
185+
186+
var (
187+
task = newWrappedTask(test.task)
188+
start = time.Now()
189+
err = Retry(test.spec, task)
190+
end = time.Now()
191+
actual = end.Sub(start)
192+
)
193+
194+
// Sanity check that there were the same number of actual results
195+
// as expected.
196+
require.Equal(t, len(test.results), len(task.results))
197+
198+
// Check error for overall task run.
199+
checkError(t, test.failed, err)
200+
201+
// Check duration for overall task run.
202+
checkDuration(t, test.duration, actual)
203+
204+
for i, result := range test.results {
205+
// Check error for this specific task run.
206+
checkError(t, result.failed, task.results[i].error)
207+
208+
// Check duration for this specific task run.
209+
checkDuration(t, result.elapsed, task.results[i].elapsed)
210+
}
211+
})
212+
}
213+
}
214+
215+
func checkError(t *testing.T, failureExpected bool, actual error) {
216+
t.Helper()
217+
if failureExpected {
218+
assert.Error(t, actual)
219+
} else {
220+
assert.NoError(t, actual)
221+
}
222+
}
223+
224+
func checkDuration(t *testing.T, expected time.Duration, actual time.Duration) {
225+
// epsilon is the time duration delta that is allowed when comparing times.
226+
// Higher epsilon values result in longer time margins. Lower epsilon
227+
// values result in smaller time margins, but potentially flaky tests.
228+
epsilon := time.Millisecond * 500
229+
230+
t.Helper()
231+
if actual < expected-epsilon || expected+epsilon < actual {
232+
assert.Failf(t, "duration mismatch", "A duration of %v ± %v is expected but got %v", expected, epsilon, actual)
233+
}
234+
}
235+
236+
type result struct {
237+
elapsed time.Duration
238+
error error
239+
failed bool
240+
}
241+
242+
type wrappedTask struct {
243+
task Task
244+
results []result
245+
}
246+
247+
func (t *wrappedTask) Run(ctx context.Context) error {
248+
var (
249+
start = time.Now()
250+
err = t.task.Run(ctx)
251+
end = time.Now()
252+
)
253+
254+
t.results = append(t.results, result{
255+
elapsed: end.Sub(start),
256+
error: err,
257+
failed: err != nil,
258+
})
259+
260+
return err
261+
}
262+
263+
func newWrappedTask(task Task) *wrappedTask {
264+
return &wrappedTask{
265+
task: task,
266+
}
267+
}

0 commit comments

Comments
 (0)