github.com/andy2046/gopie@v0.7.0/pkg/breaker/breaker_test.go (about)

     1  package breaker
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  var (
    10  	defaultCB *CircuitBreaker
    11  	customCB  *CircuitBreaker
    12  )
    13  
    14  func init() {
    15  	defaultCB = New()
    16  	customCB = newCustom()
    17  }
    18  
    19  func sleep(cb *CircuitBreaker, period time.Duration) {
    20  	if !cb.expiry.IsZero() {
    21  		cb.expiry = cb.expiry.Add(-period)
    22  	}
    23  }
    24  
    25  func succeed(cb *CircuitBreaker) error {
    26  	_, err := cb.Execute(func() (interface{}, error) { return nil, nil })
    27  	return err
    28  }
    29  
    30  func fail(cb *CircuitBreaker) error {
    31  	msg := "fail"
    32  	_, err := cb.Execute(func() (interface{}, error) { return nil, fmt.Errorf(msg) })
    33  	if err.Error() == msg {
    34  		return nil
    35  	}
    36  	return err
    37  }
    38  
    39  func succeedLater(cb *CircuitBreaker, delay time.Duration) <-chan error {
    40  	ch := make(chan error)
    41  	go func() {
    42  		_, err := cb.Execute(func() (interface{}, error) {
    43  			time.Sleep(delay)
    44  			return nil, nil
    45  		})
    46  		ch <- err
    47  	}()
    48  	return ch
    49  }
    50  
    51  func newCustom() *CircuitBreaker {
    52  	opt := func(st *Settings) error {
    53  		st.Name = "breaker"
    54  		st.MaxRequests = 3
    55  		st.Interval = 30 * time.Second
    56  		st.Timeout = 90 * time.Second
    57  		st.ShouldTrip = func(counts Counts) bool {
    58  			return counts.Requests == 3 && counts.TotalFailures == 2
    59  		}
    60  		return nil
    61  	}
    62  
    63  	return New(opt)
    64  }
    65  
    66  func assert(t *testing.T) func(bool) {
    67  	return func(equal bool) {
    68  		if !equal {
    69  			t.Error("fail to assert")
    70  		}
    71  	}
    72  }
    73  
    74  func TestDefaultCircuitBreaker(t *testing.T) {
    75  	eq := assert(t)
    76  	eq("CircuitBreaker" == defaultCB.Name())
    77  
    78  	for i := 0; i < 5; i++ {
    79  		eq(nil == fail(defaultCB))
    80  	}
    81  	eq(StateClosed == defaultCB.State())
    82  	eq("{Requests:5 TotalSuccesses:0 TotalFailures:5 ConsecutiveSuccesses:0 ConsecutiveFailures:5}" ==
    83  		fmt.Sprintf("%+v", defaultCB.counts))
    84  	eq(nil == succeed(defaultCB))
    85  	eq(StateClosed == defaultCB.State())
    86  	eq("{Requests:6 TotalSuccesses:1 TotalFailures:5 ConsecutiveSuccesses:1 ConsecutiveFailures:0}" ==
    87  		fmt.Sprintf("%+v", defaultCB.counts))
    88  
    89  	eq(nil == fail(defaultCB))
    90  	eq(StateClosed == defaultCB.State())
    91  	eq("{Requests:7 TotalSuccesses:1 TotalFailures:6 ConsecutiveSuccesses:0 ConsecutiveFailures:1}" ==
    92  		fmt.Sprintf("%+v", defaultCB.counts))
    93  
    94  	// StateClosed to StateOpen
    95  	for i := 0; i < 5; i++ {
    96  		eq(nil == fail(defaultCB)) // 6 consecutive failures
    97  	}
    98  	eq(StateOpen == defaultCB.State())
    99  	eq("{Requests:0 TotalSuccesses:0 TotalFailures:0 ConsecutiveSuccesses:0 ConsecutiveFailures:0}" ==
   100  		fmt.Sprintf("%+v", defaultCB.counts))
   101  	eq(false == defaultCB.expiry.IsZero())
   102  
   103  	eq(nil != succeed(defaultCB))
   104  	eq(nil != fail(defaultCB))
   105  	eq("{Requests:0 TotalSuccesses:0 TotalFailures:0 ConsecutiveSuccesses:0 ConsecutiveFailures:0}" ==
   106  		fmt.Sprintf("%+v", defaultCB.counts))
   107  
   108  	sleep(defaultCB, 59*time.Second)
   109  	eq(StateOpen == defaultCB.State())
   110  
   111  	// StateOpen to StateHalfOpen
   112  	sleep(defaultCB, 1*time.Second) // over Timeout 60 seconds
   113  	eq(StateHalfOpen == defaultCB.State())
   114  	eq(true == defaultCB.expiry.IsZero())
   115  
   116  	// StateHalfOpen to StateOpen
   117  	eq(nil == fail(defaultCB))
   118  	eq(StateOpen == defaultCB.State())
   119  	eq("{Requests:0 TotalSuccesses:0 TotalFailures:0 ConsecutiveSuccesses:0 ConsecutiveFailures:0}" ==
   120  		fmt.Sprintf("%+v", defaultCB.counts))
   121  	eq(false == defaultCB.expiry.IsZero())
   122  
   123  	// StateOpen to StateHalfOpen
   124  	sleep(defaultCB, 60*time.Second)
   125  	eq(StateHalfOpen == defaultCB.State())
   126  	eq(true == defaultCB.expiry.IsZero())
   127  
   128  	// StateHalfOpen to StateClosed
   129  	eq(nil == succeed(defaultCB))
   130  	eq(StateClosed == defaultCB.State())
   131  	eq("{Requests:0 TotalSuccesses:0 TotalFailures:0 ConsecutiveSuccesses:0 ConsecutiveFailures:0}" ==
   132  		fmt.Sprintf("%+v", defaultCB.counts))
   133  	eq(true == defaultCB.expiry.IsZero())
   134  }
   135  
   136  func TestCustomCircuitBreaker(t *testing.T) {
   137  	eq := assert(t)
   138  	eq("breaker" == customCB.Name())
   139  
   140  	for i := 0; i < 5; i++ {
   141  		eq(nil == succeed(customCB))
   142  		eq(nil == fail(customCB))
   143  	}
   144  	eq(StateClosed == customCB.State())
   145  	eq("{Requests:10 TotalSuccesses:5 TotalFailures:5 ConsecutiveSuccesses:0 ConsecutiveFailures:1}" ==
   146  		fmt.Sprintf("%+v", customCB.counts))
   147  
   148  	sleep(customCB, 29*time.Second)
   149  	eq(nil == succeed(customCB))
   150  	eq(StateClosed == customCB.State())
   151  	eq("{Requests:11 TotalSuccesses:6 TotalFailures:5 ConsecutiveSuccesses:1 ConsecutiveFailures:0}" ==
   152  		fmt.Sprintf("%+v", customCB.counts))
   153  
   154  	sleep(customCB, 1*time.Second) // over Interval
   155  	eq(nil == fail(customCB))
   156  	eq(StateClosed == customCB.State())
   157  	eq("{Requests:1 TotalSuccesses:0 TotalFailures:1 ConsecutiveSuccesses:0 ConsecutiveFailures:1}" ==
   158  		fmt.Sprintf("%+v", customCB.counts))
   159  
   160  	// StateClosed to StateOpen
   161  	eq(nil == succeed(customCB))
   162  	eq(nil == fail(customCB)) // ShouldTrip triggered
   163  	eq(StateOpen == customCB.State())
   164  	eq("{Requests:0 TotalSuccesses:0 TotalFailures:0 ConsecutiveSuccesses:0 ConsecutiveFailures:0}" ==
   165  		fmt.Sprintf("%+v", customCB.counts))
   166  	eq(false == customCB.expiry.IsZero())
   167  
   168  	// StateOpen to StateHalfOpen
   169  	sleep(customCB, 90*time.Second)
   170  	eq(StateHalfOpen == customCB.State())
   171  	eq(true == customCB.expiry.IsZero())
   172  
   173  	eq(nil == succeed(customCB))
   174  	eq(nil == succeed(customCB))
   175  	eq(StateHalfOpen == customCB.State())
   176  	eq("{Requests:2 TotalSuccesses:2 TotalFailures:0 ConsecutiveSuccesses:2 ConsecutiveFailures:0}" ==
   177  		fmt.Sprintf("%+v", customCB.counts))
   178  
   179  	// StateHalfOpen to StateClosed
   180  	ch := succeedLater(customCB, 100*time.Millisecond) // 3 consecutive successes
   181  	time.Sleep(50 * time.Millisecond)
   182  	customCB.mutex.Lock()
   183  	eq("{Requests:3 TotalSuccesses:2 TotalFailures:0 ConsecutiveSuccesses:2 ConsecutiveFailures:0}" ==
   184  		fmt.Sprintf("%+v", customCB.counts))
   185  	customCB.mutex.Unlock()
   186  	eq(nil != succeed(customCB)) // over MaxRequests
   187  	eq(nil == <-ch)
   188  	eq(StateClosed == customCB.State())
   189  	eq("{Requests:0 TotalSuccesses:0 TotalFailures:0 ConsecutiveSuccesses:0 ConsecutiveFailures:0}" ==
   190  		fmt.Sprintf("%+v", customCB.counts))
   191  	eq(false == customCB.expiry.IsZero())
   192  }