-
Notifications
You must be signed in to change notification settings - Fork 251
/
periodic_test.go
394 lines (374 loc) · 11.2 KB
/
periodic_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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
// Copyright 2017 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package periodic
import (
"math"
"os"
"strings"
"sync"
"testing"
"time"
"fortio.org/fortio/log"
)
type Noop struct{}
func (n *Noop) Run(t int) {
}
// used for when we don't actually run periodic test/want to initialize
// watchers.
var bogusTestChan = NewAborter()
func TestNewPeriodicRunner(t *testing.T) {
tests := []struct {
qps float64 // input
numThreads int // input
expectedQPS float64 // expected
expectedNumThreads int // expected
}{
{qps: 0.1, numThreads: 1, expectedQPS: 0.1, expectedNumThreads: 1},
{qps: 1, numThreads: 3, expectedQPS: 1, expectedNumThreads: 3},
{qps: 10, numThreads: 10, expectedQPS: 10, expectedNumThreads: 10},
{qps: 100000, numThreads: 10, expectedQPS: 100000, expectedNumThreads: 10},
{qps: 0.5, numThreads: 1, expectedQPS: 0.5, expectedNumThreads: 1},
// Error cases negative qps same as -1 qps == max speed
{qps: -10, numThreads: 0, expectedQPS: -1, expectedNumThreads: 4},
// Need at least 1 thread
{qps: 0, numThreads: -6, expectedQPS: DefaultRunnerOptions.QPS, expectedNumThreads: 1},
}
for _, tst := range tests {
o := RunnerOptions{
QPS: tst.qps,
NumThreads: tst.numThreads,
Stop: bogusTestChan, // TODO: use bogusTestChan so gOutstandingRuns does reach 0
}
r := newPeriodicRunner(&o)
r.MakeRunners(&Noop{})
if r.QPS != tst.expectedQPS {
t.Errorf("qps: got %f, not as expected %f", r.QPS, tst.expectedQPS)
}
if r.NumThreads != tst.expectedNumThreads {
t.Errorf("threads: with %d input got %d, not as expected %d",
tst.numThreads, r.NumThreads, tst.expectedNumThreads)
}
r.ReleaseRunners()
}
}
type TestCount struct {
count *int64
lock *sync.Mutex
}
func (c *TestCount) Run(i int) {
c.lock.Lock()
(*c.count)++
c.lock.Unlock()
time.Sleep(50 * time.Millisecond)
}
func TestStart(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
o := RunnerOptions{
QPS: 11.4,
NumThreads: 1,
Duration: 1 * time.Second,
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
r.Run()
if count != 11 {
t.Errorf("Test executed unexpected number of times %d instead %d", count, 11)
}
count = 0
oo := r.Options()
oo.NumThreads = 10 // will be lowered to 5 so 10 calls (2 in each thread)
r.Run()
if count != 10 {
t.Errorf("MT Test executed unexpected number of times %d instead %d", count, 10)
}
// note: it's kind of a bug this only works after Run() and not before
if oo.NumThreads != 5 {
t.Errorf("Lowering of thread count broken, got %d instead of 5", oo.NumThreads)
}
count = 0
oo.Duration = 1 * time.Nanosecond
r.Run()
if count != 2 {
t.Errorf("Test executed unexpected number of times %d instead minimum 2", count)
}
r.Options().ReleaseRunners()
}
func TestStartMaxQps(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
o := RunnerOptions{
QPS: -1, // max speed (0 is default qps, not max)
NumThreads: 4,
Duration: 140 * time.Millisecond,
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
var res1 HasRunnerResult // test that interface
res := r.Run()
res1 = res.Result()
expected := int64(3 * 4) // can start 3 50ms in 140ms * 4 threads
// Check the count both from the histogram and from our own test counter:
actual := res1.Result().DurationHistogram.Count
if actual != expected {
t.Errorf("MaxQpsTest executed unexpected number of times %d instead %d", actual, expected)
}
if count != expected {
t.Errorf("MaxQpsTest executed unexpected number of times %d instead %d", count, expected)
}
r.Options().ReleaseRunners()
}
func TestExactlyLargeDur(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
o := RunnerOptions{
QPS: 3,
NumThreads: 4,
Duration: 100 * time.Hour, // will not be used, large to catch if it would
Exactly: 9, // exactly 9 times, so 2 per thread + 1
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
res := r.Run()
expected := o.Exactly
// Check the count both from the histogram and from our own test counter:
actual := res.DurationHistogram.Count
if actual != expected {
t.Errorf("Exact count executed unexpected number of times %d instead %d", actual, expected)
}
if count != expected {
t.Errorf("Exact count executed unexpected number of times %d instead %d", count, expected)
}
r.Options().ReleaseRunners()
}
func TestExactlySmallDur(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
expected := int64(11)
o := RunnerOptions{
QPS: 3,
NumThreads: 4,
Duration: 1 * time.Second, // would do only 3 calls without Exactly
Exactly: expected, // exactly 11 times, so 2 per thread + 3
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
res := r.Run()
// Check the count both from the histogram and from our own test counter:
actual := res.DurationHistogram.Count
if actual != expected {
t.Errorf("Exact count executed unexpected number of times %d instead %d", actual, expected)
}
if count != expected {
t.Errorf("Exact count executed unexpected number of times %d instead %d", count, expected)
}
r.Options().ReleaseRunners()
}
func TestExactlyMaxQps(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
expected := int64(503)
o := RunnerOptions{
QPS: -1, // max qps
NumThreads: 4,
Duration: -1, // infinite but should not be used
Exactly: expected, // exactly 503 times, so 125 per thread + 3
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
res := r.Run()
// Check the count both from the histogram and from our own test counter:
actual := res.DurationHistogram.Count
if actual != expected {
t.Errorf("Exact count executed unexpected number of times %d instead %d", actual, expected)
}
if count != expected {
t.Errorf("Exact count executed unexpected number of times %d instead %d", count, expected)
}
r.Options().ReleaseRunners()
}
func TestID(t *testing.T) {
tests := []struct {
labels string // input
id string // expected suffix after the date
}{
{"", ""},
{"abcDEF123", "_abcDEF123"},
{"A!@#$%^&*()-+=/'B", "_A_B"},
// Ends with non alpha, skip last _
{"A ", "_A"},
{" ", ""},
// truncated to fit 96 (17 from date/time + _ + 78 from labels)
{
"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
"_123456789012345678901234567890123456789012345678901234567890123456789012345678",
},
}
startTime := time.Date(2001, time.January, 2, 3, 4, 5, 0, time.Local)
prefix := "2001-01-02-030405"
for _, tst := range tests {
o := RunnerResults{
StartTime: startTime,
Labels: tst.labels,
}
id := o.ID()
expected := prefix + tst.id
if id != expected {
t.Errorf("id: got %s, not as expected %s", id, expected)
}
}
}
func TestInfiniteDurationAndAbort(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
o := RunnerOptions{
QPS: 10,
NumThreads: 1,
Duration: -1, // infinite but we'll abort after 1sec
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
go func() {
time.Sleep(1 * time.Second)
log.LogVf("Calling abort after 1 sec")
r.Options().Abort()
}()
r.Run()
if count < 9 || count > 13 {
t.Errorf("Test executed unexpected number of times %d instead of 9-13", count)
}
// Same with infinite qps
count = 0
o.QPS = -1 // infinite qps
r.Options().ReleaseRunners()
r = NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
go func() {
time.Sleep(140 * time.Millisecond)
log.LogVf("Sending global interrupt after 0.14 sec")
gAbortMutex.Lock()
gAbortChan <- os.Interrupt
gAbortMutex.Unlock()
}()
r.Run()
r.Options().ReleaseRunners()
if count < 2 || count > 4 { // should get 3 in 140ms
t.Errorf("Test executed unexpected number of times %d instead of 3 (2-4)", count)
}
}
func TestExactlyAndAbort(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
o := RunnerOptions{
QPS: 10,
NumThreads: 1,
Exactly: 100, // would take 10s we'll abort after 1sec
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
go func() {
time.Sleep(1 * time.Second)
log.LogVf("Calling abort after 1 sec")
r.Options().Abort()
}()
res := r.Run()
r.Options().ReleaseRunners()
if count < 9 || count > 13 {
t.Errorf("Test executed unexpected number of times %d instead of 9-13", count)
}
if !strings.Contains(res.RequestedDuration, "exactly 100 calls, interrupted after") {
t.Errorf("Got '%s' and didn't find expected aborted", res.RequestedDuration)
}
}
func TestSleepFallingBehind(t *testing.T) {
var count int64
var lock sync.Mutex
c := TestCount{&count, &lock}
o := RunnerOptions{
QPS: 1000000, // similar to max qps but with sleep falling behind
NumThreads: 4,
Duration: 140 * time.Millisecond,
}
r := NewPeriodicRunner(&o)
r.Options().MakeRunners(&c)
count = 0
res := r.Run()
r.Options().ReleaseRunners()
expected := int64(3 * 4) // can start 3 50ms in 140ms * 4 threads
// Check the count both from the histogram and from our own test counter:
actual := res.DurationHistogram.Count
if actual > expected+2 || actual < expected-2 {
t.Errorf("Extra high qps executed unexpected number of times %d instead %d", actual, expected)
}
// check histogram and our counter got same result
if count != actual {
t.Errorf("Extra high qps internal counter %d doesn't match histogram %d for expected %d", count, actual, expected)
}
}
func Test2Watchers(t *testing.T) {
// Wait for previous test to cleanup watchers
time.Sleep(200 * time.Millisecond)
o1 := RunnerOptions{}
r1 := newPeriodicRunner(&o1)
o2 := RunnerOptions{}
r2 := newPeriodicRunner(&o2)
time.Sleep(200 * time.Millisecond)
gAbortMutex.Lock()
if gOutstandingRuns != 2 {
t.Errorf("found %d watches while expecting 2 for (%v %v)", gOutstandingRuns, r1, r2)
}
gAbortMutex.Unlock()
gAbortChan <- os.Interrupt
// wait for interrupt to propagate
time.Sleep(200 * time.Millisecond)
gAbortMutex.Lock()
if gOutstandingRuns != 0 {
t.Errorf("found %d watches while expecting 0", gOutstandingRuns)
}
gAbortMutex.Unlock()
}
func TestGetJitter(t *testing.T) {
d := getJitter(4)
if d != time.Duration(0) {
t.Errorf("getJitter < 5 got %v instead of expected 0", d)
}
sum := 0.
for i := 0; i < 100; i++ {
d = getJitter(6)
a := math.Abs(float64(d))
// only valid values are -1, 0, 1
if a != 1. && d != 0 {
t.Errorf("getJitter 6 got %v (%v) instead of expected -1/+1", d, a)
}
// make sure we don't always get 0
sum += a
}
if sum <= 60 {
t.Errorf("getJitter 6 got %v sum of abs value instead of expected > 60 at -1/+1", sum)
}
}