2021-09-28 08:24:04 +00:00
|
|
|
package rate5
|
|
|
|
|
|
|
|
import (
|
2021-09-29 12:18:51 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/binary"
|
2021-09-28 08:24:04 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
dummyTicker *ticker
|
|
|
|
testDebug = true
|
2021-09-29 12:18:51 +00:00
|
|
|
|
|
|
|
stopDebug chan bool
|
2021-09-28 08:24:04 +00:00
|
|
|
)
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-09-28 08:24:04 +00:00
|
|
|
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
|
2021-09-28 08:24:04 +00:00
|
|
|
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)
|
2021-09-28 08:24:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2021-09-28 08:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2021-09-28 08:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2021-09-28 08:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for n := 0; n < 24; n++ {
|
|
|
|
limiter.Check(dummyTicker)
|
|
|
|
}
|
2021-09-29 12:18:51 +00:00
|
|
|
|
2021-09-28 08:24:04 +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())
|
|
|
|
}
|
2021-09-28 08:24:04 +00:00
|
|
|
}
|