Skip to content

Commit 05c723a

Browse files
committed
perfect circuit breaker module unit test
1 parent 392e84f commit 05c723a

File tree

3 files changed

+545
-0
lines changed

3 files changed

+545
-0
lines changed

core/circuitbreaker/circuit_breaker_test.go

+316
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package circuitbreaker
22

33
import (
4+
"errors"
5+
"sync/atomic"
46
"testing"
57

68
"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"
712
"github.com/stretchr/testify/assert"
813
"github.com/stretchr/testify/mock"
914
)
@@ -37,6 +42,26 @@ func (m *CircuitBreakerMock) OnRequestComplete(rt uint64, err error) {
3742
return
3843
}
3944

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+
4065
func TestStatus(t *testing.T) {
4166
t.Run("get_set", func(t *testing.T) {
4267
status := new(State)
@@ -56,3 +81,294 @@ func TestStatus(t *testing.T) {
5681
assert.True(t, status.casState(HalfOpen, Open))
5782
})
5883
}
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

Comments
 (0)