1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package cloudsql
16
17 import (
18 "context"
19 "errors"
20 "fmt"
21 "testing"
22 "time"
23
24 "google.golang.org/api/googleapi"
25 )
26
27 func TestRetryExponentialBackoff(t *testing.T) {
28 tcs := []struct {
29 attempt int
30
31 low time.Duration
32 high time.Duration
33 }{
34 {
35 attempt: 0,
36 low: 324 * time.Millisecond,
37 high: 524 * time.Millisecond,
38 },
39 {
40 attempt: 1,
41 low: 524 * time.Millisecond,
42 high: 847 * time.Millisecond,
43 },
44 {
45 attempt: 2,
46 low: 847 * time.Millisecond,
47 high: 1371 * time.Millisecond,
48 },
49 {
50 attempt: 3,
51 low: 1371 * time.Millisecond,
52 high: 2218 * time.Millisecond,
53 },
54 {
55 attempt: 4,
56 low: 2218 * time.Millisecond,
57 high: 3588 * time.Millisecond,
58 },
59 }
60
61 for _, tc := range tcs {
62 t.Run(fmt.Sprintf("attempt %d", tc.attempt), func(t *testing.T) {
63 got := exponentialBackoff(tc.attempt)
64 got = got.Round(time.Millisecond)
65 if got < tc.low {
66 t.Fatalf("got was below lower bound, got = %v, want = %v",
67 got, tc.low,
68 )
69 }
70 if got > tc.high {
71 t.Fatalf("got was above upper bound, got = %v, want = %v",
72 got, tc.high,
73 )
74 }
75 })
76 }
77 }
78
79 func TestRetry(t *testing.T) {
80 tcs := []struct {
81 desc string
82 f func(context.Context) (*any, error)
83 wantCount int
84 }{
85 {
86 desc: "unknown errors are not retried",
87 f: func(context.Context) (*any, error) {
88 return nil, errors.New("unknown")
89 },
90 wantCount: 0,
91 },
92 {
93 desc: "do not retry non-50x responses",
94 f: func(context.Context) (*any, error) {
95 return nil, &googleapi.Error{
96 Code: 400,
97 }
98 },
99 wantCount: 0,
100 },
101 {
102 desc: "retry >= 500 HTTP responses with wait function",
103 f: func(context.Context) (*any, error) {
104 return nil, &googleapi.Error{
105 Code: 500,
106 }
107 },
108 wantCount: 5,
109 },
110 {
111 desc: "successful response is not retried",
112 f: func(context.Context) (*any, error) {
113
114 return nil, nil
115 },
116 wantCount: 0,
117 },
118 }
119 for _, tc := range tcs {
120 t.Run(tc.desc, func(t *testing.T) {
121 var callCount int
122 waitSpy := func(int) time.Duration {
123 callCount++
124 return time.Microsecond
125 }
126
127 retry50x(context.Background(), tc.f, waitSpy)
128
129 if callCount != tc.wantCount {
130 t.Fatalf(
131 "retry call count, want = %v, got = %v",
132 tc.wantCount, callCount,
133 )
134 }
135 })
136 }
137 }
138
139 func TestRetryExitsEarlyOnContextCancellation(t *testing.T) {
140 ctx, cancel := context.WithCancel(context.Background())
141 cancel()
142
143 fail := func(context.Context) (*any, error) {
144 err := &googleapi.Error{
145 Code: 500,
146 Message: "I always fail",
147 }
148 return nil, err
149 }
150
151 waitOneHour := func(int) time.Duration { return time.Hour }
152
153 _, err := retry50x(ctx, fail, waitOneHour)
154 if !errors.Is(err, context.Canceled) {
155 t.Fatalf("want = %v, got = %v", context.Canceled, err)
156 }
157 }
158
View as plain text