github.com/blend/go-sdk@v1.20220411.3/breaker/breaker_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package breaker 9 10 import ( 11 "context" 12 "fmt" 13 "testing" 14 "time" 15 16 "github.com/blend/go-sdk/assert" 17 "github.com/blend/go-sdk/ex" 18 ) 19 20 func TestNew(t *testing.T) { 21 its := assert.New(t) 22 23 b := New() 24 its.Equal(DefaultHalfOpenMaxActions, b.HalfOpenMaxActions) 25 its.Equal(DefaultOpenExpiryInterval, b.OpenExpiryInterval) 26 its.Equal(DefaultClosedExpiryInterval, b.ClosedExpiryInterval) 27 } 28 29 func TestNewOptions(t *testing.T) { 30 its := assert.New(t) 31 32 b := New( 33 OptHalfOpenMaxActions(5), 34 OptOpenExpiryInterval(10*time.Second), 35 OptClosedExpiryInterval(20*time.Second), 36 ) 37 its.Equal(5, b.HalfOpenMaxActions) 38 its.Equal(10*time.Second, b.OpenExpiryInterval) 39 its.Equal(20*time.Second, b.ClosedExpiryInterval) 40 } 41 42 func createTestBreaker() *Breaker { 43 return New(OptClosedExpiryInterval(0)) 44 } 45 46 func succeed(b *Breaker) error { 47 _, err := b.Intercept(ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 48 return nil, nil 49 })).Action(context.Background(), nil) 50 return err 51 } 52 53 func fail(b *Breaker) error { 54 msg := "fail" 55 _, err := b.Intercept(ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 56 return nil, fmt.Errorf(msg) 57 })).Action(context.Background(), nil) 58 if err != nil && err.Error() == msg { 59 return nil 60 } 61 return err 62 } 63 64 func pseudoSleep(b *Breaker, period time.Duration) { 65 if !b.stateExpiresAt.IsZero() { 66 b.stateExpiresAt = b.stateExpiresAt.Add(-period) 67 } 68 } 69 70 func Test_Breaker(t *testing.T) { 71 its := assert.New(t) 72 ctx := context.Background() 73 74 b := createTestBreaker() 75 76 for i := 0; i < 5; i++ { 77 its.Nil(fail(b)) 78 } 79 its.Equal(StateClosed, b.EvaluateState(ctx)) 80 its.Equal(Counts{5, 0, 5, 0, 5}, b.Counts) 81 82 its.Nil(succeed(b)) 83 its.Equal(StateClosed, b.EvaluateState(ctx)) 84 its.Equal(Counts{6, 1, 5, 1, 0}, b.Counts) 85 86 its.Nil(fail(b)) 87 its.Equal(StateClosed, b.EvaluateState(ctx)) 88 its.Equal(Counts{7, 1, 6, 0, 1}, b.Counts) 89 90 // StateClosed to StateOpen 91 for i := 0; i < 5; i++ { 92 its.Nil(fail(b)) // 5 more consecutive failures 93 } 94 its.Equal(StateOpen, b.EvaluateState(ctx)) 95 its.Equal(Counts{0, 0, 0, 0, 0}, b.Counts) 96 its.False(b.stateExpiresAt.IsZero()) 97 98 err := succeed(b) 99 its.True(ErrIsOpen(err)) // this shouldn't have called the action, should yield closed 100 err = fail(b) 101 its.True(ErrIsOpen(err)) // this shouldn't have called the action either 102 its.Equal(Counts{0, 0, 0, 0, 0}, b.Counts) 103 104 pseudoSleep(b, 59*time.Second) // push forward time by 59s 105 its.Equal(StateOpen, b.EvaluateState(ctx)) 106 107 // StateOpen to StateHalfOpen 108 pseudoSleep(b, 2*time.Second) // over Timeout 109 its.Equal(StateHalfOpen, b.EvaluateState(ctx)) 110 its.True(b.stateExpiresAt.IsZero()) 111 112 // StateHalfOpen to StateOpen 113 // there are like, (3) calls queued here 114 its.Nil(fail(b)) 115 its.Equal(StateOpen, b.EvaluateState(ctx)) 116 its.Equal(Counts{0, 0, 0, 0, 0}, b.Counts) 117 its.False(b.stateExpiresAt.IsZero()) 118 119 // StateOpen to StateHalfOpen 120 pseudoSleep(b, time.Duration(60)*time.Second) 121 its.Equal(StateHalfOpen, b.EvaluateState(ctx)) 122 its.True(b.stateExpiresAt.IsZero()) 123 124 // StateHalfOpen to StateClosed 125 its.Nil(succeed(b)) 126 its.Equal(StateClosed, b.EvaluateState(ctx)) 127 its.Equal(Counts{0, 0, 0, 0, 0}, b.Counts) 128 its.True(b.stateExpiresAt.IsZero()) 129 } 130 131 func Test_Breaker_ErrStateOpen(t *testing.T) { 132 its := assert.New(t) 133 134 var didCall bool 135 b := New() 136 b.state = StateOpen 137 b.stateExpiresAt = time.Now().Add(time.Hour) 138 _, err := b.Intercept(ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 139 didCall = true 140 return nil, nil 141 })).Action(context.Background(), nil) 142 its.True(ex.Is(err, ErrOpenState), fmt.Sprintf("%v", err)) 143 its.False(didCall) 144 } 145 146 func Test_Breaker_ErrTooManyRequests(t *testing.T) { 147 its := assert.New(t) 148 149 var didCall bool 150 b := New() 151 152 b.state = StateHalfOpen 153 b.Counts.Requests = 10 154 b.HalfOpenMaxActions = 5 155 156 _, err := b.Intercept(ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 157 didCall = true 158 return nil, nil 159 })).Action(context.Background(), nil) 160 its.True(ex.Is(err, ErrTooManyRequests)) 161 its.False(didCall) 162 } 163 164 func Test_Breaker_callsOnOpenHandler(t *testing.T) { 165 its := assert.New(t) 166 167 var didCall, didCallOpen bool 168 b := New( 169 OptOpenAction(ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 170 didCallOpen = true 171 return "on open", nil 172 })), 173 ) 174 175 b.state = StateOpen 176 b.stateExpiresAt = time.Now().Add(time.Hour) 177 178 res, err := b.Intercept(ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 179 didCall = true 180 return nil, nil 181 })).Action(context.Background(), nil) 182 its.Nil(err) 183 its.False(didCall) 184 its.True(didCallOpen) 185 its.Equal("on open", res) 186 }