1
2
mirror of https://github.com/yunginnanet/Rate5 synced 2024-06-27 09:28:29 +00:00
Rate5/ratelimiter_test.go

244 lines
5.0 KiB
Go
Raw Normal View History

package rate5
import (
2021-09-29 12:18:51 +00:00
"crypto/rand"
"encoding/binary"
"testing"
"time"
)
var (
dummyTicker *ticker
testDebug = true
2021-09-29 12:18:51 +00:00
stopDebug chan bool
)
2021-09-29 12:18:51 +00:00
func init() {
stopDebug = make(chan bool)
}
type randomPatron struct {
key string
Identity
}
const charset = "abcdefghijklmnopqrstuvwxyz1234567890"
func (rp *randomPatron) UniqueKey() string {
return rp.key
}
func randomUint32() uint32 {
b := make([]byte, 8192)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return binary.LittleEndian.Uint32(b)
}
func (rp *randomPatron) GenerateKey() {
var keylen = 8
buf := make([]byte, keylen)
for n := 0; n != keylen; n++ {
buf[n] = charset[randomUint32()%uint32(len(charset))]
}
rp.key = string(buf)
}
func watchDebug(r *Limiter, t *testing.T) {
t.Logf("debug enabled")
rd := r.DebugChannel()
pre := "[Rate5] "
var lastcount = 0
var count = 0
for {
select {
case msg := <-rd:
t.Logf("%s Limit: %s \n", pre, msg)
2021-09-29 12:18:51 +00:00
case <-stopDebug:
return
default:
count++
if count-lastcount >= 10 {
lastcount = count
t.Logf("Times limited: %d", r.GetGrandTotalRated())
}
2021-09-29 12:18:51 +00:00
time.Sleep(10 * time.Millisecond)
}
}
}
type ticker struct{}
func init() {
dummyTicker = &ticker{}
}
func (tick *ticker) UniqueKey() string {
return "tick"
}
func Test_NewCustomLimiter(t *testing.T) {
limiter := NewCustomLimiter(Policy{
Window: 5,
Burst: 10,
Strict: false,
})
//goland:noinspection GoBoolExpressions
if testDebug {
go watchDebug(limiter, t)
2021-09-29 12:18:51 +00:00
time.Sleep(25 * time.Millisecond)
}
for n := 0; n < 9; n++ {
limiter.Check(dummyTicker)
}
if limiter.Peek(dummyTicker) {
if ct, ok := limiter.Patrons.Get(dummyTicker.UniqueKey()); ok {
t.Errorf("Should not have been limited. Ratelimiter count: %d", ct)
} else {
t.Errorf("dummyTicker does not exist in ratelimiter at all!")
}
}
if !limiter.Check(dummyTicker) {
if ct, ok := limiter.Patrons.Get(dummyTicker.UniqueKey()); ok {
t.Errorf("Should have been limited. Ratelimiter count: %d", ct)
} else {
t.Errorf("dummyTicker does not exist in ratelimiter at all!")
}
}
//goland:noinspection GoBoolExpressions
if testDebug {
t.Logf("[Finished NewCustomLimiter] Times ratelimited: %d", limiter.GetGrandTotalRated())
}
2021-09-29 12:18:51 +00:00
stopDebug <- true
limiter = nil
}
func Test_NewDefaultStrictLimiter(t *testing.T) {
// DefaultBurst = 25
// DefaultWindow = 5
limiter := NewDefaultStrictLimiter()
//goland:noinspection GoBoolExpressions
if testDebug {
go watchDebug(limiter, t)
2021-09-29 12:18:51 +00:00
time.Sleep(25 * time.Millisecond)
}
for n := 0; n < 24; n++ {
limiter.Check(dummyTicker)
}
2021-09-29 12:18:51 +00:00
if limiter.Peek(dummyTicker) {
if ct, ok := limiter.Patrons.Get(dummyTicker.UniqueKey()); ok {
t.Errorf("Should not have been limited. Ratelimiter count: %d", ct)
} else {
t.Errorf("dummyTicker does not exist in ratelimiter at all!")
}
}
if !limiter.Check(dummyTicker) {
if ct, ok := limiter.Patrons.Get(dummyTicker.UniqueKey()); ok {
t.Errorf("Should have been limited. Ratelimiter count: %d, policy: %d", ct, limiter.Ruleset.Burst)
} else {
t.Errorf("dummyTicker does not exist in ratelimiter at all!")
}
}
//goland:noinspection GoBoolExpressions
if testDebug {
t.Logf("[Finished NewCustomLimiter] Times ratelimited: %d", limiter.GetGrandTotalRated())
}
2021-09-29 12:18:51 +00:00
stopDebug <- true
limiter = nil
}
// This test is only here for safety, if the package is not safe, this will often panic.
// We give this a healthy amount of padding in terms of our checks as this is far beyond the tolerances we expect during runtime.
// At the end of the day, not panicing here is passing.
func Test_ConcurrentSafetyTest(t *testing.T) {
var randos map[int]*randomPatron
randos = make(map[int]*randomPatron)
limiter := NewCustomLimiter(Policy{
Window: 240,
Burst: 5000,
Strict: true,
})
usedkeys := make(map[string]interface{})
2022-03-04 21:09:08 +00:00
const jobs = 1000
for n := 0; n != jobs ; n++ {
2021-09-29 12:18:51 +00:00
randos[n] = new(randomPatron)
ok := true
for ok {
randos[n].GenerateKey()
_, ok = usedkeys[randos[n].key]
if ok {
t.Log("collision")
}
}
}
t.Logf("generated %d Patrons with unique keys", len(randos))
2022-03-04 21:09:08 +00:00
doneChan := make(chan bool, 10)
finChan := make(chan bool, 10)
2021-09-29 12:18:51 +00:00
var finished = 0
for _, rp := range randos {
for n := 0; n != 5; n++ {
2022-03-04 21:09:08 +00:00
go func(randomp *randomPatron) {
limiter.Check(randomp)
limiter.Peek(randomp)
2021-09-29 12:18:51 +00:00
finChan <- true
2022-03-04 21:09:08 +00:00
}(rp)
2021-09-29 12:18:51 +00:00
}
}
var done = false
for {
select {
case <-finChan:
finished++
default:
2022-03-04 21:09:08 +00:00
if finished == (jobs * 5) {
2021-09-29 12:18:51 +00:00
done = true
break
}
}
if done {
go func() {
doneChan <- true
}()
break
}
}
<-doneChan
println("done")
for _, rp := range randos {
if limiter.Peek(rp) {
if ct, ok := limiter.Patrons.Get(rp.UniqueKey()); ok {
t.Logf("WARN: Should not have been limited. Ratelimiter count: %d, policy: %d", ct, limiter.Ruleset.Burst)
} else {
t.Errorf("randomPatron does not exist in ratelimiter at all!")
}
}
}
//goland:noinspection GoBoolExpressions
if testDebug {
t.Logf("[Finished StrictConcurrentStressTest] Times ratelimited: %d", limiter.GetGrandTotalRated())
}
}