Files
traefikoidc/cache_optimized_coverage_test.go
T

374 lines
9.5 KiB
Go

package traefikoidc
import (
"runtime"
"strings"
"sync"
"testing"
"time"
)
// TestOptimizedCacheBasicOperations tests basic cache operations
func TestOptimizedCacheBasicOperations(t *testing.T) {
cache := NewOptimizedCache()
// Test Set and Get
cache.Set("key1", "value1", 10*time.Minute)
value, found := cache.Get("key1")
if !found {
t.Error("Expected to find key1")
}
if value != "value1" {
t.Errorf("Expected 'value1', got '%v'", value)
}
// Test Get non-existent key
_, found = cache.Get("nonexistent")
if found {
t.Error("Expected not to find nonexistent key")
}
// Test Delete
cache.Delete("key1")
_, found = cache.Get("key1")
if found {
t.Error("Expected key1 to be deleted")
}
}
// TestOptimizedCacheExpiration tests cache entry expiration
func TestOptimizedCacheExpiration(t *testing.T) {
cache := NewOptimizedCache()
// Test immediate expiration
cache.Set("expired_key", "value", 1*time.Millisecond)
// Wait for expiration
time.Sleep(10 * time.Millisecond)
_, found := cache.Get("expired_key")
if found {
t.Error("Expected expired key not to be found")
}
// Test non-expiring entry (expiration = 0)
cache.Set("permanent_key", "permanent_value", 0)
value, found := cache.Get("permanent_key")
if !found {
t.Error("Expected permanent key to be found")
}
if value != "permanent_value" {
t.Errorf("Expected 'permanent_value', got '%v'", value)
}
}
// TestOptimizedCacheLRUEviction tests LRU eviction behavior
func TestOptimizedCacheLRUEviction(t *testing.T) {
// Create small cache to trigger eviction
logger := newNoOpLogger()
config := OptimizedCacheConfig{
MaxSize: 3,
Logger: logger,
EnableMemoryLimit: false, // Disable memory limit for LRU test
}
cache := NewOptimizedCacheWithConfig(config) // Max 3 items
// Fill cache to capacity
cache.Set("key1", "value1", 10*time.Minute)
cache.Set("key2", "value2", 10*time.Minute)
cache.Set("key3", "value3", 10*time.Minute)
// Access key1 to make it most recently used
cache.Get("key1")
// Add another item, should evict key2 (least recently used)
cache.Set("key4", "value4", 10*time.Minute)
// key2 should be evicted
_, found := cache.Get("key2")
if found {
t.Error("Expected key2 to be evicted")
}
// key1 should still exist (was recently accessed)
_, found = cache.Get("key1")
if !found {
t.Error("Expected key1 to still exist")
}
// key3 and key4 should exist
_, found = cache.Get("key3")
if !found {
t.Error("Expected key3 to still exist")
}
_, found = cache.Get("key4")
if !found {
t.Error("Expected key4 to exist")
}
}
// TestOptimizedCacheMemoryPressure tests memory-based eviction
func TestOptimizedCacheMemoryPressure(t *testing.T) {
logger := newNoOpLogger()
config := OptimizedCacheConfig{
MaxSize: 1000,
MaxMemoryBytes: 1,
Logger: logger,
EnableMemoryLimit: true,
}
cache := NewOptimizedCacheWithConfig(config) // 1 MB memory limit
// Create large values to trigger memory pressure
largeValue := strings.Repeat("a", 256*1024) // 256KB each
// Add several large values
cache.Set("large1", largeValue, 10*time.Minute)
cache.Set("large2", largeValue, 10*time.Minute)
cache.Set("large3", largeValue, 10*time.Minute)
cache.Set("large4", largeValue, 10*time.Minute)
cache.Set("large5", largeValue, 10*time.Minute) // This should trigger eviction
// Force garbage collection to get accurate memory reading
runtime.GC()
// Check that some entries were evicted due to memory pressure
count := 0
for i := 1; i <= 5; i++ {
if _, found := cache.Get(formatString("large%d", i)); found {
count++
}
}
// Should have fewer than 5 items due to memory pressure eviction
if count >= 5 {
t.Errorf("Expected some items to be evicted due to memory pressure, but found %d items", count)
}
}
// TestOptimizedCacheCleanup tests manual cleanup functionality
func TestOptimizedCacheCleanup(t *testing.T) {
cache := NewOptimizedCache()
// Add expired and non-expired items
cache.Set("expired1", "value1", 1*time.Millisecond)
cache.Set("expired2", "value2", 1*time.Millisecond)
cache.Set("valid", "value", 10*time.Minute)
// Wait for expiration
time.Sleep(10 * time.Millisecond)
// Manual cleanup should remove expired items
cache.Cleanup()
// Expired items should be gone
_, found := cache.Get("expired1")
if found {
t.Error("Expected expired1 to be cleaned up")
}
_, found = cache.Get("expired2")
if found {
t.Error("Expected expired2 to be cleaned up")
}
// Valid item should remain
_, found = cache.Get("valid")
if !found {
t.Error("Expected valid item to remain after cleanup")
}
}
// TestOptimizedCacheConcurrency tests thread safety
func TestOptimizedCacheConcurrency(t *testing.T) {
cache := NewOptimizedCache()
var wg sync.WaitGroup
// Number of goroutines for each operation type
numGoroutines := 10
numOperations := 100
// Test concurrent writes
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := formatString("write_%d_%d", id, j)
cache.Set(key, formatString("value_%d_%d", id, j), 10*time.Minute)
}
}(i)
}
// Test concurrent reads
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := formatString("write_%d_%d", id, j)
cache.Get(key) // Don't care about result, just testing concurrency
}
}(i)
}
// Test concurrent deletes
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := formatString("delete_%d_%d", id, j)
cache.Set(key, "value", 10*time.Minute)
cache.Delete(key)
}
}(i)
}
// Test concurrent cleanup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
cache.Cleanup()
time.Sleep(1 * time.Millisecond)
}
}()
wg.Wait()
// If we reach here without deadlock or panic, concurrency test passed
t.Log("Concurrency test completed successfully")
}
// TestOptimizedCacheEdgeCases tests edge cases and error conditions
func TestOptimizedCacheEdgeCases(t *testing.T) {
cache := NewOptimizedCache()
// Test empty key
cache.Set("", "empty_key_value", 10*time.Minute)
value, found := cache.Get("")
if !found || value != "empty_key_value" {
t.Error("Expected to handle empty key correctly")
}
// Test nil value
cache.Set("nil_key", nil, 10*time.Minute)
value, found = cache.Get("nil_key")
if !found || value != nil {
t.Error("Expected to handle nil value correctly")
}
// Test overwriting existing key
cache.Set("overwrite", "original", 10*time.Minute)
cache.Set("overwrite", "new_value", 10*time.Minute)
value, found = cache.Get("overwrite")
if !found || value != "new_value" {
t.Error("Expected key to be overwritten with new value")
}
// Test delete non-existent key (should not panic)
cache.Delete("nonexistent")
// Test very long key
longKey := strings.Repeat("a", 1000)
cache.Set(longKey, "long_key_value", 10*time.Minute)
value, found = cache.Get(longKey)
if !found || value != "long_key_value" {
t.Error("Expected to handle very long key correctly")
}
}
// TestOptimizedCacheWithDifferentValueTypes tests cache with various value types
func TestOptimizedCacheWithDifferentValueTypes(t *testing.T) {
cache := NewOptimizedCache()
// Test string value
cache.Set("string", "test_string", 10*time.Minute)
// Test int value
cache.Set("int", 42, 10*time.Minute)
// Test slice value
cache.Set("slice", []string{"a", "b", "c"}, 10*time.Minute)
// Test map value
cache.Set("map", map[string]int{"key1": 1, "key2": 2}, 10*time.Minute)
// Test struct value
type TestStruct struct {
Name string
Age int
}
cache.Set("struct", TestStruct{Name: "John", Age: 30}, 10*time.Minute)
// Verify all types can be retrieved correctly
if val, found := cache.Get("string"); !found || val != "test_string" {
t.Error("Failed to retrieve string value")
}
if val, found := cache.Get("int"); !found || val != 42 {
t.Error("Failed to retrieve int value")
}
if val, found := cache.Get("slice"); !found {
t.Error("Failed to retrieve slice value")
} else if slice, ok := val.([]string); !ok || len(slice) != 3 || slice[0] != "a" {
t.Error("Retrieved slice value is incorrect")
}
if val, found := cache.Get("map"); !found {
t.Error("Failed to retrieve map value")
} else if mapVal, ok := val.(map[string]int); !ok || mapVal["key1"] != 1 {
t.Error("Retrieved map value is incorrect")
}
if val, found := cache.Get("struct"); !found {
t.Error("Failed to retrieve struct value")
} else if structVal, ok := val.(TestStruct); !ok || structVal.Name != "John" || structVal.Age != 30 {
t.Error("Retrieved struct value is incorrect")
}
}
// Helper to create a formatted string key
func formatString(format string, args ...interface{}) string {
// Simple sprintf implementation for tests
result := format
for _, arg := range args {
if strings.Contains(result, "%d") {
if intVal, ok := arg.(int); ok {
result = strings.Replace(result, "%d", intToString(intVal), 1)
}
} else if strings.Contains(result, "%s") {
if strVal, ok := arg.(string); ok {
result = strings.Replace(result, "%s", strVal, 1)
}
}
}
return result
}
// Helper to convert int to string
func intToString(i int) string {
if i == 0 {
return "0"
}
negative := i < 0
if negative {
i = -i
}
var result []byte
for i > 0 {
result = append([]byte{byte('0' + (i % 10))}, result...)
i /= 10
}
if negative {
result = append([]byte{'-'}, result...)
}
return string(result)
}