Files
traefikoidc/cache_bounded_test.go
T

256 lines
5.0 KiB
Go

package traefikoidc
import (
"sync"
"testing"
"time"
)
func TestBoundedCache(t *testing.T) {
t.Run("basic operations", func(t *testing.T) {
cache := NewBoundedCache(10)
defer cache.Close()
// Set and get
cache.Set("key1", "value1", time.Minute)
val, found := cache.Get("key1")
if !found {
t.Error("Expected to find key1")
}
if val != "value1" {
t.Errorf("Expected value1, got %v", val)
}
// Delete
cache.Delete("key1")
_, found = cache.Get("key1")
if found {
t.Error("Expected key1 to be deleted")
}
})
t.Run("size limit enforcement", func(t *testing.T) {
cache := NewBoundedCache(5)
defer cache.Close()
// Add items up to the limit
for i := 0; i < 10; i++ {
key := string(rune('a' + i))
cache.Set(key, i, time.Minute)
}
// Cache should only have 5 items
if cache.Size() > 5 {
t.Errorf("Cache size %d exceeds limit of 5", cache.Size())
}
// Oldest items should be evicted
_, found := cache.Get("a")
if found {
t.Error("Expected oldest item 'a' to be evicted")
}
// Newest items should still be present
_, found = cache.Get("j")
if !found {
t.Error("Expected newest item 'j' to be present")
}
})
t.Run("LRU eviction", func(t *testing.T) {
cache := NewBoundedCache(3)
defer cache.Close()
// Add 3 items
cache.Set("a", 1, time.Minute)
cache.Set("b", 2, time.Minute)
cache.Set("c", 3, time.Minute)
// Access 'a' to make it recently used
cache.Get("a")
// Add new item, should evict 'b' (least recently used)
cache.Set("d", 4, time.Minute)
_, foundA := cache.Get("a")
_, foundB := cache.Get("b")
_, foundC := cache.Get("c")
_, foundD := cache.Get("d")
if !foundA {
t.Error("Expected 'a' to be present (recently accessed)")
}
if foundB {
t.Error("Expected 'b' to be evicted (LRU)")
}
if !foundC {
t.Error("Expected 'c' to be present")
}
if !foundD {
t.Error("Expected 'd' to be present (newest)")
}
})
t.Run("expiration", func(t *testing.T) {
cache := NewBoundedCache(10)
defer cache.Close()
// Set with short TTL
cache.Set("expire", "value", 100*time.Millisecond)
// Should exist initially
_, found := cache.Get("expire")
if !found {
t.Error("Expected to find key before expiration")
}
// Wait for expiration
time.Sleep(150 * time.Millisecond)
// Should be expired
_, found = cache.Get("expire")
if found {
t.Error("Expected key to be expired")
}
})
t.Run("concurrent access", func(t *testing.T) {
cache := NewBoundedCache(100)
defer cache.Close()
var wg sync.WaitGroup
// Concurrent writes
for i := 0; i < 50; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := string(rune('a' + (id % 26)))
cache.Set(key, id, time.Minute)
}(i)
}
// Concurrent reads
for i := 0; i < 50; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := string(rune('a' + (id % 26)))
cache.Get(key)
}(i)
}
wg.Wait()
// Verify cache is still functional
cache.Set("test", "value", time.Minute)
val, found := cache.Get("test")
if !found || val != "value" {
t.Error("Cache not functional after concurrent access")
}
})
t.Run("clear operation", func(t *testing.T) {
cache := NewBoundedCache(10)
defer cache.Close()
// Add items
for i := 0; i < 5; i++ {
cache.Set(string(rune('a'+i)), i, time.Minute)
}
// Clear cache
cache.Clear()
if cache.Size() != 0 {
t.Errorf("Expected size 0 after clear, got %d", cache.Size())
}
// Verify items are gone
_, found := cache.Get("a")
if found {
t.Error("Expected cache to be empty after clear")
}
})
t.Run("stats", func(t *testing.T) {
cache := NewBoundedCache(10)
defer cache.Close()
// Add some items
for i := 0; i < 5; i++ {
cache.Set(string(rune('a'+i)), i, time.Minute)
}
stats := cache.GetStats()
if stats["max_size"] != 10 {
t.Errorf("Expected max_size 10, got %v", stats["max_size"])
}
if size, ok := stats["size"].(int64); !ok || size != 5 {
t.Errorf("Expected size 5, got %v (type: %T)", stats["size"], stats["size"])
}
})
}
func TestBoundedCacheRaceConditions(t *testing.T) {
if testing.Short() {
t.Skip("Skipping race condition test in short mode")
}
cache := NewBoundedCache(50)
defer cache.Close()
var wg sync.WaitGroup
// Concurrent operations
for i := 0; i < 100; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := string(rune('a' + (id % 26)))
// Mix of operations
cache.Set(key, id, time.Second)
cache.Get(key)
if id%10 == 0 {
cache.Delete(key)
}
if id%20 == 0 {
cache.Clear()
}
cache.GetStats()
}(i)
}
wg.Wait()
}
func BenchmarkBoundedCache(b *testing.B) {
cache := NewBoundedCache(1000)
defer cache.Close()
b.Run("Set", func(b *testing.B) {
for i := 0; i < b.N; i++ {
key := string(rune(i % 1000))
cache.Set(key, i, time.Minute)
}
})
b.Run("Get", func(b *testing.B) {
// Pre-populate
for i := 0; i < 1000; i++ {
cache.Set(string(rune(i)), i, time.Minute)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := string(rune(i % 1000))
cache.Get(key)
}
})
}