github.com/cloudwego/kitex@v0.9.0/pkg/circuitbreak/circuitbreak_test.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package circuitbreak 18 19 import ( 20 "context" 21 "errors" 22 "testing" 23 24 "github.com/bytedance/gopkg/cloud/circuitbreaker" 25 26 "github.com/cloudwego/kitex/internal/test" 27 "github.com/cloudwego/kitex/pkg/kerrors" 28 ) 29 30 type ctxKeyType int 31 32 const ( 33 ctxKey ctxKeyType = iota 34 ctxEnabled 35 ctxErrorType 36 ) 37 38 var ( 39 errFake = errors.New("fake error") 40 errDecorate = errors.New("decorate error") 41 allowed = "Allowed" 42 ) 43 44 func invoke(ctx context.Context, request, response interface{}) error { 45 return errFake 46 } 47 48 func mockGetKey(ctx context.Context, request interface{}) (key string, enabled bool) { 49 key = ctx.Value(ctxKey).(string) 50 enabled = ctx.Value(ctxEnabled).(bool) 51 return 52 } 53 54 func mockGetErrorType(ctx context.Context, request, response interface{}, err error) ErrorType { 55 t, ok := ctx.Value(ctxErrorType).(ErrorType) 56 if !ok { 57 return TypeIgnorable 58 } 59 return t 60 } 61 62 func mockDecorateError(ctx context.Context, request interface{}, err error) error { 63 return errDecorate 64 } 65 66 type mockPanel struct { 67 circuitbreaker.Panel 68 judged bool 69 timeoutRecorded bool 70 failRecorded bool 71 succeedRecorded bool 72 } 73 74 func (m *mockPanel) IsAllowed(key string) bool { 75 m.judged = true 76 return key == allowed 77 } 78 79 func (m *mockPanel) Timeout(key string) { 80 m.timeoutRecorded = true 81 } 82 83 func (m *mockPanel) Fail(key string) { 84 m.failRecorded = true 85 } 86 87 func (m *mockPanel) Succeed(key string) { 88 m.succeedRecorded = true 89 } 90 91 func TestNewCircuitBreakerMW(t *testing.T) { 92 ctl := Control{ 93 GetKey: mockGetKey, 94 GetErrorType: mockGetErrorType, 95 DecorateError: mockDecorateError, 96 } 97 panel, err := circuitbreaker.NewPanel(nil, circuitbreaker.Options{}) 98 if err != nil { 99 t.Fatal(err) 100 } 101 mp := &mockPanel{Panel: panel} 102 cbMW := NewCircuitBreakerMW(ctl, mp) 103 // test disabled 104 ctx := context.Background() 105 ctx = context.WithValue(ctx, ctxKey, allowed) 106 ctx = context.WithValue(ctx, ctxEnabled, false) 107 test.Assert(t, errors.Is(cbMW(invoke)(ctx, nil, nil), errFake)) 108 test.Assert(t, !mp.judged) 109 // test enabled and allowed 110 ctx = context.WithValue(ctx, ctxKey, allowed) 111 ctx = context.WithValue(ctx, ctxEnabled, true) 112 test.Assert(t, errors.Is(cbMW(invoke)(ctx, nil, nil), errFake)) 113 test.Assert(t, mp.judged) 114 // test enabled and not allowed 115 ctx = context.WithValue(ctx, ctxKey, "you should not pass") 116 ctx = context.WithValue(ctx, ctxEnabled, true) 117 test.Assert(t, errors.Is(cbMW(invoke)(ctx, nil, nil), errDecorate)) 118 test.Assert(t, mp.judged) 119 } 120 121 func TestRecordStat(t *testing.T) { 122 ctl := Control{ 123 GetKey: mockGetKey, 124 GetErrorType: mockGetErrorType, 125 DecorateError: mockDecorateError, 126 } 127 panel, err := circuitbreaker.NewPanel(nil, circuitbreaker.Options{}) 128 if err != nil { 129 t.Fatal(err) 130 } 131 // test timeout 132 mp := &mockPanel{Panel: panel} 133 cbMW := NewCircuitBreakerMW(ctl, mp) 134 ctx := context.Background() 135 ctx = context.WithValue(ctx, ctxKey, allowed) 136 ctx = context.WithValue(ctx, ctxEnabled, true) 137 ctx = context.WithValue(ctx, ctxErrorType, TypeTimeout) 138 cbMW(invoke)(ctx, nil, nil) 139 test.Assert(t, mp.timeoutRecorded) 140 // test failure 141 mp = &mockPanel{Panel: panel} 142 cbMW = NewCircuitBreakerMW(ctl, mp) 143 ctx = context.Background() 144 ctx = context.WithValue(ctx, ctxKey, allowed) 145 ctx = context.WithValue(ctx, ctxEnabled, true) 146 ctx = context.WithValue(ctx, ctxErrorType, TypeFailure) 147 cbMW(invoke)(ctx, nil, nil) 148 test.Assert(t, mp.failRecorded) 149 // test success 150 mp = &mockPanel{Panel: panel} 151 cbMW = NewCircuitBreakerMW(ctl, mp) 152 ctx = context.Background() 153 ctx = context.WithValue(ctx, ctxKey, allowed) 154 ctx = context.WithValue(ctx, ctxEnabled, true) 155 ctx = context.WithValue(ctx, ctxErrorType, TypeSuccess) 156 cbMW(invoke)(ctx, nil, nil) 157 test.Assert(t, mp.succeedRecorded) 158 } 159 160 func TestErrorType(t *testing.T) { 161 err1 := kerrors.ErrRPCTimeout 162 wrapErr := WrapErrorWithType(err1, TypeIgnorable) 163 test.Assert(t, errors.Is(wrapErr, kerrors.ErrRPCTimeout)) 164 test.Assert(t, errors.Unwrap(wrapErr) == err1) 165 test.Assert(t, wrapErr.TypeForCircuitBreaker() == TypeIgnorable) 166 167 err2 := kerrors.ErrRemoteOrNetwork.WithCause(errors.New("mock")) 168 wrapErr = WrapErrorWithType(err2, TypeIgnorable) 169 test.Assert(t, errors.Is(wrapErr, kerrors.ErrRemoteOrNetwork)) 170 test.Assert(t, errors.Is(errors.Unwrap(wrapErr), kerrors.ErrRemoteOrNetwork)) 171 test.Assert(t, wrapErr.TypeForCircuitBreaker() == TypeIgnorable) 172 173 err3 := kerrors.ErrRemoteOrNetwork.WithCause(wrapErr) 174 var e *errorWrapperWithType 175 ok := errors.As(err3, &e) 176 test.Assert(t, ok) 177 test.Assert(t, e.TypeForCircuitBreaker() == TypeIgnorable) 178 }