github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/ratelimit/ratelimit_test.go (about) 1 package ratelimit 2 3 import ( 4 "math" 5 "testing" 6 "time" 7 8 gc "gopkg.in/check.v1" 9 ) 10 11 func TestPackage(t *testing.T) { 12 gc.TestingT(t) 13 } 14 15 type rateLimitSuite struct{} 16 17 var _ = gc.Suite(rateLimitSuite{}) 18 19 type takeReq struct { 20 time time.Duration 21 count int64 22 expectWait time.Duration 23 } 24 25 var takeTests = []struct { 26 about string 27 fillInterval time.Duration 28 capacity int64 29 reqs []takeReq 30 }{{ 31 about: "serial requests", 32 fillInterval: 250 * time.Millisecond, 33 capacity: 10, 34 reqs: []takeReq{{ 35 time: 0, 36 count: 0, 37 expectWait: 0, 38 }, { 39 time: 0, 40 count: 10, 41 expectWait: 0, 42 }, { 43 time: 0, 44 count: 1, 45 expectWait: 250 * time.Millisecond, 46 }, { 47 time: 250 * time.Millisecond, 48 count: 1, 49 expectWait: 250 * time.Millisecond, 50 }}, 51 }, { 52 about: "concurrent requests", 53 fillInterval: 250 * time.Millisecond, 54 capacity: 10, 55 reqs: []takeReq{{ 56 time: 0, 57 count: 10, 58 expectWait: 0, 59 }, { 60 time: 0, 61 count: 2, 62 expectWait: 500 * time.Millisecond, 63 }, { 64 time: 0, 65 count: 2, 66 expectWait: 1000 * time.Millisecond, 67 }, { 68 time: 0, 69 count: 1, 70 expectWait: 1250 * time.Millisecond, 71 }}, 72 }, { 73 about: "more than capacity", 74 fillInterval: 1 * time.Millisecond, 75 capacity: 10, 76 reqs: []takeReq{{ 77 time: 0, 78 count: 10, 79 expectWait: 0, 80 }, { 81 time: 20 * time.Millisecond, 82 count: 15, 83 expectWait: 5 * time.Millisecond, 84 }}, 85 }, { 86 about: "sub-quantum time", 87 fillInterval: 10 * time.Millisecond, 88 capacity: 10, 89 reqs: []takeReq{{ 90 time: 0, 91 count: 10, 92 expectWait: 0, 93 }, { 94 time: 7 * time.Millisecond, 95 count: 1, 96 expectWait: 3 * time.Millisecond, 97 }, { 98 time: 8 * time.Millisecond, 99 count: 1, 100 expectWait: 12 * time.Millisecond, 101 }}, 102 }, { 103 about: "within capacity", 104 fillInterval: 10 * time.Millisecond, 105 capacity: 5, 106 reqs: []takeReq{{ 107 time: 0, 108 count: 5, 109 expectWait: 0, 110 }, { 111 time: 60 * time.Millisecond, 112 count: 5, 113 expectWait: 0, 114 }, { 115 time: 60 * time.Millisecond, 116 count: 1, 117 expectWait: 10 * time.Millisecond, 118 }, { 119 time: 80 * time.Millisecond, 120 count: 2, 121 expectWait: 10 * time.Millisecond, 122 }}, 123 }} 124 125 var availTests = []struct { 126 about string 127 capacity int64 128 fillInterval time.Duration 129 take int64 130 sleep time.Duration 131 132 expectCountAfterTake int64 133 expectCountAfterSleep int64 134 }{{ 135 about: "should fill tokens after interval", 136 capacity: 5, 137 fillInterval: time.Second, 138 take: 5, 139 sleep: time.Second, 140 expectCountAfterTake: 0, 141 expectCountAfterSleep: 1, 142 }, { 143 about: "should fill tokens plus existing count", 144 capacity: 2, 145 fillInterval: time.Second, 146 take: 1, 147 sleep: time.Second, 148 expectCountAfterTake: 1, 149 expectCountAfterSleep: 2, 150 }, { 151 about: "shouldn't fill before interval", 152 capacity: 2, 153 fillInterval: 2 * time.Second, 154 take: 1, 155 sleep: time.Second, 156 expectCountAfterTake: 1, 157 expectCountAfterSleep: 1, 158 }, { 159 about: "should fill only once after 1*interval before 2*interval", 160 capacity: 2, 161 fillInterval: 2 * time.Second, 162 take: 1, 163 sleep: 3 * time.Second, 164 expectCountAfterTake: 1, 165 expectCountAfterSleep: 2, 166 }} 167 168 func (rateLimitSuite) TestTake(c *gc.C) { 169 for i, test := range takeTests { 170 tb := NewBucket(test.fillInterval, test.capacity) 171 for j, req := range test.reqs { 172 d, ok := tb.take(tb.startTime.Add(req.time), req.count, infinityDuration) 173 c.Assert(ok, gc.Equals, true) 174 if d != req.expectWait { 175 c.Fatalf("test %d.%d, %s, got %v want %v", i, j, test.about, d, req.expectWait) 176 } 177 } 178 } 179 } 180 181 func (rateLimitSuite) TestTakeMaxDuration(c *gc.C) { 182 for i, test := range takeTests { 183 tb := NewBucket(test.fillInterval, test.capacity) 184 for j, req := range test.reqs { 185 if req.expectWait > 0 { 186 d, ok := tb.take(tb.startTime.Add(req.time), req.count, req.expectWait-1) 187 c.Assert(ok, gc.Equals, false) 188 c.Assert(d, gc.Equals, time.Duration(0)) 189 } 190 d, ok := tb.take(tb.startTime.Add(req.time), req.count, req.expectWait) 191 c.Assert(ok, gc.Equals, true) 192 if d != req.expectWait { 193 c.Fatalf("test %d.%d, %s, got %v want %v", i, j, test.about, d, req.expectWait) 194 } 195 } 196 } 197 } 198 199 type takeAvailableReq struct { 200 time time.Duration 201 count int64 202 expect int64 203 } 204 205 var takeAvailableTests = []struct { 206 about string 207 fillInterval time.Duration 208 capacity int64 209 reqs []takeAvailableReq 210 }{{ 211 about: "serial requests", 212 fillInterval: 250 * time.Millisecond, 213 capacity: 10, 214 reqs: []takeAvailableReq{{ 215 time: 0, 216 count: 0, 217 expect: 0, 218 }, { 219 time: 0, 220 count: 10, 221 expect: 10, 222 }, { 223 time: 0, 224 count: 1, 225 expect: 0, 226 }, { 227 time: 250 * time.Millisecond, 228 count: 1, 229 expect: 1, 230 }}, 231 }, { 232 about: "concurrent requests", 233 fillInterval: 250 * time.Millisecond, 234 capacity: 10, 235 reqs: []takeAvailableReq{{ 236 time: 0, 237 count: 5, 238 expect: 5, 239 }, { 240 time: 0, 241 count: 2, 242 expect: 2, 243 }, { 244 time: 0, 245 count: 5, 246 expect: 3, 247 }, { 248 time: 0, 249 count: 1, 250 expect: 0, 251 }}, 252 }, { 253 about: "more than capacity", 254 fillInterval: 1 * time.Millisecond, 255 capacity: 10, 256 reqs: []takeAvailableReq{{ 257 time: 0, 258 count: 10, 259 expect: 10, 260 }, { 261 time: 20 * time.Millisecond, 262 count: 15, 263 expect: 10, 264 }}, 265 }, { 266 about: "within capacity", 267 fillInterval: 10 * time.Millisecond, 268 capacity: 5, 269 reqs: []takeAvailableReq{{ 270 time: 0, 271 count: 5, 272 expect: 5, 273 }, { 274 time: 60 * time.Millisecond, 275 count: 5, 276 expect: 5, 277 }, { 278 time: 70 * time.Millisecond, 279 count: 1, 280 expect: 1, 281 }}, 282 }} 283 284 func (rateLimitSuite) TestTakeAvailable(c *gc.C) { 285 for i, test := range takeAvailableTests { 286 tb := NewBucket(test.fillInterval, test.capacity) 287 for j, req := range test.reqs { 288 d := tb.takeAvailable(tb.startTime.Add(req.time), req.count) 289 if d != req.expect { 290 c.Fatalf("test %d.%d, %s, got %v want %v", i, j, test.about, d, req.expect) 291 } 292 } 293 } 294 } 295 296 func (rateLimitSuite) TestPanics(c *gc.C) { 297 c.Assert(func() { NewBucket(0, 1) }, gc.PanicMatches, "token bucket fill interval is not > 0") 298 c.Assert(func() { NewBucket(-2, 1) }, gc.PanicMatches, "token bucket fill interval is not > 0") 299 c.Assert(func() { NewBucket(1, 0) }, gc.PanicMatches, "token bucket capacity is not > 0") 300 c.Assert(func() { NewBucket(1, -2) }, gc.PanicMatches, "token bucket capacity is not > 0") 301 } 302 303 func isCloseTo(x, y, tolerance float64) bool { 304 return math.Abs(x-y)/y < tolerance 305 } 306 307 func (rateLimitSuite) TestRate(c *gc.C) { 308 tb := NewBucket(1, 1) 309 if !isCloseTo(tb.Rate(), 1e9, 0.00001) { 310 c.Fatalf("got %v want 1e9", tb.Rate()) 311 } 312 tb = NewBucket(2*time.Second, 1) 313 if !isCloseTo(tb.Rate(), 0.5, 0.00001) { 314 c.Fatalf("got %v want 0.5", tb.Rate()) 315 } 316 tb = NewBucketWithQuantum(100*time.Millisecond, 1, 5) 317 if !isCloseTo(tb.Rate(), 50, 0.00001) { 318 c.Fatalf("got %v want 50", tb.Rate()) 319 } 320 } 321 322 func checkRate(c *gc.C, rate float64) { 323 tb := NewBucketWithRate(rate, 1<<62) 324 if !isCloseTo(tb.Rate(), rate, rateMargin) { 325 c.Fatalf("got %g want %v", tb.Rate(), rate) 326 } 327 d, ok := tb.take(tb.startTime, 1<<62, infinityDuration) 328 c.Assert(ok, gc.Equals, true) 329 c.Assert(d, gc.Equals, time.Duration(0)) 330 331 // Check that the actual rate is as expected by 332 // asking for a not-quite multiple of the bucket's 333 // quantum and checking that the wait time 334 // correct. 335 d, ok = tb.take(tb.startTime, tb.quantum*2-tb.quantum/2, infinityDuration) 336 c.Assert(ok, gc.Equals, true) 337 expectTime := 1e9 * float64(tb.quantum) * 2 / rate 338 if !isCloseTo(float64(d), expectTime, rateMargin) { 339 c.Fatalf("rate %g: got %g want %v", rate, float64(d), expectTime) 340 } 341 } 342 343 func (rateLimitSuite) NewBucketWithRate(c *gc.C) { 344 for rate := float64(1); rate < 1e6; rate += 7 { 345 checkRate(c, rate) 346 } 347 for _, rate := range []float64{ 348 1024 * 1024 * 1024, 349 1e-5, 350 0.9e-5, 351 0.5, 352 0.9, 353 0.9e8, 354 3e12, 355 4e18, 356 float64(1<<63 - 1), 357 } { 358 checkRate(c, rate) 359 checkRate(c, rate/3) 360 checkRate(c, rate*1.3) 361 } 362 } 363 364 func TestAvailable(t *testing.T) { 365 for i, tt := range availTests { 366 tb := NewBucket(tt.fillInterval, tt.capacity) 367 if c := tb.takeAvailable(tb.startTime, tt.take); c != tt.take { 368 t.Fatalf("#%d: %s, take = %d, want = %d", i, tt.about, c, tt.take) 369 } 370 if c := tb.available(tb.startTime); c != tt.expectCountAfterTake { 371 t.Fatalf("#%d: %s, after take, available = %d, want = %d", i, tt.about, c, tt.expectCountAfterTake) 372 } 373 if c := tb.available(tb.startTime.Add(tt.sleep)); c != tt.expectCountAfterSleep { 374 t.Fatalf("#%d: %s, after some time it should fill in new tokens, available = %d, want = %d", 375 i, tt.about, c, tt.expectCountAfterSleep) 376 } 377 } 378 379 } 380 381 func TestNoBonusTokenAfterBucketIsFull(t *testing.T) { 382 tb := NewBucketWithQuantum(time.Second*1, 100, 20) 383 curAvail := tb.Available() 384 if curAvail != 100 { 385 t.Fatalf("initially: actual available = %d, expected = %d", curAvail, 100) 386 } 387 388 time.Sleep(time.Second * 5) 389 390 curAvail = tb.Available() 391 if curAvail != 100 { 392 t.Fatalf("after pause: actual available = %d, expected = %d", curAvail, 100) 393 } 394 395 cnt := tb.TakeAvailable(100) 396 if cnt != 100 { 397 t.Fatalf("taking: actual taken count = %d, expected = %d", cnt, 100) 398 } 399 400 curAvail = tb.Available() 401 if curAvail != 0 { 402 t.Fatalf("after taken: actual available = %d, expected = %d", curAvail, 0) 403 } 404 } 405 406 func BenchmarkWait(b *testing.B) { 407 tb := NewBucket(1, 16*1024) 408 for i := b.N - 1; i >= 0; i-- { 409 tb.Wait(1) 410 } 411 } 412 413 func BenchmarkNewBucket(b *testing.B) { 414 for i := b.N - 1; i >= 0; i-- { 415 NewBucketWithRate(4e18, 1<<62) 416 } 417 }