-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcircuitbreaker_test.go
145 lines (121 loc) · 2.56 KB
/
circuitbreaker_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package patterns
import (
"context"
"errors"
"fmt"
"math/rand"
"sync"
"testing"
"time"
)
func getResettingCounter() Circuit {
var timer *time.Timer
var total, i int
var mu sync.Mutex
return func(ctx context.Context) (string, error) {
mu.Lock()
defer mu.Unlock()
total++
if i >= 3 {
if timer != nil {
timer.Stop()
}
timer = time.AfterFunc(time.Second*1, func() {
mu.Lock()
defer mu.Unlock()
i = 0
})
return "", errors.New("please wait")
}
i++
return fmt.Sprint(total), nil
}
}
func TestCircuitBreaker(t *testing.T) {
e := Breaker(getResettingCounter(), 3)
for i := 0; i < 100; i++ {
e(context.Background())
}
time.Sleep(time.Second * 3)
total, _ := e(context.Background())
if total != "7" {
t.Errorf("total == %s, want %s", total, "7")
}
total, _ = e(context.Background())
if total != "8" {
t.Errorf("total == %s, want %s", total, "8")
}
}
func failAfter(threshold int) Circuit {
var count int
return func(ctx context.Context) (string, error) {
count++
if count > threshold {
return "", errors.New("intentional fail!")
}
return "success", nil
}
}
func waitAndContinue() Circuit {
return func(ctx context.Context) (string, error) {
time.Sleep(time.Second)
if rand.Int()%2 == 0 {
return "success", nil
}
return "", fmt.Errorf("forced failure")
}
}
func TestCircuitBreakerWithFailAfter(t *testing.T) {
circuit := failAfter(5)
breaker := Breaker(circuit, 1)
circuitOpen := false
doesCircuitOpen := false
doesCircuitReclose := false
count := 0
for range time.NewTicker(time.Second).C {
_, err := breaker(context.Background())
if err != nil {
if err.Error() == "open circuit" {
if !circuitOpen {
circuitOpen = true
doesCircuitOpen = true
t.Log("circuit has opened")
}
} else {
if circuitOpen {
circuitOpen = false
doesCircuitReclose = true
t.Log("circuit has automatically closed")
}
}
} else {
t.Log("circuit closed and operational")
}
count++
if count >= 10 {
break
}
}
if !doesCircuitOpen {
t.Error("circuit didn't appear to open")
}
if !doesCircuitReclose {
t.Error("circuit didn't appear to close after time")
}
}
func TestCircuitBreakerDataRace(t *testing.T) {
ctx := context.Background()
circuit := waitAndContinue()
breaker := Breaker(circuit, 1)
var wg sync.WaitGroup
for count := 1; count <= 20; count++ {
wg.Add(1)
go func(count int) {
defer wg.Done()
time.Sleep(50 * time.Millisecond)
_, err := breaker(ctx)
t.Logf("attempt %d: err=%v", count, err)
}(count)
}
wg.Wait()
}