github.com/lingyao2333/mo-zero@v1.4.1/core/breaker/googlebreaker_test.go (about)

     1  package breaker
     2  
     3  import (
     4  	"errors"
     5  	"math/rand"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/lingyao2333/mo-zero/core/collection"
    10  	"github.com/lingyao2333/mo-zero/core/mathx"
    11  	"github.com/lingyao2333/mo-zero/core/stat"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  const (
    16  	testBuckets  = 10
    17  	testInterval = time.Millisecond * 10
    18  )
    19  
    20  func init() {
    21  	stat.SetReporter(nil)
    22  }
    23  
    24  func getGoogleBreaker() *googleBreaker {
    25  	st := collection.NewRollingWindow(testBuckets, testInterval)
    26  	return &googleBreaker{
    27  		stat:  st,
    28  		k:     5,
    29  		proba: mathx.NewProba(),
    30  	}
    31  }
    32  
    33  func markSuccessWithDuration(b *googleBreaker, count int, sleep time.Duration) {
    34  	for i := 0; i < count; i++ {
    35  		b.markSuccess()
    36  		time.Sleep(sleep)
    37  	}
    38  }
    39  
    40  func markFailedWithDuration(b *googleBreaker, count int, sleep time.Duration) {
    41  	for i := 0; i < count; i++ {
    42  		b.markFailure()
    43  		time.Sleep(sleep)
    44  	}
    45  }
    46  
    47  func TestGoogleBreakerClose(t *testing.T) {
    48  	b := getGoogleBreaker()
    49  	markSuccess(b, 80)
    50  	assert.Nil(t, b.accept())
    51  	markSuccess(b, 120)
    52  	assert.Nil(t, b.accept())
    53  }
    54  
    55  func TestGoogleBreakerOpen(t *testing.T) {
    56  	b := getGoogleBreaker()
    57  	markSuccess(b, 10)
    58  	assert.Nil(t, b.accept())
    59  	markFailed(b, 100000)
    60  	time.Sleep(testInterval * 2)
    61  	verify(t, func() bool {
    62  		return b.accept() != nil
    63  	})
    64  }
    65  
    66  func TestGoogleBreakerFallback(t *testing.T) {
    67  	b := getGoogleBreaker()
    68  	markSuccess(b, 1)
    69  	assert.Nil(t, b.accept())
    70  	markFailed(b, 10000)
    71  	time.Sleep(testInterval * 2)
    72  	verify(t, func() bool {
    73  		return b.doReq(func() error {
    74  			return errors.New("any")
    75  		}, func(err error) error {
    76  			return nil
    77  		}, defaultAcceptable) == nil
    78  	})
    79  }
    80  
    81  func TestGoogleBreakerReject(t *testing.T) {
    82  	b := getGoogleBreaker()
    83  	markSuccess(b, 100)
    84  	assert.Nil(t, b.accept())
    85  	markFailed(b, 10000)
    86  	time.Sleep(testInterval)
    87  	assert.Equal(t, ErrServiceUnavailable, b.doReq(func() error {
    88  		return ErrServiceUnavailable
    89  	}, nil, defaultAcceptable))
    90  }
    91  
    92  func TestGoogleBreakerAcceptable(t *testing.T) {
    93  	b := getGoogleBreaker()
    94  	errAcceptable := errors.New("any")
    95  	assert.Equal(t, errAcceptable, b.doReq(func() error {
    96  		return errAcceptable
    97  	}, nil, func(err error) bool {
    98  		return err == errAcceptable
    99  	}))
   100  }
   101  
   102  func TestGoogleBreakerNotAcceptable(t *testing.T) {
   103  	b := getGoogleBreaker()
   104  	errAcceptable := errors.New("any")
   105  	assert.Equal(t, errAcceptable, b.doReq(func() error {
   106  		return errAcceptable
   107  	}, nil, func(err error) bool {
   108  		return err != errAcceptable
   109  	}))
   110  }
   111  
   112  func TestGoogleBreakerPanic(t *testing.T) {
   113  	b := getGoogleBreaker()
   114  	assert.Panics(t, func() {
   115  		_ = b.doReq(func() error {
   116  			panic("fail")
   117  		}, nil, defaultAcceptable)
   118  	})
   119  }
   120  
   121  func TestGoogleBreakerHalfOpen(t *testing.T) {
   122  	b := getGoogleBreaker()
   123  	assert.Nil(t, b.accept())
   124  	t.Run("accept single failed/accept", func(t *testing.T) {
   125  		markFailed(b, 10000)
   126  		time.Sleep(testInterval * 2)
   127  		verify(t, func() bool {
   128  			return b.accept() != nil
   129  		})
   130  	})
   131  	t.Run("accept single failed/allow", func(t *testing.T) {
   132  		markFailed(b, 10000)
   133  		time.Sleep(testInterval * 2)
   134  		verify(t, func() bool {
   135  			_, err := b.allow()
   136  			return err != nil
   137  		})
   138  	})
   139  	time.Sleep(testInterval * testBuckets)
   140  	t.Run("accept single succeed", func(t *testing.T) {
   141  		assert.Nil(t, b.accept())
   142  		markSuccess(b, 10000)
   143  		verify(t, func() bool {
   144  			return b.accept() == nil
   145  		})
   146  	})
   147  }
   148  
   149  func TestGoogleBreakerSelfProtection(t *testing.T) {
   150  	t.Run("total request < 100", func(t *testing.T) {
   151  		b := getGoogleBreaker()
   152  		markFailed(b, 4)
   153  		time.Sleep(testInterval)
   154  		assert.Nil(t, b.accept())
   155  	})
   156  	t.Run("total request > 100, total < 2 * success", func(t *testing.T) {
   157  		b := getGoogleBreaker()
   158  		size := rand.Intn(10000)
   159  		accepts := size + 1
   160  		markSuccess(b, accepts)
   161  		markFailed(b, size-accepts)
   162  		assert.Nil(t, b.accept())
   163  	})
   164  }
   165  
   166  func TestGoogleBreakerHistory(t *testing.T) {
   167  	var b *googleBreaker
   168  	var accepts, total int64
   169  
   170  	sleep := testInterval
   171  	t.Run("accepts == total", func(t *testing.T) {
   172  		b = getGoogleBreaker()
   173  		markSuccessWithDuration(b, 10, sleep/2)
   174  		accepts, total = b.history()
   175  		assert.Equal(t, int64(10), accepts)
   176  		assert.Equal(t, int64(10), total)
   177  	})
   178  
   179  	t.Run("fail == total", func(t *testing.T) {
   180  		b = getGoogleBreaker()
   181  		markFailedWithDuration(b, 10, sleep/2)
   182  		accepts, total = b.history()
   183  		assert.Equal(t, int64(0), accepts)
   184  		assert.Equal(t, int64(10), total)
   185  	})
   186  
   187  	t.Run("accepts = 1/2 * total, fail = 1/2 * total", func(t *testing.T) {
   188  		b = getGoogleBreaker()
   189  		markFailedWithDuration(b, 5, sleep/2)
   190  		markSuccessWithDuration(b, 5, sleep/2)
   191  		accepts, total = b.history()
   192  		assert.Equal(t, int64(5), accepts)
   193  		assert.Equal(t, int64(10), total)
   194  	})
   195  
   196  	t.Run("auto reset rolling counter", func(t *testing.T) {
   197  		b = getGoogleBreaker()
   198  		time.Sleep(testInterval * testBuckets)
   199  		accepts, total = b.history()
   200  		assert.Equal(t, int64(0), accepts)
   201  		assert.Equal(t, int64(0), total)
   202  	})
   203  }
   204  
   205  func BenchmarkGoogleBreakerAllow(b *testing.B) {
   206  	breaker := getGoogleBreaker()
   207  	b.ResetTimer()
   208  	for i := 0; i <= b.N; i++ {
   209  		breaker.accept()
   210  		if i%2 == 0 {
   211  			breaker.markSuccess()
   212  		} else {
   213  			breaker.markFailure()
   214  		}
   215  	}
   216  }
   217  
   218  func markSuccess(b *googleBreaker, count int) {
   219  	for i := 0; i < count; i++ {
   220  		p, err := b.allow()
   221  		if err != nil {
   222  			break
   223  		}
   224  		p.Accept()
   225  	}
   226  }
   227  
   228  func markFailed(b *googleBreaker, count int) {
   229  	for i := 0; i < count; i++ {
   230  		p, err := b.allow()
   231  		if err == nil {
   232  			p.Reject()
   233  		}
   234  	}
   235  }