mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
782 lines
21 KiB
Go
782 lines
21 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestCacheBasicOperations tests the fundamental cache operations using table-driven tests
|
|
func TestCacheBasicOperations(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeQuick) {
|
|
return
|
|
}
|
|
|
|
runner := NewTestSuiteRunner()
|
|
runner.SetTimeout(10 * time.Second)
|
|
|
|
testCases := []TableTestCase{
|
|
{
|
|
Name: "Set and Get - Valid Key",
|
|
Description: "Basic set and get operation with valid data",
|
|
Setup: func(t *testing.T) error {
|
|
return nil
|
|
},
|
|
Teardown: func(t *testing.T) error {
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Name: "Get Non-existent Key",
|
|
Description: "Getting a key that doesn't exist should return false",
|
|
},
|
|
{
|
|
Name: "Set and Get - Zero Value",
|
|
Description: "Test caching zero values like empty strings, nil, zero numbers",
|
|
},
|
|
{
|
|
Name: "Delete Existing Key",
|
|
Description: "Delete should remove the key and make it unavailable",
|
|
},
|
|
{
|
|
Name: "Delete Non-existent Key",
|
|
Description: "Deleting a non-existent key should not cause errors",
|
|
},
|
|
}
|
|
|
|
// Execute basic operation tests with custom logic
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
switch tc.Name {
|
|
case "Set and Get - Valid Key":
|
|
testValue := "test-value-123"
|
|
cache.Set("test-key", testValue, time.Hour)
|
|
|
|
result, found := cache.Get("test-key")
|
|
assert.True(t, found, "Expected key to be found")
|
|
assert.Equal(t, testValue, result, "Expected values to match")
|
|
|
|
case "Get Non-existent Key":
|
|
result, found := cache.Get("non-existent")
|
|
assert.False(t, found, "Expected key not to be found")
|
|
assert.Nil(t, result, "Expected nil result for non-existent key")
|
|
|
|
case "Set and Get - Zero Value":
|
|
// Test various zero values
|
|
cache.Set("empty-string", "", time.Hour)
|
|
cache.Set("zero-int", 0, time.Hour)
|
|
cache.Set("nil-value", nil, time.Hour)
|
|
cache.Set("false-value", false, time.Hour)
|
|
|
|
val, found := cache.Get("empty-string")
|
|
assert.True(t, found)
|
|
assert.Equal(t, "", val)
|
|
|
|
val, found = cache.Get("zero-int")
|
|
assert.True(t, found)
|
|
assert.Equal(t, 0, val)
|
|
|
|
val, found = cache.Get("nil-value")
|
|
assert.True(t, found)
|
|
assert.Nil(t, val)
|
|
|
|
val, found = cache.Get("false-value")
|
|
assert.True(t, found)
|
|
assert.Equal(t, false, val)
|
|
|
|
case "Delete Existing Key":
|
|
cache.Set("delete-me", "value", time.Hour)
|
|
_, found := cache.Get("delete-me")
|
|
assert.True(t, found, "Key should exist before deletion")
|
|
|
|
cache.Delete("delete-me")
|
|
_, found = cache.Get("delete-me")
|
|
assert.False(t, found, "Key should not exist after deletion")
|
|
|
|
case "Delete Non-existent Key":
|
|
// Should not panic or cause errors
|
|
cache.Delete("does-not-exist")
|
|
// If we reach here, the test passes
|
|
assert.True(t, true)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCacheExpiration tests TTL functionality with edge cases
|
|
func TestCacheExpiration(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeQuick) {
|
|
return
|
|
}
|
|
edgeGen := NewEdgeCaseGenerator()
|
|
expirationTimes := []time.Duration{
|
|
10 * time.Millisecond, // Very short but still measurable
|
|
50 * time.Millisecond, // Short
|
|
100 * time.Millisecond, // Medium short
|
|
500 * time.Millisecond, // Medium
|
|
1 * time.Second, // Long
|
|
time.Hour, // Very long
|
|
}
|
|
|
|
t.Run("Expiration Times", func(t *testing.T) {
|
|
for _, expiration := range expirationTimes {
|
|
expiration := expiration
|
|
t.Run(fmt.Sprintf("Expiration_%s", expiration), func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
key := fmt.Sprintf("test-key-%s", expiration)
|
|
value := fmt.Sprintf("test-value-%s", expiration)
|
|
|
|
cache.Set(key, value, expiration)
|
|
|
|
// Immediate retrieval should work
|
|
result, found := cache.Get(key)
|
|
assert.True(t, found, "Key should be found immediately after set")
|
|
assert.Equal(t, value, result)
|
|
|
|
// Wait for expiration
|
|
if expiration < time.Second {
|
|
time.Sleep(expiration + 10*time.Millisecond)
|
|
} else {
|
|
// For long expiration times, don't actually wait
|
|
t.Skip("Skipping long expiration test to save time")
|
|
}
|
|
|
|
// Should be expired now
|
|
result, found = cache.Get(key)
|
|
assert.False(t, found, "Key should not be found after expiration")
|
|
assert.Nil(t, result)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("Expiration Edge Cases", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
// Test with edge case strings
|
|
edgeCases := edgeGen.GenerateStringEdgeCases()
|
|
for i, testStr := range edgeCases {
|
|
if i > 5 { // Limit to first 5 to avoid too many tests
|
|
break
|
|
}
|
|
key := fmt.Sprintf("edge-key-%d", i)
|
|
cache.Set(key, testStr, 10*time.Millisecond)
|
|
}
|
|
|
|
// Wait for expiration
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// All should be expired
|
|
for i := 0; i < 6; i++ {
|
|
key := fmt.Sprintf("edge-key-%d", i)
|
|
_, found := cache.Get(key)
|
|
assert.False(t, found, "Edge case key %s should be expired", key)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCacheSize tests size management and eviction policies
|
|
func TestCacheSize(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeQuick) {
|
|
return
|
|
}
|
|
t.Run("SetMaxSize Eviction", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
originalMaxSize := 10
|
|
newMaxSize := 3
|
|
|
|
// Add more items than the new max size
|
|
for i := 0; i < originalMaxSize; i++ {
|
|
key := fmt.Sprintf("key%c", rune('A'+i))
|
|
cache.Set(key, i, time.Hour)
|
|
}
|
|
|
|
// Verify items were added
|
|
for i := 0; i < originalMaxSize; i++ {
|
|
key := fmt.Sprintf("key%c", rune('A'+i))
|
|
_, exists := cache.Get(key)
|
|
require.True(t, exists, "Expected key %s to exist before SetMaxSize", key)
|
|
}
|
|
|
|
// Change the max size to a smaller value
|
|
cache.SetMaxSize(newMaxSize)
|
|
|
|
// Count remaining items
|
|
count := 0
|
|
for i := 0; i < originalMaxSize; i++ {
|
|
key := fmt.Sprintf("key%c", rune('A'+i))
|
|
if _, exists := cache.Get(key); exists {
|
|
count++
|
|
}
|
|
}
|
|
|
|
// Check that the cache was reduced to the new max size
|
|
assert.LessOrEqual(t, count, newMaxSize, "Cache size %d exceeds new max size %d after SetMaxSize", count, newMaxSize)
|
|
|
|
// Check that the oldest items were evicted (keyA should be evicted)
|
|
_, exists := cache.Get("keyA")
|
|
assert.False(t, exists, "Expected oldest item 'keyA' to be evicted, but it still exists")
|
|
})
|
|
|
|
t.Run("Eviction With Mixed Expiration Times", func(t *testing.T) {
|
|
cache := NewCache()
|
|
cache.SetMaxSize(5)
|
|
defer cache.Close()
|
|
|
|
// Add items with different expiration times
|
|
cache.Set("expired1", "val1", -time.Hour) // Already expired
|
|
cache.Set("valid1", "val1", time.Hour) // Valid
|
|
cache.Set("expired2", "val2", -time.Hour) // Already expired
|
|
cache.Set("valid2", "val2", time.Hour) // Valid
|
|
cache.Set("valid3", "val3", time.Hour) // Valid
|
|
|
|
// Add one more to trigger eviction (should preferably evict expired items)
|
|
cache.Set("valid4", "val4", time.Hour)
|
|
|
|
// Count valid items (should be 4 valid ones)
|
|
validCount := 0
|
|
expiredCount := 0
|
|
keys := []string{"expired1", "valid1", "expired2", "valid2", "valid3", "valid4"}
|
|
|
|
for _, key := range keys {
|
|
if _, found := cache.Get(key); found {
|
|
if len(key) >= 7 && key[:7] == "expired" {
|
|
expiredCount++
|
|
} else {
|
|
validCount++
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.LessOrEqual(t, validCount+expiredCount, 5, "Total items should not exceed max size")
|
|
assert.LessOrEqual(t, expiredCount, 2, "Should prioritize evicting expired items")
|
|
})
|
|
}
|
|
|
|
// TestCacheConcurrency tests concurrent operations and thread safety
|
|
func TestCacheConcurrency(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeConcurrencyStress) {
|
|
return
|
|
}
|
|
t.Run("Concurrent Set and Get", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
const numGoroutines = 100
|
|
const operationsPerGoroutine = 50
|
|
var wg sync.WaitGroup
|
|
|
|
// Statistics tracking
|
|
var setOperations int64
|
|
var getOperations int64
|
|
var successfulGets int64
|
|
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
key := fmt.Sprintf("key-%d-%d", goroutineID, j)
|
|
value := fmt.Sprintf("value-%d-%d", goroutineID, j)
|
|
|
|
// Set operation
|
|
cache.Set(key, value, time.Minute)
|
|
atomic.AddInt64(&setOperations, 1)
|
|
|
|
// Get operation
|
|
if result, found := cache.Get(key); found {
|
|
atomic.AddInt64(&successfulGets, 1)
|
|
if result != value {
|
|
t.Errorf("Got unexpected value: expected %s, got %s", value, result)
|
|
}
|
|
}
|
|
atomic.AddInt64(&getOperations, 1)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
totalExpectedOps := int64(numGoroutines * operationsPerGoroutine)
|
|
assert.Equal(t, totalExpectedOps, atomic.LoadInt64(&setOperations), "All set operations should complete")
|
|
assert.Equal(t, totalExpectedOps, atomic.LoadInt64(&getOperations), "All get operations should complete")
|
|
|
|
// Most gets should succeed (allowing for some eviction)
|
|
successRate := float64(atomic.LoadInt64(&successfulGets)) / float64(atomic.LoadInt64(&getOperations))
|
|
assert.Greater(t, successRate, 0.7, "At least 70%% of gets should succeed")
|
|
})
|
|
|
|
t.Run("Concurrent Set and Delete", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
const numItems = 1000
|
|
var wg sync.WaitGroup
|
|
|
|
// Add items concurrently
|
|
wg.Add(numItems)
|
|
for i := 0; i < numItems; i++ {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
key := fmt.Sprintf("concurrent-key-%d", id)
|
|
cache.Set(key, fmt.Sprintf("value-%d", id), time.Minute)
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
// Delete items concurrently
|
|
wg.Add(numItems)
|
|
for i := 0; i < numItems; i++ {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
key := fmt.Sprintf("concurrent-key-%d", id)
|
|
cache.Delete(key)
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
// Verify all items are deleted
|
|
for i := 0; i < numItems; i++ {
|
|
key := fmt.Sprintf("concurrent-key-%d", i)
|
|
_, found := cache.Get(key)
|
|
assert.False(t, found, "Key %s should be deleted", key)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCacheUnifiedMemoryLeaks tests for memory leaks using unified test infrastructure
|
|
func TestCacheUnifiedMemoryLeaks(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeLeakDetection) {
|
|
return
|
|
}
|
|
runner := NewTestSuiteRunner()
|
|
|
|
memoryLeakTests := []MemoryLeakTestCase{
|
|
{
|
|
Name: "Cache Creation and Destruction",
|
|
Description: "Creating and destroying caches should not leak memory",
|
|
Iterations: 50,
|
|
MaxGoroutineGrowth: 2, // Allow some tolerance for test framework
|
|
MaxMemoryGrowthMB: 5.0,
|
|
GCBetweenRuns: true,
|
|
Operation: func() error {
|
|
cache := NewCache()
|
|
cache.Set("test", "value", time.Minute)
|
|
_, _ = cache.Get("test")
|
|
cache.Close()
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Name: "Heavy Cache Usage",
|
|
Description: "Heavy cache usage should not accumulate memory",
|
|
Iterations: 10,
|
|
MaxGoroutineGrowth: 2,
|
|
MaxMemoryGrowthMB: 10.0,
|
|
GCBetweenRuns: true,
|
|
Operation: func() error {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
// Add many items
|
|
for i := 0; i < 1000; i++ {
|
|
key := fmt.Sprintf("key-%d", i)
|
|
cache.Set(key, fmt.Sprintf("value-%d", i), time.Second)
|
|
}
|
|
|
|
// Access items randomly
|
|
for i := 0; i < 500; i++ {
|
|
key := fmt.Sprintf("key-%d", i%1000)
|
|
cache.Get(key)
|
|
}
|
|
|
|
// Delete some items
|
|
for i := 0; i < 250; i++ {
|
|
key := fmt.Sprintf("key-%d", i*2)
|
|
cache.Delete(key)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Name: "Cleanup Functionality",
|
|
Description: "Manual cleanup should free memory properly",
|
|
Iterations: 20,
|
|
MaxGoroutineGrowth: 1,
|
|
MaxMemoryGrowthMB: 3.0,
|
|
GCBetweenRuns: true,
|
|
Operation: func() error {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
// Add items that will expire
|
|
for i := 0; i < 100; i++ {
|
|
key := fmt.Sprintf("key-%d", i)
|
|
cache.Set(key, fmt.Sprintf("value-%d", i), -time.Hour) // Already expired
|
|
}
|
|
|
|
// Run cleanup
|
|
cache.Cleanup()
|
|
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
runner.RunMemoryLeakTests(t, memoryLeakTests)
|
|
}
|
|
|
|
// TestCachePerformance includes benchmarks and performance validation
|
|
func TestCachePerformance(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeExtended) {
|
|
return
|
|
}
|
|
t.Run("Basic Operations Performance", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
perfHelper := NewPerformanceTestHelper()
|
|
|
|
// Test Set performance
|
|
for i := 0; i < 1000; i++ {
|
|
key := fmt.Sprintf("perf-key-%d", i)
|
|
value := fmt.Sprintf("perf-value-%d", i)
|
|
|
|
perfHelper.Measure(func() {
|
|
cache.Set(key, value, time.Hour)
|
|
})
|
|
}
|
|
|
|
avgSetTime := perfHelper.GetAverageTime()
|
|
t.Logf("Average Set time: %v", avgSetTime)
|
|
assert.Less(t, avgSetTime.Nanoseconds(), int64(10*time.Microsecond), "Set operations should be fast")
|
|
|
|
// Reset and test Get performance
|
|
perfHelper.Reset()
|
|
for i := 0; i < 1000; i++ {
|
|
key := fmt.Sprintf("perf-key-%d", i)
|
|
|
|
perfHelper.Measure(func() {
|
|
cache.Get(key)
|
|
})
|
|
}
|
|
|
|
avgGetTime := perfHelper.GetAverageTime()
|
|
t.Logf("Average Get time: %v", avgGetTime)
|
|
assert.Less(t, avgGetTime.Nanoseconds(), int64(5*time.Microsecond), "Get operations should be very fast")
|
|
})
|
|
}
|
|
|
|
// TestCacheEdgeCases tests various edge cases and error conditions
|
|
func TestCacheEdgeCases(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeExtended) {
|
|
return
|
|
}
|
|
edgeGen := NewEdgeCaseGenerator()
|
|
|
|
t.Run("Edge Case Keys and Values", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
keys := edgeGen.GenerateStringEdgeCases()
|
|
values := edgeGen.GenerateStringEdgeCases()
|
|
|
|
// Test combinations of edge case keys and values
|
|
for i, key := range keys[:5] { // Limit to first 5 for performance
|
|
for j, value := range values[:3] { // Limit to first 3 for performance
|
|
testName := fmt.Sprintf("Key_%d_Value_%d", i, j)
|
|
t.Run(testName, func(t *testing.T) {
|
|
cache.Set(key, value, time.Minute)
|
|
result, found := cache.Get(key)
|
|
|
|
assert.True(t, found, "Should find edge case key")
|
|
assert.Equal(t, value, result, "Should get correct edge case value")
|
|
|
|
// Clean up
|
|
cache.Delete(key)
|
|
_, found = cache.Get(key)
|
|
assert.False(t, found, "Should not find deleted edge case key")
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Boundary Conditions", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
// Test with zero and negative TTL
|
|
cache.Set("zero-ttl", "value", 0)
|
|
cache.Set("negative-ttl", "value", -time.Hour)
|
|
|
|
// Should be immediately expired
|
|
_, found := cache.Get("zero-ttl")
|
|
assert.False(t, found, "Zero TTL should expire immediately")
|
|
|
|
_, found = cache.Get("negative-ttl")
|
|
assert.False(t, found, "Negative TTL should expire immediately")
|
|
|
|
// Test max size boundaries
|
|
cache.SetMaxSize(1)
|
|
cache.Set("first", "value1", time.Hour)
|
|
cache.Set("second", "value2", time.Hour)
|
|
|
|
// Should only have one item
|
|
firstFound := false
|
|
secondFound := false
|
|
if _, found := cache.Get("first"); found {
|
|
firstFound = true
|
|
}
|
|
if _, found := cache.Get("second"); found {
|
|
secondFound = true
|
|
}
|
|
|
|
assert.True(t, firstFound != secondFound, "Should have exactly one item when max size is 1")
|
|
})
|
|
|
|
t.Run("Invalid Operations", func(t *testing.T) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
// Test invalid max size
|
|
cache.SetMaxSize(0) // Should be ignored
|
|
cache.SetMaxSize(-10) // Should be ignored
|
|
|
|
// Cache should still work
|
|
cache.Set("test", "value", time.Hour)
|
|
result, found := cache.Get("test")
|
|
assert.True(t, found)
|
|
assert.Equal(t, "value", result)
|
|
})
|
|
}
|
|
|
|
// TestCacheLRUBehavior specifically tests the LRU (Least Recently Used) eviction policy
|
|
func TestCacheLRUBehavior(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeExtended) {
|
|
return
|
|
}
|
|
t.Run("LRU Eviction Order", func(t *testing.T) {
|
|
cache := NewCache()
|
|
cache.SetMaxSize(3)
|
|
defer cache.Close()
|
|
|
|
// Add items in order
|
|
cache.Set("first", "1", time.Hour)
|
|
cache.Set("second", "2", time.Hour)
|
|
cache.Set("third", "3", time.Hour)
|
|
|
|
// Access first item to make it recently used
|
|
cache.Get("first")
|
|
|
|
// Add fourth item, should evict "second" (least recently used)
|
|
cache.Set("fourth", "4", time.Hour)
|
|
|
|
// Verify eviction order
|
|
_, found := cache.Get("first")
|
|
assert.True(t, found, "First should still exist (recently accessed)")
|
|
|
|
_, found = cache.Get("second")
|
|
assert.False(t, found, "Second should be evicted (least recently used)")
|
|
|
|
_, found = cache.Get("third")
|
|
assert.True(t, found, "Third should still exist")
|
|
|
|
_, found = cache.Get("fourth")
|
|
assert.True(t, found, "Fourth should exist (just added)")
|
|
})
|
|
|
|
t.Run("LRU Update on Set", func(t *testing.T) {
|
|
cache := NewCache()
|
|
cache.SetMaxSize(3)
|
|
defer cache.Close()
|
|
|
|
// Add items
|
|
cache.Set("a", "1", time.Hour)
|
|
cache.Set("b", "2", time.Hour)
|
|
cache.Set("c", "3", time.Hour)
|
|
|
|
// Update existing item (should move to end of LRU)
|
|
cache.Set("a", "updated", time.Hour)
|
|
|
|
// Add new item, should evict "b" (now least recently used)
|
|
cache.Set("d", "4", time.Hour)
|
|
|
|
_, found := cache.Get("a")
|
|
assert.True(t, found, "A should still exist (recently updated)")
|
|
|
|
_, found = cache.Get("b")
|
|
assert.False(t, found, "B should be evicted")
|
|
|
|
_, found = cache.Get("c")
|
|
assert.True(t, found, "C should still exist")
|
|
|
|
_, found = cache.Get("d")
|
|
assert.True(t, found, "D should exist")
|
|
})
|
|
}
|
|
|
|
// BenchmarkCache provides performance benchmarks
|
|
func BenchmarkCache(b *testing.B) {
|
|
cache := NewCache()
|
|
defer cache.Close()
|
|
|
|
b.Run("Set", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
key := fmt.Sprintf("benchmark-key-%d", i)
|
|
cache.Set(key, fmt.Sprintf("value-%d", i), time.Hour)
|
|
}
|
|
})
|
|
|
|
b.Run("Get", func(b *testing.B) {
|
|
// Pre-populate cache
|
|
for i := 0; i < 1000; i++ {
|
|
key := fmt.Sprintf("benchmark-key-%d", i)
|
|
cache.Set(key, fmt.Sprintf("value-%d", i), time.Hour)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
key := fmt.Sprintf("benchmark-key-%d", i%1000)
|
|
cache.Get(key)
|
|
}
|
|
})
|
|
|
|
b.Run("Delete", func(b *testing.B) {
|
|
// Pre-populate cache
|
|
for i := 0; i < b.N; i++ {
|
|
key := fmt.Sprintf("benchmark-delete-key-%d", i)
|
|
cache.Set(key, fmt.Sprintf("value-%d", i), time.Hour)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
key := fmt.Sprintf("benchmark-delete-key-%d", i)
|
|
cache.Delete(key)
|
|
}
|
|
})
|
|
|
|
b.Run("Concurrent", func(b *testing.B) {
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
key := fmt.Sprintf("concurrent-key-%d", i)
|
|
value := fmt.Sprintf("concurrent-value-%d", i)
|
|
|
|
cache.Set(key, value, time.Minute)
|
|
cache.Get(key)
|
|
|
|
if i%10 == 0 {
|
|
cache.Delete(key)
|
|
}
|
|
|
|
i++
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// TestCache_Cleanup tests the specific cleanup functionality (converted from original test)
|
|
func TestCache_Cleanup(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeQuick) {
|
|
return
|
|
}
|
|
c := NewCache()
|
|
defer c.Close()
|
|
|
|
// Add some items with different expiration times
|
|
now := time.Now()
|
|
pastTime := now.Add(-1 * time.Hour) // Already expired
|
|
futureTime := now.Add(1 * time.Hour) // Not expired
|
|
|
|
c.Set("expired", "expired-value", pastTime.Sub(now))
|
|
c.Set("valid", "valid-value", futureTime.Sub(now))
|
|
|
|
// Call cleanup, which should only remove expired items
|
|
c.Cleanup()
|
|
|
|
// Check that only the expired item was removed
|
|
if _, exists := c.Get("expired"); exists {
|
|
t.Error("Expired item was not removed by Cleanup()")
|
|
}
|
|
|
|
if _, exists := c.Get("valid"); !exists {
|
|
t.Error("Valid item was incorrectly removed by Cleanup()")
|
|
}
|
|
}
|
|
|
|
// TestCache_SetMaxSize tests the specific max size functionality (converted from original test)
|
|
func TestCache_SetMaxSize(t *testing.T) {
|
|
config := GetTestConfig()
|
|
if config.ShouldSkipTest(t, TestTypeQuick) {
|
|
return
|
|
}
|
|
c := NewCache()
|
|
defer c.Close()
|
|
|
|
// Set a lower max size
|
|
originalMaxSize := 10
|
|
newMaxSize := 3
|
|
|
|
// Add more items than the new max size
|
|
for i := 0; i < originalMaxSize; i++ {
|
|
key := "key" + string(rune('A'+i))
|
|
c.Set(key, i, 1*time.Hour)
|
|
}
|
|
|
|
// Verify items were added by checking a few keys
|
|
for i := 0; i < originalMaxSize; i++ {
|
|
key := "key" + string(rune('A'+i))
|
|
if _, exists := c.Get(key); !exists {
|
|
t.Errorf("Expected key %s to exist before SetMaxSize", key)
|
|
}
|
|
}
|
|
|
|
// Change the max size to a smaller value
|
|
c.SetMaxSize(newMaxSize)
|
|
|
|
// Count remaining items
|
|
count := 0
|
|
for i := 0; i < originalMaxSize; i++ {
|
|
key := "key" + string(rune('A'+i))
|
|
if _, exists := c.Get(key); exists {
|
|
count++
|
|
}
|
|
}
|
|
|
|
// Check that the cache was reduced to the new max size
|
|
if count > newMaxSize {
|
|
t.Errorf("Cache size %d exceeds new max size %d after SetMaxSize", count, newMaxSize)
|
|
}
|
|
|
|
// Check that the oldest items were evicted (keyA should be evicted)
|
|
if _, exists := c.Get("keyA"); exists {
|
|
t.Error("Expected oldest item 'keyA' to be evicted, but it still exists")
|
|
}
|
|
}
|