1
18
19 package adaptive
20
21 import (
22 "sync"
23 "testing"
24 "time"
25 )
26
27
28 func (t *Throttler) stats() (int64, int64) {
29 now := timeNowFunc()
30
31 t.mu.Lock()
32 a, th := t.accepts.sum(now), t.throttles.sum(now)
33 t.mu.Unlock()
34 return a, th
35 }
36
37
38 const (
39 E = iota
40 A
41 T
42 )
43
44 func TestRegisterBackendResponse(t *testing.T) {
45 testcases := []struct {
46 desc string
47 bins int64
48 ticks []int64
49 responses []int64
50 wantAccepts []int64
51 wantThrottled []int64
52 }{
53 {
54 "Accumulate",
55 3,
56 []int64{0, 1, 2},
57 []int64{A, T, E},
58 []int64{1, 1, 1},
59 []int64{0, 1, 1},
60 },
61 {
62 "LightTimeTravel",
63 3,
64 []int64{1, 0, 2},
65 []int64{A, T, E},
66 []int64{1, 1, 1},
67 []int64{0, 1, 1},
68 },
69 {
70 "HeavyTimeTravel",
71 3,
72 []int64{8, 0, 9},
73 []int64{A, A, A},
74 []int64{1, 1, 2},
75 []int64{0, 0, 0},
76 },
77 {
78 "Rollover",
79 1,
80 []int64{0, 1, 2},
81 []int64{A, T, E},
82 []int64{1, 0, 0},
83 []int64{0, 1, 0},
84 },
85 }
86
87 m := mockClock{}
88 oldTimeNowFunc := timeNowFunc
89 timeNowFunc = m.Now
90 defer func() { timeNowFunc = oldTimeNowFunc }()
91
92 for _, test := range testcases {
93 t.Run(test.desc, func(t *testing.T) {
94 th := newWithArgs(time.Duration(test.bins), test.bins, 2.0, 8)
95 for i, tick := range test.ticks {
96 m.SetNanos(tick)
97
98 if test.responses[i] != E {
99 th.RegisterBackendResponse(test.responses[i] == T)
100 }
101
102 if gotAccepts, gotThrottled := th.stats(); gotAccepts != test.wantAccepts[i] || gotThrottled != test.wantThrottled[i] {
103 t.Errorf("th.stats() = {%d, %d} for index %d, want {%d, %d}", i, gotAccepts, gotThrottled, test.wantAccepts[i], test.wantThrottled[i])
104 }
105 }
106 })
107 }
108 }
109
110 func TestShouldThrottleOptions(t *testing.T) {
111
112
113
114
115 responses := []int64{T, T, T, T, T, T, T, T, T, A, A, A, A, A, A, T, T, T, T}
116
117 n := false
118 y := true
119
120 testcases := []struct {
121 desc string
122 ratioForAccepts float64
123 requestsPadding float64
124 want []bool
125 }{
126 {
127 "Baseline",
128 1.1,
129 8,
130 []bool{n, n, n, n, n, n, n, n, y, y, y, y, y, n, n, n, y, y, y},
131 },
132 {
133 "ChangePadding",
134 1.1,
135 7,
136 []bool{n, n, n, n, n, n, n, y, y, y, y, y, y, y, y, y, y, y, y},
137 },
138 {
139 "ChangeRatioForAccepts",
140 1.4,
141 8,
142 []bool{n, n, n, n, n, n, n, n, y, y, n, n, n, n, n, n, n, n, n},
143 },
144 }
145
146 m := mockClock{}
147 oldTimeNowFunc := timeNowFunc
148 timeNowFunc = m.Now
149 oldRandFunc := randFunc
150 randFunc = func() float64 { return 0.5 }
151 defer func() {
152 timeNowFunc = oldTimeNowFunc
153 randFunc = oldRandFunc
154 }()
155
156 for _, test := range testcases {
157 t.Run(test.desc, func(t *testing.T) {
158 m.SetNanos(0)
159 th := newWithArgs(time.Nanosecond, 1, test.ratioForAccepts, test.requestsPadding)
160 for i, response := range responses {
161 if response != E {
162 th.RegisterBackendResponse(response == T)
163 }
164 if got := th.ShouldThrottle(); got != test.want[i] {
165 t.Errorf("ShouldThrottle for index %d: got %v, want %v", i, got, test.want[i])
166 }
167 }
168 })
169 }
170 }
171
172 func TestParallel(t *testing.T) {
173
174 th := New()
175
176 testDuration := 2 * time.Second
177 numRoutines := 10
178 accepts := make([]int64, numRoutines)
179 throttles := make([]int64, numRoutines)
180 var wg sync.WaitGroup
181 for i := 0; i < numRoutines; i++ {
182 wg.Add(1)
183 go func(num int) {
184 defer wg.Done()
185
186 ticker := time.NewTicker(testDuration)
187 var accept int64
188 var throttle int64
189 for i := 0; ; i++ {
190 select {
191 case <-ticker.C:
192 ticker.Stop()
193 accepts[num] = accept
194 throttles[num] = throttle
195 return
196 default:
197 if i%2 == 0 {
198 th.RegisterBackendResponse(true)
199 throttle++
200 } else {
201 th.RegisterBackendResponse(false)
202 accept++
203 }
204 }
205 }
206 }(i)
207 }
208 wg.Wait()
209
210 var wantAccepts, wantThrottles int64
211 for i := 0; i < numRoutines; i++ {
212 wantAccepts += accepts[i]
213 wantThrottles += throttles[i]
214 }
215
216 if gotAccepts, gotThrottles := th.stats(); gotAccepts != wantAccepts || gotThrottles != wantThrottles {
217 t.Errorf("th.stats() = {%d, %d}, want {%d, %d}", gotAccepts, gotThrottles, wantAccepts, wantThrottles)
218 }
219 }
220
221 type mockClock struct {
222 t time.Time
223 }
224
225 func (m *mockClock) Now() time.Time {
226 return m.t
227 }
228
229 func (m *mockClock) SetNanos(n int64) {
230 m.t = time.Unix(0, n)
231 }
232
View as plain text