129 lines
4 KiB
Go
129 lines
4 KiB
Go
package gocache
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCache_StartJanitor(t *testing.T) {
|
|
cache := NewCache()
|
|
cache.SetWithTTL("1", "1", time.Nanosecond)
|
|
if cacheSize := cache.Count(); cacheSize != 1 {
|
|
t.Errorf("expected cacheSize to be 1, but was %d", cacheSize)
|
|
}
|
|
err := cache.StartJanitor()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cache.StopJanitor()
|
|
time.Sleep(JanitorMinShiftBackOff * 2)
|
|
if cacheSize := cache.Count(); cacheSize != 0 {
|
|
t.Errorf("expected cacheSize to be 0, but was %d", cacheSize)
|
|
}
|
|
}
|
|
|
|
func TestCache_StartJanitorWhenAlreadyStarted(t *testing.T) {
|
|
cache := NewCache()
|
|
if err := cache.StartJanitor(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := cache.StartJanitor(); err == nil {
|
|
t.Fatal("expected StartJanitor to return an error, because the janitor is already started")
|
|
}
|
|
cache.StopJanitor()
|
|
}
|
|
|
|
func TestCache_StopJanitor(t *testing.T) {
|
|
cache := NewCache()
|
|
_ = cache.StartJanitor()
|
|
if cache.stopJanitor == nil {
|
|
t.Error("starting the janitor should've initialized cache.stopJanitor")
|
|
}
|
|
cache.StopJanitor()
|
|
if cache.stopJanitor != nil {
|
|
t.Error("stopping the janitor should've set cache.stopJanitor to nil")
|
|
}
|
|
// Check if stopping the janitor even though it's already stopped causes a panic
|
|
cache.StopJanitor()
|
|
}
|
|
|
|
func TestJanitor(t *testing.T) {
|
|
cache := NewCache().WithMaxSize(3 * JanitorMaxIterationsPerShift)
|
|
defer cache.Clear()
|
|
for i := 0; i < 3*JanitorMaxIterationsPerShift; i++ {
|
|
if i < JanitorMaxIterationsPerShift && i%2 == 0 {
|
|
cache.SetWithTTL(fmt.Sprintf("%d", i), "value", time.Millisecond)
|
|
} else {
|
|
cache.SetWithTTL(fmt.Sprintf("%d", i), "value", time.Hour)
|
|
}
|
|
}
|
|
cacheSize := cache.Count()
|
|
err := cache.StartJanitor()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cache.StopJanitor()
|
|
time.Sleep(JanitorMinShiftBackOff * 4)
|
|
if cacheSize <= cache.Count() {
|
|
t.Error("The janitor should be deleting expired cache entries")
|
|
}
|
|
cacheSize = cache.Count()
|
|
time.Sleep(JanitorMinShiftBackOff * 4)
|
|
if cacheSize <= cache.Count() {
|
|
t.Error("The janitor should be deleting expired cache entries")
|
|
}
|
|
cacheSize = cache.Count()
|
|
time.Sleep(JanitorMinShiftBackOff * 4)
|
|
if cacheSize <= cache.Count() {
|
|
t.Error("The janitor should be deleting expired cache entries")
|
|
}
|
|
}
|
|
|
|
func TestJanitorIsLoopingProperly(t *testing.T) {
|
|
cache := NewCache().WithMaxSize(JanitorMaxIterationsPerShift + 3)
|
|
defer cache.Clear()
|
|
for i := 0; i < JanitorMaxIterationsPerShift; i++ {
|
|
cache.SetWithTTL(fmt.Sprintf("%d", i), "value", time.Hour)
|
|
}
|
|
cache.SetWithTTL("key-to-expire-1", "value", JanitorMinShiftBackOff*2)
|
|
cache.SetWithTTL("key-to-expire-2", "value", JanitorMinShiftBackOff*2)
|
|
cache.SetWithTTL("key-to-expire-3", "value", JanitorMinShiftBackOff*2)
|
|
err := cache.StartJanitor()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cache.StopJanitor()
|
|
if cache.Count() != JanitorMaxIterationsPerShift+3 {
|
|
t.Error("The janitor shouldn't have had enough time to remove anything from the cache yet", cache.Count())
|
|
}
|
|
const timeout = JanitorMinShiftBackOff * 20
|
|
threeKeysExpiredWithinOneSecond := false
|
|
for start := time.Now(); time.Since(start) < timeout; {
|
|
if cache.Stats().ExpiredKeys == 3 {
|
|
threeKeysExpiredWithinOneSecond = true
|
|
break
|
|
}
|
|
time.Sleep(JanitorMinShiftBackOff)
|
|
}
|
|
if !threeKeysExpiredWithinOneSecond {
|
|
t.Error("expected 3 keys to expire within 1 second")
|
|
}
|
|
if cache.Count() != JanitorMaxIterationsPerShift {
|
|
t.Error("The janitor should've deleted 3 entries")
|
|
}
|
|
}
|
|
|
|
func TestJanitorDoesNotThrowATantrumWhenThereIsNothingToClean(t *testing.T) {
|
|
cache := NewCache()
|
|
start := time.Now()
|
|
_ = cache.StartJanitor()
|
|
defer cache.StopJanitor()
|
|
time.Sleep(JanitorMaxShiftBackOff * 3)
|
|
// Technically, if the janitor doesn't backoff properly, the sleep above is likely to take more time than the sleep
|
|
// below because it would be eating up the CPU.
|
|
// This is a far-fetched test, but it's a good sanity check.
|
|
if time.Since(start) > JanitorMaxShiftBackOff*4 {
|
|
t.Error("The janitor should've backed off and prevented CPU usage from throttling the application")
|
|
}
|
|
}
|