1
1
package circuitbreaker
2
2
3
3
import (
4
+ "errors"
5
+ "sync/atomic"
4
6
"testing"
5
7
6
8
"github.com/alibaba/sentinel-golang/core/base"
9
+ sbase "github.com/alibaba/sentinel-golang/core/stat/base"
10
+ "github.com/alibaba/sentinel-golang/logging"
11
+ "github.com/alibaba/sentinel-golang/util"
7
12
"github.com/stretchr/testify/assert"
8
13
"github.com/stretchr/testify/mock"
9
14
)
@@ -37,6 +42,26 @@ func (m *CircuitBreakerMock) OnRequestComplete(rt uint64, err error) {
37
42
return
38
43
}
39
44
45
+ type StateChangeListenerMock struct {
46
+ mock.Mock
47
+ }
48
+
49
+ func (s * StateChangeListenerMock ) OnTransformToClosed (prev State , rule Rule ) {
50
+ _ = s .Called (prev , rule )
51
+ logging .Debug ("transform to closed" , "strategy" , rule .Strategy , "prevState" , prev .String ())
52
+ return
53
+ }
54
+
55
+ func (s * StateChangeListenerMock ) OnTransformToOpen (prev State , rule Rule , snapshot interface {}) {
56
+ _ = s .Called (prev , rule , snapshot )
57
+ logging .Debug ("transform to open" , "strategy" , rule .Strategy , "prevState" , prev .String (), "snapshot" , snapshot )
58
+ }
59
+
60
+ func (s * StateChangeListenerMock ) OnTransformToHalfOpen (prev State , rule Rule ) {
61
+ _ = s .Called (prev , rule )
62
+ logging .Debug ("transform to Half-Open" , "strategy" , rule .Strategy , "prevState" , prev .String ())
63
+ }
64
+
40
65
func TestStatus (t * testing.T ) {
41
66
t .Run ("get_set" , func (t * testing.T ) {
42
67
status := new (State )
@@ -56,3 +81,294 @@ func TestStatus(t *testing.T) {
56
81
assert .True (t , status .casState (HalfOpen , Open ))
57
82
})
58
83
}
84
+
85
+ func TestSlowRtCircuitBreaker_TryPass (t * testing.T ) {
86
+ ClearStateChangeListeners ()
87
+ stateChangeListenerMock := & StateChangeListenerMock {}
88
+ stateChangeListenerMock .On ("OnTransformToHalfOpen" , Open , mock .Anything ).Return ()
89
+ RegisterStateChangeListeners (stateChangeListenerMock )
90
+ t .Run ("TryPass_Closed" , func (t * testing.T ) {
91
+ r := & Rule {
92
+ Resource : "abc" ,
93
+ Strategy : SlowRequestRatio ,
94
+ RetryTimeoutMs : 3000 ,
95
+ MinRequestAmount : 10 ,
96
+ StatIntervalMs : 10000 ,
97
+ MaxAllowedRtMs : 50 ,
98
+ Threshold : 0.5 ,
99
+ }
100
+ b , err := newSlowRtCircuitBreaker (r )
101
+ assert .Nil (t , err )
102
+ pass := b .TryPass (base .NewEmptyEntryContext ())
103
+ assert .True (t , pass )
104
+ })
105
+
106
+ t .Run ("TryPass_Probe" , func (t * testing.T ) {
107
+ r := & Rule {
108
+ Resource : "abc" ,
109
+ Strategy : SlowRequestRatio ,
110
+ RetryTimeoutMs : 3000 ,
111
+ MinRequestAmount : 10 ,
112
+ StatIntervalMs : 10000 ,
113
+ MaxAllowedRtMs : 50 ,
114
+ Threshold : 0.5 ,
115
+ }
116
+ b , err := newSlowRtCircuitBreaker (r )
117
+ assert .Nil (t , err )
118
+
119
+ b .state .set (Open )
120
+ ctx := & base.EntryContext {
121
+ Resource : base .NewResourceWrapper ("abc" , base .ResTypeCommon , base .Inbound ),
122
+ }
123
+ e := base .NewSentinelEntry (ctx , base .NewResourceWrapper ("abc" , base .ResTypeCommon , base .Inbound ), nil )
124
+ ctx .SetEntry (e )
125
+ pass := b .TryPass (ctx )
126
+ assert .True (t , pass )
127
+ assert .True (t , b .state .get () == HalfOpen )
128
+ })
129
+ }
130
+
131
+ func TestSlowRt_OnRequestComplete (t * testing.T ) {
132
+ ClearStateChangeListeners ()
133
+ r := & Rule {
134
+ Resource : "abc" ,
135
+ Strategy : SlowRequestRatio ,
136
+ RetryTimeoutMs : 3000 ,
137
+ MinRequestAmount : 10 ,
138
+ StatIntervalMs : 10000 ,
139
+ MaxAllowedRtMs : 50 ,
140
+ Threshold : 0.5 ,
141
+ }
142
+ b , err := newSlowRtCircuitBreaker (r )
143
+ assert .Nil (t , err )
144
+ t .Run ("OnRequestComplete_Less_Than_MinRequestMount" , func (t * testing.T ) {
145
+ b .OnRequestComplete (base .NewEmptyEntryContext ().Rt (), nil )
146
+ assert .True (t , b .CurrentState () == Closed )
147
+ })
148
+ t .Run ("OnRequestComplete_Probe_Failed" , func (t * testing.T ) {
149
+ b .state .set (HalfOpen )
150
+ b .OnRequestComplete (base .NewEmptyEntryContext ().Rt (), nil )
151
+ assert .True (t , b .CurrentState () == Open )
152
+ })
153
+ t .Run ("OnRequestComplete_Probe_Succeed" , func (t * testing.T ) {
154
+ b .state .set (HalfOpen )
155
+ b .OnRequestComplete (10 , nil )
156
+ assert .True (t , b .CurrentState () == Closed )
157
+ })
158
+ }
159
+
160
+ func TestSlowRt_ResetBucketTo (t * testing.T ) {
161
+ t .Run ("ResetBucketTo" , func (t * testing.T ) {
162
+ wrap := & sbase.BucketWrap {
163
+ BucketStart : 1 ,
164
+ Value : atomic.Value {},
165
+ }
166
+ wrap .Value .Store (& slowRequestCounter {
167
+ slowCount : 1 ,
168
+ totalCount : 1 ,
169
+ })
170
+
171
+ la := & slowRequestLeapArray {}
172
+ la .ResetBucketTo (wrap , util .CurrentTimeMillis ())
173
+ counter := wrap .Value .Load ().(* slowRequestCounter )
174
+ assert .True (t , counter .totalCount == 0 && counter .slowCount == 0 )
175
+ })
176
+ }
177
+
178
+ func TestErrorRatioCircuitBreaker_TryPass (t * testing.T ) {
179
+ t .Run ("TryPass_Closed" , func (t * testing.T ) {
180
+ r := & Rule {
181
+ Resource : "abc" ,
182
+ Strategy : ErrorRatio ,
183
+ RetryTimeoutMs : 3000 ,
184
+ MinRequestAmount : 10 ,
185
+ StatIntervalMs : 10000 ,
186
+ Threshold : 0.5 ,
187
+ }
188
+ b , err := newErrorRatioCircuitBreaker (r )
189
+ assert .Nil (t , err )
190
+ pass := b .TryPass (base .NewEmptyEntryContext ())
191
+ assert .True (t , pass )
192
+ })
193
+
194
+ t .Run ("TryPass_Probe" , func (t * testing.T ) {
195
+ r := & Rule {
196
+ Resource : "abc" ,
197
+ Strategy : ErrorRatio ,
198
+ RetryTimeoutMs : 3000 ,
199
+ MinRequestAmount : 10 ,
200
+ StatIntervalMs : 10000 ,
201
+ Threshold : 0.5 ,
202
+ }
203
+ b , err := newErrorRatioCircuitBreaker (r )
204
+ assert .Nil (t , err )
205
+
206
+ b .state .set (Open )
207
+ ctx := & base.EntryContext {
208
+ Resource : base .NewResourceWrapper ("abc" , base .ResTypeCommon , base .Inbound ),
209
+ }
210
+ e := base .NewSentinelEntry (ctx , base .NewResourceWrapper ("abc" , base .ResTypeCommon , base .Inbound ), nil )
211
+ ctx .SetEntry (e )
212
+ pass := b .TryPass (ctx )
213
+ assert .True (t , pass )
214
+ assert .True (t , b .state .get () == HalfOpen )
215
+ })
216
+ }
217
+
218
+ func TestErrorRatio_OnRequestComplete (t * testing.T ) {
219
+ r := & Rule {
220
+ Resource : "abc" ,
221
+ Strategy : ErrorRatio ,
222
+ RetryTimeoutMs : 3000 ,
223
+ MinRequestAmount : 10 ,
224
+ StatIntervalMs : 10000 ,
225
+ Threshold : 0.5 ,
226
+ }
227
+ b , err := newErrorRatioCircuitBreaker (r )
228
+ assert .Nil (t , err )
229
+ t .Run ("OnRequestComplete_Less_Than_MinRequestAmount" , func (t * testing.T ) {
230
+ b .OnRequestComplete (base .NewEmptyEntryContext ().Rt (), nil )
231
+ assert .True (t , b .CurrentState () == Closed )
232
+ })
233
+ t .Run ("OnRequestComplete_Probe_Succeed" , func (t * testing.T ) {
234
+ b .state .set (HalfOpen )
235
+ b .OnRequestComplete (base .NewEmptyEntryContext ().Rt (), nil )
236
+ assert .True (t , b .CurrentState () == Closed )
237
+ })
238
+ t .Run ("OnRequestComplete_Probe_Failed" , func (t * testing.T ) {
239
+ b .state .set (HalfOpen )
240
+ b .OnRequestComplete (0 , errors .New ("errorRatio" ))
241
+ assert .True (t , b .CurrentState () == Open )
242
+ })
243
+ }
244
+
245
+ func TestErrorRatio_ResetBucketTo (t * testing.T ) {
246
+ t .Run ("ResetBucketTo" , func (t * testing.T ) {
247
+ wrap := & sbase.BucketWrap {
248
+ BucketStart : 1 ,
249
+ Value : atomic.Value {},
250
+ }
251
+ wrap .Value .Store (& errorCounter {
252
+ errorCount : 1 ,
253
+ totalCount : 1 ,
254
+ })
255
+
256
+ la := & errorCounterLeapArray {}
257
+ la .ResetBucketTo (wrap , util .CurrentTimeMillis ())
258
+ counter := wrap .Value .Load ().(* errorCounter )
259
+ assert .True (t , counter .errorCount == 0 && counter .totalCount == 0 )
260
+ })
261
+ }
262
+
263
+ func TestErrorCountCircuitBreaker_TryPass (t * testing.T ) {
264
+ t .Run ("TryPass_Closed" , func (t * testing.T ) {
265
+ r := & Rule {
266
+ Resource : "abc" ,
267
+ Strategy : ErrorCount ,
268
+ RetryTimeoutMs : 3000 ,
269
+ MinRequestAmount : 10 ,
270
+ StatIntervalMs : 10000 ,
271
+ Threshold : 1 ,
272
+ }
273
+ b , err := newErrorCountCircuitBreaker (r )
274
+ assert .Nil (t , err )
275
+ pass := b .TryPass (base .NewEmptyEntryContext ())
276
+ assert .True (t , pass )
277
+ })
278
+
279
+ t .Run ("TryPass_Probe" , func (t * testing.T ) {
280
+ r := & Rule {
281
+ Resource : "abc" ,
282
+ Strategy : ErrorCount ,
283
+ RetryTimeoutMs : 3000 ,
284
+ MinRequestAmount : 10 ,
285
+ StatIntervalMs : 10000 ,
286
+ Threshold : 1 ,
287
+ }
288
+ b , err := newErrorCountCircuitBreaker (r )
289
+ assert .Nil (t , err )
290
+
291
+ b .state .set (Open )
292
+ ctx := & base.EntryContext {
293
+ Resource : base .NewResourceWrapper ("abc" , base .ResTypeCommon , base .Inbound ),
294
+ }
295
+ e := base .NewSentinelEntry (ctx , base .NewResourceWrapper ("abc" , base .ResTypeCommon , base .Inbound ), nil )
296
+ ctx .SetEntry (e )
297
+ pass := b .TryPass (ctx )
298
+ assert .True (t , pass )
299
+ assert .True (t , b .state .get () == HalfOpen )
300
+ })
301
+ }
302
+
303
+ func TestErrorCount_OnRequestComplete (t * testing.T ) {
304
+ r := & Rule {
305
+ Resource : "abc" ,
306
+ Strategy : ErrorCount ,
307
+ RetryTimeoutMs : 3000 ,
308
+ MinRequestAmount : 10 ,
309
+ StatIntervalMs : 10000 ,
310
+ Threshold : 1 ,
311
+ }
312
+ b , err := newErrorCountCircuitBreaker (r )
313
+ assert .Nil (t , err )
314
+ t .Run ("OnRequestComplete_Less_Than_MinRequestAmount" , func (t * testing.T ) {
315
+ b .OnRequestComplete (base .NewEmptyEntryContext ().Rt (), nil )
316
+ assert .True (t , b .CurrentState () == Closed )
317
+ })
318
+ t .Run ("OnRequestComplete_Probe_Succeed" , func (t * testing.T ) {
319
+ b .state .set (HalfOpen )
320
+ b .OnRequestComplete (base .NewEmptyEntryContext ().Rt (), nil )
321
+ assert .True (t , b .CurrentState () == Closed )
322
+ })
323
+ t .Run ("OnRequestComplete_Probe_Failed" , func (t * testing.T ) {
324
+ b .state .set (HalfOpen )
325
+ b .OnRequestComplete (0 , errors .New ("errorCount" ))
326
+ assert .True (t , b .CurrentState () == Open )
327
+ })
328
+ }
329
+
330
+ func TestFromClosedToOpen (t * testing.T ) {
331
+ ClearStateChangeListeners ()
332
+ stateChangeListenerMock := & StateChangeListenerMock {}
333
+ stateChangeListenerMock .On ("OnTransformToOpen" , Closed , mock .Anything , mock .Anything ).Return ()
334
+ RegisterStateChangeListeners (stateChangeListenerMock )
335
+ t .Run ("FromCloseToOpen" , func (t * testing.T ) {
336
+ r := & Rule {
337
+ Resource : "abc" ,
338
+ Strategy : ErrorCount ,
339
+ RetryTimeoutMs : 3000 ,
340
+ MinRequestAmount : 10 ,
341
+ StatIntervalMs : 10000 ,
342
+ Threshold : 1 ,
343
+ }
344
+ b , err := newErrorCountCircuitBreaker (r )
345
+ assert .Nil (t , err )
346
+ statusChanged := b .fromClosedToOpen ("" )
347
+ assert .True (t , statusChanged )
348
+ stateChangeListenerMock .MethodCalled ("OnTransformToOpen" , Closed , mock .Anything , mock .Anything )
349
+ })
350
+ }
351
+
352
+ func TestFromHalfOpenToOpen (t * testing.T ) {
353
+ ClearStateChangeListeners ()
354
+ stateChangeListenerMock := & StateChangeListenerMock {}
355
+ stateChangeListenerMock .On ("OnTransformToOpen" , HalfOpen , mock .Anything , mock .Anything ).Return ()
356
+ RegisterStateChangeListeners (stateChangeListenerMock )
357
+ t .Run ("FromHalfOpenToOpen" , func (t * testing.T ) {
358
+ r := & Rule {
359
+ Resource : "abc" ,
360
+ Strategy : ErrorCount ,
361
+ RetryTimeoutMs : 3000 ,
362
+ MinRequestAmount : 10 ,
363
+ StatIntervalMs : 10000 ,
364
+ Threshold : 1 ,
365
+ }
366
+ b , err := newErrorCountCircuitBreaker (r )
367
+ assert .Nil (t , err )
368
+ b .state .set (HalfOpen )
369
+ statusChanged := b .fromHalfOpenToOpen ("" )
370
+ assert .True (t , statusChanged )
371
+ assert .True (t , b .nextRetryTimestampMs > 0 )
372
+ stateChangeListenerMock .MethodCalled ("OnTransformToOpen" , HalfOpen , mock .Anything , mock .Anything )
373
+ })
374
+ }
0 commit comments