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 }