go-pattern-examples/gomore/06_circuit_breaker/breaker/breaker_test.go
2020-05-03 10:54:01 +08:00

197 lines
4.2 KiB
Go

package breaker
import (
"errors"
"testing"
"time"
)
var errSomeError = errors.New("errSomeError")
func alwaysPanics() error {
panic("foo")
}
func returnsError() error {
return errSomeError
}
func returnsSuccess() error {
return nil
}
func TestBreakerErrorExpiry(t *testing.T) {
breaker := New(2, 1, 1*time.Second)
for i := 0; i < 3; i++ {
if err := breaker.Run(returnsError); err != errSomeError {
t.Error(err)
}
time.Sleep(1 * time.Second)
}
for i := 0; i < 3; i++ {
if err := breaker.Go(returnsError); err != nil {
t.Error(err)
}
time.Sleep(1 * time.Second)
}
}
func TestBreakerPanicsCountAsErrors(t *testing.T) {
breaker := New(3, 2, 1*time.Second)
// three errors opens the breaker
for i := 0; i < 3; i++ {
func() {
defer func() {
val := recover()
if val.(string) != "foo" {
t.Error("incorrect panic")
}
}()
if err := breaker.Run(alwaysPanics); err != nil {
t.Error(err)
}
t.Error("shouldn't get here")
}()
}
// breaker is open
for i := 0; i < 5; i++ {
if err := breaker.Run(returnsError); err != ErrBreakerOpen {
t.Error(err)
}
}
}
func TestBreakerStateTransitions(t *testing.T) {
breaker := New(3, 2, 1*time.Second)
// three errors opens the breaker
for i := 0; i < 3; i++ {
if err := breaker.Run(returnsError); err != errSomeError {
t.Error(err)
}
}
// breaker is open
for i := 0; i < 5; i++ {
if err := breaker.Run(returnsError); err != ErrBreakerOpen {
t.Error(err)
}
}
// wait for it to half-close
time.Sleep(2 * time.Second)
// one success works, but is not enough to fully close
if err := breaker.Run(returnsSuccess); err != nil {
t.Error(err)
}
// error works, but re-opens immediately
if err := breaker.Run(returnsError); err != errSomeError {
t.Error(err)
}
// breaker is open
if err := breaker.Run(returnsError); err != ErrBreakerOpen {
t.Error(err)
}
// wait for it to half-close
time.Sleep(2 * time.Second)
// two successes is enough to close it for good
for i := 0; i < 2; i++ {
if err := breaker.Run(returnsSuccess); err != nil {
t.Error(err)
}
}
// error works
if err := breaker.Run(returnsError); err != errSomeError {
t.Error(err)
}
// breaker is still closed
if err := breaker.Run(returnsSuccess); err != nil {
t.Error(err)
}
}
func TestBreakerAsyncStateTransitions(t *testing.T) {
breaker := New(3, 2, 1*time.Second)
// three errors opens the breaker
for i := 0; i < 3; i++ {
if err := breaker.Go(returnsError); err != nil {
t.Error(err)
}
}
// just enough to yield the scheduler and let the goroutines work off
time.Sleep(1 * time.Millisecond)
// breaker is open
for i := 0; i < 5; i++ {
if err := breaker.Go(returnsError); err != ErrBreakerOpen {
t.Error(err)
}
}
// wait for it to half-close
time.Sleep(2 * time.Second)
// one success works, but is not enough to fully close
if err := breaker.Go(returnsSuccess); err != nil {
t.Error(err)
}
// error works, but re-opens immediately
if err := breaker.Go(returnsError); err != nil {
t.Error(err)
}
// just enough to yield the scheduler and let the goroutines work off
time.Sleep(1 * time.Millisecond)
// breaker is open
if err := breaker.Go(returnsError); err != ErrBreakerOpen {
t.Error(err)
}
// wait for it to half-close
time.Sleep(2 * time.Second)
// two successes is enough to close it for good
for i := 0; i < 2; i++ {
if err := breaker.Go(returnsSuccess); err != nil {
t.Error(err)
}
}
// just enough to yield the scheduler and let the goroutines work off
time.Sleep(1 * time.Millisecond)
// error works
if err := breaker.Go(returnsError); err != nil {
t.Error(err)
}
// just enough to yield the scheduler and let the goroutines work off
time.Sleep(1 * time.Millisecond)
// breaker is still closed
if err := breaker.Go(returnsSuccess); err != nil {
t.Error(err)
}
}
func ExampleBreaker() {
breaker := New(3, 1, 5*time.Second)
for {
result := breaker.Run(func() error {
// communicate with some external service and
// return an error if the communication failed
return nil
})
switch result {
case nil:
// success!
case ErrBreakerOpen:
// our function wasn't run because the breaker was open
default:
// some other error
}
}
}