mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
Weekend fun with further optimisations.
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user