Package list golang-github-go-kit-kit / 8317f15
Unify and minimize breaker tests Peter Bourgon 6 years ago
5 changed file(s) with 93 addition(s) and 220 deletion(s). Raw diff Collapse all Expand all
00 package circuitbreaker_test
11
22 import (
3 "errors"
43 "testing"
5 "time"
64
75 "github.com/sony/gobreaker"
8 "golang.org/x/net/context"
96
107 "github.com/go-kit/kit/circuitbreaker"
11 "github.com/go-kit/kit/endpoint"
128 )
139
1410 func TestGobreaker(t *testing.T) {
1511 var (
16 thru int
17 last gobreaker.State
18 myError = errors.New("❤️")
19 timeout = time.Millisecond
20 stateChange = func(_ string, from, to gobreaker.State) { last = to }
12 breaker = circuitbreaker.Gobreaker(gobreaker.Settings{})
13 primeWith = 100
14 shouldPass = func(n int) bool { return n <= 5 } // https://github.com/sony/gobreaker/blob/bfa846d/gobreaker.go#L76
15 circuitOpenError = "circuit breaker is open"
2116 )
22
23 var e endpoint.Endpoint
24 e = func(context.Context, interface{}) (interface{}, error) { thru++; return struct{}{}, myError }
25 e = circuitbreaker.Gobreaker(gobreaker.Settings{
26 Timeout: timeout,
27 OnStateChange: stateChange,
28 })(e)
29
30 // "Default ReadyToTrip returns true when the number of consecutive
31 // failures is more than 5."
32 // https://github.com/sony/gobreaker/blob/bfa846d/gobreaker.go#L76
33 for i := 0; i < 5; i++ {
34 if _, err := e(context.Background(), struct{}{}); err != myError {
35 t.Errorf("want %v, have %v", myError, err)
36 }
37 }
38
39 if want, have := 5, thru; want != have {
40 t.Errorf("want %d, have %d", want, have)
41 }
42
43 e(context.Background(), struct{}{})
44 if want, have := 6, thru; want != have { // got thru
45 t.Errorf("want %d, have %d", want, have)
46 }
47 if want, have := gobreaker.StateOpen, last; want != have { // tripped
48 t.Errorf("want %v, have %v", want, have)
49 }
50
51 e(context.Background(), struct{}{})
52 if want, have := 6, thru; want != have { // didn't get thru
53 t.Errorf("want %d, have %d", want, have)
54 }
55
56 time.Sleep(2 * timeout)
57
58 e(context.Background(), struct{}{})
59 if want, have := 7, thru; want != have { // got thru via halfopen
60 t.Errorf("want %d, have %d", want, have)
61 }
62 if want, have := gobreaker.StateOpen, last; want != have { // re-tripped
63 t.Errorf("want %v, have %v", want, have)
64 }
65
66 time.Sleep(2 * timeout)
67
68 myError = nil
69 e(context.Background(), struct{}{})
70 if want, have := 8, thru; want != have { // got thru via halfopen
71 t.Errorf("want %d, have %d", want, have)
72 }
73 if want, have := gobreaker.StateClosed, last; want != have { // now it's good
74 t.Errorf("want %v, have %v", want, have)
75 }
17 testFailingEndpoint(t, breaker, primeWith, shouldPass, circuitOpenError)
7618 }
00 package circuitbreaker
11
22 import (
3 "errors"
43 "time"
54
65 "github.com/streadway/handy/breaker"
87
98 "github.com/go-kit/kit/endpoint"
109 )
11
12 // ErrCircuitBreakerOpen is returned when the HandyBreaker's circuit is open
13 // and the request is stopped from proceeding.
14 var ErrCircuitBreakerOpen = errors.New("circuit breaker open")
1510
1611 // HandyBreaker returns an endpoint.Middleware that implements the circuit
1712 // breaker pattern using the streadway/handy/breaker package. Only errors
2520 return func(next endpoint.Endpoint) endpoint.Endpoint {
2621 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
2722 if !b.Allow() {
28 return nil, ErrCircuitBreakerOpen
23 return nil, breaker.ErrCircuitOpen
2924 }
3025
3126 defer func(begin time.Time) {
00 package circuitbreaker_test
11
22 import (
3 "errors"
43 "testing"
54
6 "github.com/streadway/handy/breaker"
7
8 "golang.org/x/net/context"
5 handybreaker "github.com/streadway/handy/breaker"
96
107 "github.com/go-kit/kit/circuitbreaker"
11 "github.com/go-kit/kit/endpoint"
128 )
139
1410 func TestHandyBreaker(t *testing.T) {
1511 var (
16 thru = 0
17 myError = error(nil)
18 ratio = 0.05
19 primeWith = breaker.DefaultMinObservations * 10
20 shouldPass = func(failed int) bool { return (float64(failed) / float64(primeWith+failed)) <= ratio }
21 extraTries = 10
12 failureRatio = 0.05
13 breaker = circuitbreaker.HandyBreaker(failureRatio)
14 primeWith = handybreaker.DefaultMinObservations * 10
15 shouldPass = func(n int) bool { return (float64(n) / float64(primeWith+n)) <= failureRatio }
16 openCircuitError = handybreaker.ErrCircuitOpen.Error()
2217 )
23
24 var e endpoint.Endpoint
25 e = func(context.Context, interface{}) (interface{}, error) { thru++; return struct{}{}, myError }
26 e = circuitbreaker.HandyBreaker(ratio)(e)
27
28 // Prime with some successes.
29 for i := 0; i < primeWith; i++ {
30 if _, err := e(context.Background(), struct{}{}); err != nil {
31 t.Fatal(err)
32 }
33 }
34
35 // Now we start throwing errors.
36 myError = errors.New(":(")
37
38 // The first few should get thru.
39 var letThru int
40 for i := 0; shouldPass(i); i++ { // off-by-one
41 letThru++
42 if _, err := e(context.Background(), struct{}{}); err != myError {
43 t.Fatalf("want %v, have %v", myError, err)
44 }
45 }
46
47 // But the rest should be blocked by an open circuit.
48 for i := 1; i <= extraTries; i++ {
49 if _, err := e(context.Background(), struct{}{}); err != circuitbreaker.ErrCircuitBreakerOpen {
50 t.Errorf("with request #%d, want %v, have %v", primeWith+letThru+i, circuitbreaker.ErrCircuitBreakerOpen, err)
51 }
52 }
53
54 // Confirm the rest didn't get through.
55 if want, have := primeWith+letThru, thru; want != have {
56 t.Errorf("want %d, have %d", want, have)
57 }
18 testFailingEndpoint(t, breaker, primeWith, shouldPass, openCircuitError)
5819 }
00 package circuitbreaker_test
11
22 import (
3 "errors"
3 stdlog "log"
4 "os"
45 "testing"
5 "time"
66
77 "github.com/afex/hystrix-go/hystrix"
8 "golang.org/x/net/context"
98
109 "github.com/go-kit/kit/circuitbreaker"
11 "github.com/go-kit/kit/endpoint"
10 kitlog "github.com/go-kit/kit/log"
1211 )
1312
14 func TestHystrixCircuitBreakerOpen(t *testing.T) {
15 var (
16 thru = 0
17 myError = error(nil)
18 ratio = 0.04
19 primeWith = hystrix.DefaultVolumeThreshold * 2
20 shouldPass = func(failed int) bool { return (float64(failed) / float64(primeWith+failed)) <= ratio }
21 extraTries = 10
13 func TestHystrix(t *testing.T) {
14 logger := kitlog.NewLogfmtLogger(os.Stderr)
15 stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
16
17 const (
18 commandName = "my-endpoint"
19 errorPercent = 5
20 maxConcurrent = 1000
2221 )
23
24 // configure hystrix
25 hystrix.ConfigureCommand("myEndpoint", hystrix.CommandConfig{
26 ErrorPercentThreshold: 5,
27 MaxConcurrentRequests: 200,
22 hystrix.ConfigureCommand(commandName, hystrix.CommandConfig{
23 ErrorPercentThreshold: errorPercent,
24 MaxConcurrentRequests: maxConcurrent,
2825 })
2926
30 var e endpoint.Endpoint
31 e = func(context.Context, interface{}) (interface{}, error) { thru++; return struct{}{}, myError }
32 e = circuitbreaker.Hystrix("myEndpoint")(e)
33
34 // prime
35 for i := 0; i < primeWith; i++ {
36 if _, err := e(context.Background(), struct{}{}); err != nil {
37 t.Fatal(err)
38 }
39 }
40
41 // Now we start throwing errors.
42 myError = errors.New(":(")
43
44 // The first few should get thru.
45 var letThru int
46 for i := 0; shouldPass(i); i++ { // off-by-one
47 letThru++
48 if _, err := e(context.Background(), struct{}{}); err != myError {
49 t.Fatalf("want %v, have %v", myError, err)
50 }
51 }
52
53 // But the rest should be blocked by an open circuit.
54 for i := 1; i <= extraTries; i++ {
55 if _, err := e(context.Background(), struct{}{}); err != hystrix.ErrCircuitOpen {
56 t.Errorf("with request #%d, want %v, have %v", primeWith+letThru+i, hystrix.ErrCircuitOpen, err)
57 }
58 }
59
60 // Confirm the rest didn't get through.
61 if want, have := primeWith+letThru, thru; want != have {
62 t.Errorf("want %d, have %d", want, have)
63 }
27 var (
28 breaker = circuitbreaker.Hystrix(commandName)
29 primeWith = hystrix.DefaultVolumeThreshold * 2
30 shouldPass = func(n int) bool { return (float64(n) / float64(primeWith+n)) <= (float64(errorPercent-1) / 100.0) }
31 openCircuitError = hystrix.ErrCircuitOpen.Error()
32 )
33 testFailingEndpoint(t, breaker, primeWith, shouldPass, openCircuitError)
6434 }
65
66 func TestHystrixTimeout(t *testing.T) {
67 var (
68 timeout = time.Millisecond * 0
69 primeWith = hystrix.DefaultVolumeThreshold * 2
70 failNumber = 2 // 5% threshold
71 )
72
73 // configure hystrix
74 hystrix.ConfigureCommand("timeoutEndpoint", hystrix.CommandConfig{
75 ErrorPercentThreshold: 5,
76 MaxConcurrentRequests: 200,
77 SleepWindow: 5, // milliseconds
78 Timeout: 1, // milliseconds
79 })
80
81 var e endpoint.Endpoint
82 e = func(context.Context, interface{}) (interface{}, error) {
83 time.Sleep(2 * timeout)
84 return struct{}{}, nil
85 }
86 e = circuitbreaker.Hystrix("timeoutEndpoint")(e)
87
88 // prime
89 for i := 0; i < primeWith; i++ {
90 if _, err := e(context.Background(), struct{}{}); err != nil {
91 t.Errorf("expecting %v, have %v", nil, err)
92 }
93 }
94
95 // times out
96 timeout = time.Millisecond * 2
97 for i := 0; i < failNumber; i++ {
98 if _, err := e(context.Background(), struct{}{}); err != hystrix.ErrTimeout {
99 t.Errorf("%d expecting %v, have %v", i, hystrix.ErrTimeout, err)
100 }
101 }
102
103 // fix timeout
104 timeout = time.Millisecond * 0
105
106 // fails for a little while still
107 for i := 0; i < failNumber; i++ {
108 if _, err := e(context.Background(), struct{}{}); err != hystrix.ErrCircuitOpen {
109 t.Errorf("expecting %v, have %v", hystrix.ErrCircuitOpen, err)
110 }
111 }
112
113 // back to OK
114 time.Sleep(time.Millisecond * 5)
115 if _, err := e(context.Background(), struct{}{}); err != nil {
116 t.Errorf("expecting %v, have %v", nil, err)
117 }
118 }
0 package circuitbreaker_test
1
2 import (
3 "errors"
4 "testing"
5
6 "golang.org/x/net/context"
7
8 "github.com/go-kit/kit/endpoint"
9 )
10
11 func testFailingEndpoint(t *testing.T, breaker endpoint.Middleware, primeWith int, shouldPass func(int) bool, openCircuitError string) {
12 // Create a mock endpoint and wrap it with the breaker.
13 m := mock{}
14 var e endpoint.Endpoint
15 e = m.endpoint
16 e = breaker(e)
17
18 // Prime the endpoint with successful requests.
19 for i := 0; i < primeWith; i++ {
20 if _, err := e(context.Background(), struct{}{}); err != nil {
21 t.Fatalf("during priming, got error: %v", err)
22 }
23 }
24
25 // Switch the endpoint to start throwing errors.
26 m.err = errors.New("tragedy+disaster")
27 m.thru = 0
28
29 // The first several should be allowed through and yield our error.
30 for i := 0; shouldPass(i); i++ {
31 if _, err := e(context.Background(), struct{}{}); err != m.err {
32 t.Fatalf("want %v, have %v", m.err, err)
33 }
34 }
35 thru := m.thru
36
37 // But the rest should be blocked by an open circuit.
38 for i := 0; i < 10; i++ {
39 if _, err := e(context.Background(), struct{}{}); err.Error() != openCircuitError {
40 t.Fatalf("want %q, have %q", openCircuitError, err.Error())
41 }
42 }
43
44 // Make sure none of those got through.
45 if want, have := thru, m.thru; want != have {
46 t.Errorf("want %d, have %d", want, have)
47 }
48 }
49
50 type mock struct {
51 thru int
52 err error
53 }
54
55 func (m *mock) endpoint(context.Context, interface{}) (interface{}, error) {
56 m.thru++
57 return struct{}{}, m.err
58 }