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  }