зеркало из https://github.com/golang/pkgsite.git
718 строки
18 KiB
Go
718 строки
18 KiB
Go
// Copyright 2020 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package breaker
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
)
|
|
|
|
func TestBucketReset(t *testing.T) {
|
|
bu := bucket{12, 8}
|
|
bu.reset()
|
|
if bu.successes != 0 {
|
|
t.Errorf("got successes = %d, want %d", bu.successes, 0)
|
|
}
|
|
if bu.failures != 0 {
|
|
t.Errorf("got failures = %d, want %d", bu.failures, 0)
|
|
}
|
|
}
|
|
|
|
func TestResetCounts(t *testing.T) {
|
|
b := newTestBreaker(Config{})
|
|
for i := 0; i < len(b.buckets); i++ {
|
|
b.buckets[i].successes = 10
|
|
b.buckets[i].failures = 15
|
|
}
|
|
|
|
b.resetCounts()
|
|
testBuckets(t, b.buckets[:], 0, 0)
|
|
testCounts(t, b, 0, 0, 0)
|
|
}
|
|
|
|
func TestNewBreaker(t *testing.T) {
|
|
timeNow = func() time.Time {
|
|
return time.Date(2020, time.May, 26, 18, 0, 0, 0, time.UTC)
|
|
}
|
|
got, err := New(Config{
|
|
FailsToRed: 10,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New() returned %e, want nil", err)
|
|
}
|
|
want := &Breaker{
|
|
config: Config{
|
|
FailsToRed: 10,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
buckets: [numBuckets]bucket{},
|
|
granularity: 2500 * time.Millisecond,
|
|
state: Green,
|
|
cur: 0,
|
|
consecutiveSuccs: 0,
|
|
timeout: 30 * time.Second,
|
|
lastEvent: time.Date(2020, time.May, 26, 18, 0, 0, 0, time.UTC),
|
|
}
|
|
|
|
diff := cmp.Diff(want, got, cmpopts.IgnoreUnexported(sync.Mutex{}), cmp.AllowUnexported(Breaker{}, bucket{}))
|
|
if diff != "" {
|
|
t.Fatalf("mismatch (-want +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestIllegalBreaker(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
config Config
|
|
}{
|
|
{
|
|
name: "FailsToRed cannot be 0",
|
|
config: Config{
|
|
FailsToRed: 0,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "FailsToRed cannot be negative",
|
|
config: Config{
|
|
FailsToRed: -5,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "FailureThreshold cannot be 0",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "FailureThreshold cannot be negative",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: -0.8,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "FailureThreshold cannot exceed 1",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 1.2,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "GreenInterval cannot be 0",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 0,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "GreenInterval cannot be negative",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: -4 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "MinTimeout cannot be 0",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 0,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "MinTimeout cannot be negative",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: -2 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "MaxTimeout cannot be 0",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 0,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "MaxTimeout cannot be negative",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: -12 * time.Minute,
|
|
SuccsToGreen: 15,
|
|
},
|
|
},
|
|
{
|
|
name: "SuccsToGreen cannot be 0",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "SuccsToGreen cannot be negative",
|
|
config: Config{
|
|
FailsToRed: 8,
|
|
FailureThreshold: 0.65,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: 30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: -7,
|
|
},
|
|
},
|
|
{
|
|
name: "multiple illegal values return error",
|
|
config: Config{
|
|
FailsToRed: 0,
|
|
FailureThreshold: 1.4,
|
|
GreenInterval: 20 * time.Second,
|
|
MinTimeout: -30 * time.Second,
|
|
MaxTimeout: 16 * time.Minute,
|
|
SuccsToGreen: 100,
|
|
},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
b, err := New(test.config)
|
|
if err == nil {
|
|
t.Fatalf("New() returned nil error")
|
|
}
|
|
if b != nil {
|
|
t.Fatalf("New() returned %+v, want nil", b)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBreakerGranularity(t *testing.T) {
|
|
for _, test := range []struct {
|
|
config Config
|
|
want time.Duration
|
|
}{
|
|
{
|
|
config: Config{},
|
|
want: 1250 * time.Millisecond,
|
|
},
|
|
{
|
|
config: Config{GreenInterval: 1 * time.Second},
|
|
want: 125 * time.Millisecond,
|
|
},
|
|
{
|
|
config: Config{GreenInterval: 3 * time.Second},
|
|
want: 375 * time.Millisecond,
|
|
},
|
|
{
|
|
config: Config{GreenInterval: 1 * time.Minute},
|
|
want: 7500 * time.Millisecond,
|
|
},
|
|
{
|
|
config: Config{GreenInterval: 1 * time.Hour},
|
|
want: 450 * time.Second,
|
|
},
|
|
} {
|
|
b := newTestBreaker(test.config)
|
|
if b.granularity != test.want {
|
|
t.Errorf("b.granularity = %d, want %d", b.granularity, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestState(t *testing.T) {
|
|
for _, want := range []State{
|
|
Green,
|
|
Yellow,
|
|
Red,
|
|
} {
|
|
b := newTestBreaker(Config{})
|
|
b.state = want
|
|
if got := b.checkState(); got != want {
|
|
t.Errorf("b.checkState() = %s, got %s", got, want)
|
|
}
|
|
if got := b.State(); got != want {
|
|
t.Errorf("b.State() = %s, want %s", got, want)
|
|
}
|
|
}
|
|
}
|
|
func TestAllow(t *testing.T) {
|
|
for _, test := range []struct {
|
|
state State
|
|
shouldAllow bool
|
|
}{
|
|
{
|
|
state: Green,
|
|
shouldAllow: true,
|
|
},
|
|
{
|
|
state: Yellow,
|
|
shouldAllow: true,
|
|
},
|
|
{
|
|
state: Red,
|
|
shouldAllow: false,
|
|
},
|
|
} {
|
|
b := newTestBreaker(Config{})
|
|
b.state = test.state
|
|
allowed := b.Allow()
|
|
if allowed != test.shouldAllow {
|
|
t.Errorf("b.Allow() = %t in %s, want %t", allowed, test.state, test.shouldAllow)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSuccesses(t *testing.T) {
|
|
b := newTestBreaker(Config{})
|
|
b.succeeded()
|
|
testCounts(t, b, 1, 1, 0)
|
|
b.succeeded()
|
|
testCounts(t, b, 2, 2, 0)
|
|
b.succeeded()
|
|
testCounts(t, b, 3, 3, 0)
|
|
}
|
|
|
|
func TestFailures(t *testing.T) {
|
|
b := newTestBreaker(Config{})
|
|
b.failed()
|
|
testCounts(t, b, 0, 0, 1)
|
|
b.failed()
|
|
testCounts(t, b, 0, 0, 2)
|
|
b.failed()
|
|
testCounts(t, b, 0, 0, 3)
|
|
}
|
|
|
|
func TestSucceededAndFailed(t *testing.T) {
|
|
b := newTestBreaker(Config{})
|
|
b.succeeded()
|
|
testCounts(t, b, 1, 1, 0)
|
|
b.failed()
|
|
testCounts(t, b, 0, 1, 1)
|
|
b.failed()
|
|
testCounts(t, b, 0, 1, 2)
|
|
b.succeeded()
|
|
testCounts(t, b, 1, 2, 2)
|
|
b.succeeded()
|
|
testCounts(t, b, 2, 3, 2)
|
|
b.succeeded()
|
|
testCounts(t, b, 3, 4, 2)
|
|
b.failed()
|
|
testCounts(t, b, 0, 4, 3)
|
|
}
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
now := time.Now()
|
|
b := newTestBreaker(Config{})
|
|
b.lastEvent = now
|
|
b.granularity = 1 * time.Second
|
|
for i := 0; i < len(b.buckets); i++ {
|
|
b.buckets[i].successes = 4
|
|
b.buckets[i].failures = 9
|
|
}
|
|
|
|
// Update 0 buckets.
|
|
b.update(now.Add(-1 * time.Second))
|
|
if b.cur != 0 {
|
|
t.Errorf("cur: got %d, want %d", b.cur, 0)
|
|
}
|
|
testBuckets(t, b.buckets[:], 4, 9)
|
|
testCounts(t, b, 0, 4*len(b.buckets), 9*len(b.buckets))
|
|
|
|
// Update next 3 buckets.
|
|
b.update(now.Add(3 * time.Second))
|
|
if b.cur != 3 {
|
|
t.Errorf("cur: got %d, want %d", b.cur, 3)
|
|
}
|
|
testBuckets(t, b.buckets[:1], 4, 9)
|
|
testBuckets(t, b.buckets[1:4], 0, 0)
|
|
testBuckets(t, b.buckets[4:], 4, 9)
|
|
testCounts(t, b, 0, 4*len(b.buckets)-12, 9*len(b.buckets)-27)
|
|
|
|
// Update all buckets.
|
|
b.update(now.Add(1003 * time.Second))
|
|
expectedCur := 0
|
|
if b.cur != expectedCur {
|
|
t.Errorf("cur: got %d, want %d", b.cur, expectedCur)
|
|
}
|
|
testBuckets(t, b.buckets[:], 0, 0)
|
|
testCounts(t, b, 0, 0, 0)
|
|
}
|
|
|
|
func TestStateChanges(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
config Config
|
|
preSuccesses int
|
|
preFailures int
|
|
fromState State
|
|
allow bool
|
|
success bool
|
|
sleep time.Duration
|
|
toState State
|
|
}{
|
|
{
|
|
name: "breaker state remains green when FailsToRed is not exceeded",
|
|
config: Config{FailsToRed: 8, FailureThreshold: 0.5},
|
|
preSuccesses: 6,
|
|
preFailures: 7,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Green,
|
|
},
|
|
{
|
|
name: "breaker state remains green when FailureThreshold is not exceeded",
|
|
config: Config{FailsToRed: 2, FailureThreshold: 0.8},
|
|
preSuccesses: 3,
|
|
preFailures: 6,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Green,
|
|
},
|
|
{
|
|
name: "breaker state remains green when failure ratio = FailureThreshold",
|
|
config: Config{FailsToRed: 10, FailureThreshold: 0.5},
|
|
preSuccesses: 20,
|
|
preFailures: 19,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Green,
|
|
},
|
|
{
|
|
name: "breaker state remains green when failures = FailsToRed",
|
|
config: Config{FailsToRed: 10, FailureThreshold: 0.3},
|
|
preSuccesses: 10,
|
|
preFailures: 9,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Green,
|
|
},
|
|
{
|
|
name: "breaker state changes to red when FailureThreshold is exceeded and after FailsToRed has been exceeded",
|
|
config: Config{FailsToRed: 10, FailureThreshold: 0.5},
|
|
preSuccesses: 20,
|
|
preFailures: 20,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Red,
|
|
},
|
|
{
|
|
name: "breaker state changes to red when FailsToRed is exceeded and after FailureThreshold has been exceeded",
|
|
config: Config{FailsToRed: 20, FailureThreshold: 0.3},
|
|
preSuccesses: 20,
|
|
preFailures: 20,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Red,
|
|
},
|
|
{
|
|
name: "breaker state changes from green to red",
|
|
config: Config{FailsToRed: 4, FailureThreshold: 0.5},
|
|
preSuccesses: 4,
|
|
preFailures: 4,
|
|
fromState: Green,
|
|
allow: true,
|
|
success: false,
|
|
toState: Red,
|
|
},
|
|
{
|
|
name: "failure in yellow state changes breaker to red state",
|
|
config: Config{},
|
|
preSuccesses: 0,
|
|
preFailures: 0,
|
|
fromState: Yellow,
|
|
allow: true,
|
|
success: false,
|
|
toState: Red,
|
|
},
|
|
{
|
|
name: "breaker state changes from yellow to green",
|
|
config: Config{SuccsToGreen: 1},
|
|
preSuccesses: 0,
|
|
preFailures: 0,
|
|
fromState: Yellow,
|
|
allow: true,
|
|
success: true,
|
|
toState: Green,
|
|
},
|
|
{
|
|
name: "breaker state changes from red to yellow",
|
|
config: Config{MinTimeout: 1 * time.Second},
|
|
preSuccesses: 0,
|
|
preFailures: 0,
|
|
fromState: Red,
|
|
sleep: 1*time.Second + 1*time.Nanosecond,
|
|
toState: Yellow,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
now := time.Time{}
|
|
timeNow = func() time.Time { return now }
|
|
b := newTestBreaker(test.config)
|
|
b.state = test.fromState
|
|
b.buckets[0].successes = test.preSuccesses
|
|
b.buckets[0].failures = test.preFailures
|
|
|
|
allowed := b.Allow()
|
|
if allowed != test.allow {
|
|
t.Fatalf("b.Allow() = %t in %s, want %t", allowed, test.fromState, test.allow)
|
|
}
|
|
if test.allow {
|
|
b.Record(test.success)
|
|
}
|
|
|
|
// Pseudo sleep.
|
|
now = now.Add(test.sleep)
|
|
|
|
if state := b.State(); state != test.toState {
|
|
t.Errorf("b.State() = %s, want %s", state, test.toState)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunningBreaker(t *testing.T) {
|
|
now := time.Time{}
|
|
timeNow = func() time.Time { return now }
|
|
b := newTestBreaker(Config{
|
|
GreenInterval: 5 * time.Second,
|
|
})
|
|
|
|
// The following tests happen sequentially. The tests' states depend on previous tests.
|
|
for _, test := range []struct {
|
|
name string
|
|
firstSleep time.Duration
|
|
allow bool
|
|
secondSleep time.Duration
|
|
success bool
|
|
wantConsecutiveSuccs int
|
|
wantSuccesses int
|
|
wantFailures int
|
|
}{
|
|
{
|
|
name: "successFunc called after a long time updates counts",
|
|
firstSleep: 20 * time.Second,
|
|
allow: true,
|
|
secondSleep: 20 * time.Second,
|
|
success: true,
|
|
wantConsecutiveSuccs: 1,
|
|
wantSuccesses: 1,
|
|
wantFailures: 0,
|
|
},
|
|
{
|
|
name: "success within GreenInterval updates counts correctly",
|
|
firstSleep: 1 * time.Second,
|
|
allow: true,
|
|
secondSleep: 3 * time.Second,
|
|
success: true,
|
|
wantConsecutiveSuccs: 2,
|
|
wantSuccesses: 2,
|
|
wantFailures: 0,
|
|
},
|
|
{
|
|
name: "success after a long time updates counts correctly",
|
|
firstSleep: 30 * time.Second,
|
|
allow: true,
|
|
secondSleep: 80 * time.Second,
|
|
success: true,
|
|
wantConsecutiveSuccs: 3,
|
|
wantSuccesses: 1,
|
|
wantFailures: 0,
|
|
},
|
|
{
|
|
name: "failure within GreenInterval updates counts correctly",
|
|
firstSleep: 1 * time.Second,
|
|
allow: true,
|
|
secondSleep: 3 * time.Second,
|
|
success: false,
|
|
wantConsecutiveSuccs: 0,
|
|
wantSuccesses: 1,
|
|
wantFailures: 1,
|
|
},
|
|
{
|
|
name: "second failure within GreenInterval updates counts correctly",
|
|
firstSleep: 1 * time.Millisecond,
|
|
allow: true,
|
|
secondSleep: 3 * time.Millisecond,
|
|
success: false,
|
|
wantConsecutiveSuccs: 0,
|
|
wantSuccesses: 1,
|
|
wantFailures: 2,
|
|
},
|
|
{
|
|
name: "failure after a long time updates counts correctly",
|
|
firstSleep: 10 * time.Second,
|
|
allow: true,
|
|
secondSleep: 4 * time.Minute,
|
|
success: false,
|
|
wantConsecutiveSuccs: 0,
|
|
wantSuccesses: 0,
|
|
wantFailures: 1,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
now = now.Add(test.firstSleep)
|
|
allowed := b.Allow()
|
|
|
|
if allowed != test.allow {
|
|
t.Fatalf("breaker.Allow() = %t, want %t", allowed, test.allow)
|
|
}
|
|
|
|
now = now.Add(test.secondSleep)
|
|
if test.allow {
|
|
b.Record(test.success)
|
|
}
|
|
|
|
testCounts(t, b, test.wantConsecutiveSuccs, test.wantSuccesses, test.wantFailures)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIncreaseTimeout(t *testing.T) {
|
|
b := newTestBreaker(Config{
|
|
MinTimeout: 1 * time.Second,
|
|
MaxTimeout: 12 * time.Second,
|
|
})
|
|
b.timeout = 3 * time.Second
|
|
|
|
b.increaseTimeout()
|
|
testTimeouts(t, b, 6*time.Second, 1*time.Second, 12*time.Second)
|
|
b.increaseTimeout()
|
|
testTimeouts(t, b, 12*time.Second, 1*time.Second, 12*time.Second)
|
|
b.increaseTimeout()
|
|
testTimeouts(t, b, 12*time.Second, 1*time.Second, 12*time.Second)
|
|
|
|
b.config.MaxTimeout = 14 * time.Second
|
|
testTimeouts(t, b, 12*time.Second, 1*time.Second, 14*time.Second)
|
|
b.increaseTimeout()
|
|
testTimeouts(t, b, 14*time.Second, 1*time.Second, 14*time.Second)
|
|
b.increaseTimeout()
|
|
testTimeouts(t, b, 14*time.Second, 1*time.Second, 14*time.Second)
|
|
}
|
|
|
|
// newTestBreaker is like New, but with default values for easier testing.
|
|
func newTestBreaker(config Config) *Breaker {
|
|
if config.FailsToRed <= 0 {
|
|
config.FailsToRed = 10
|
|
}
|
|
if config.FailureThreshold <= 0 {
|
|
config.FailureThreshold = 0.5
|
|
}
|
|
if config.GreenInterval <= 0 {
|
|
config.GreenInterval = 10 * time.Second
|
|
}
|
|
if config.MinTimeout <= 0 {
|
|
config.MinTimeout = 30 * time.Second
|
|
}
|
|
if config.MaxTimeout <= 0 {
|
|
config.MaxTimeout = 4 * time.Minute
|
|
}
|
|
if config.SuccsToGreen <= 0 {
|
|
config.SuccsToGreen = 20
|
|
}
|
|
b, _ := New(config)
|
|
return b
|
|
}
|
|
|
|
func testCounts(t *testing.T, b *Breaker, consecutiveSuccs, wantSuccesses, wantFailures int) {
|
|
if b.consecutiveSuccs != consecutiveSuccs {
|
|
t.Errorf("b.consecutiveSuccs = %d, want %d", b.consecutiveSuccs, consecutiveSuccs)
|
|
}
|
|
successes, failures := b.counts()
|
|
if successes != wantSuccesses {
|
|
t.Errorf("successes = %d, want %d", successes, wantSuccesses)
|
|
}
|
|
if failures != wantFailures {
|
|
t.Errorf("failures = %d, want %d", failures, wantFailures)
|
|
}
|
|
}
|
|
|
|
func testBuckets(t *testing.T, buckets []bucket, successes, failures int) {
|
|
for i, bu := range buckets {
|
|
if bu.successes != successes {
|
|
t.Errorf("slice bucket %d successes: got %d, want %d", i, bu.successes, successes)
|
|
}
|
|
if bu.failures != failures {
|
|
t.Errorf("slice bucket %d failures: got %d, want %d", i, bu.failures, failures)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testTimeouts(t *testing.T, b *Breaker, timeout, minTimeout, maxTimeout time.Duration) {
|
|
if b.timeout != timeout {
|
|
t.Errorf("b.timeout = %s, want %s", b.timeout, timeout)
|
|
}
|
|
if b.config.MinTimeout != minTimeout {
|
|
t.Errorf("b.config.MinTimeout = %s, want %s", b.config.MinTimeout, minTimeout)
|
|
}
|
|
if b.config.MaxTimeout != maxTimeout {
|
|
t.Errorf("b.config.MaxTimeout = %s, want %s", b.config.MaxTimeout, maxTimeout)
|
|
}
|
|
}
|