Spaces:
Build error
Build error
| package cache_test | |
| import ( | |
| "context" | |
| "testing" | |
| "time" | |
| "github.com/AmaniQuery/amaniquery/internal/cache" | |
| ) | |
| // TestLRUCache_SetGet verifies basic set/get operations | |
| func TestLRUCache_SetGet(t *testing.T) { | |
| lru := cache.NewLRUCache(100) | |
| // Test set and get | |
| lru.Set("key1", []byte("value1")) | |
| value, found := lru.Get("key1") | |
| if !found { | |
| t.Fatal("Expected to find key1") | |
| } | |
| if string(value) != "value1" { | |
| t.Errorf("Expected 'value1', got '%s'", string(value)) | |
| } | |
| // Test missing key | |
| _, found = lru.Get("nonexistent") | |
| if found { | |
| t.Error("Expected not to find nonexistent key") | |
| } | |
| } | |
| // TestLRUCache_Update verifies updating existing keys | |
| func TestLRUCache_Update(t *testing.T) { | |
| lru := cache.NewLRUCache(100) | |
| lru.Set("key1", []byte("original")) | |
| lru.Set("key1", []byte("updated")) | |
| value, found := lru.Get("key1") | |
| if !found { | |
| t.Fatal("Expected to find key1") | |
| } | |
| if string(value) != "updated" { | |
| t.Errorf("Expected 'updated', got '%s'", string(value)) | |
| } | |
| } | |
| // TestLRUCache_Eviction verifies LRU eviction when over capacity | |
| func TestLRUCache_Eviction(t *testing.T) { | |
| lru := cache.NewLRUCache(3) | |
| // Fill cache to capacity | |
| lru.Set("key1", []byte("value1")) | |
| lru.Set("key2", []byte("value2")) | |
| lru.Set("key3", []byte("value3")) | |
| // Access key1 to make it recently used | |
| lru.Get("key1") | |
| // Add new key, should evict key2 (least recently used) | |
| lru.Set("key4", []byte("value4")) | |
| // key2 should be evicted | |
| _, found := lru.Get("key2") | |
| if found { | |
| t.Error("Expected key2 to be evicted") | |
| } | |
| // key1 should still exist (was accessed recently) | |
| _, found = lru.Get("key1") | |
| if !found { | |
| t.Error("Expected key1 to still exist") | |
| } | |
| // key3 and key4 should exist | |
| _, found = lru.Get("key3") | |
| if !found { | |
| t.Error("Expected key3 to exist") | |
| } | |
| _, found = lru.Get("key4") | |
| if !found { | |
| t.Error("Expected key4 to exist") | |
| } | |
| } | |
| // TestLRUCache_Delete verifies deletion operations | |
| func TestLRUCache_Delete(t *testing.T) { | |
| lru := cache.NewLRUCache(100) | |
| lru.Set("key1", []byte("value1")) | |
| lru.Set("key2", []byte("value2")) | |
| lru.Delete("key1") | |
| _, found := lru.Get("key1") | |
| if found { | |
| t.Error("Expected key1 to be deleted") | |
| } | |
| // key2 should still exist | |
| _, found = lru.Get("key2") | |
| if !found { | |
| t.Error("Expected key2 to still exist") | |
| } | |
| } | |
| // TestLRUCache_DeleteNonexistent verifies deleting nonexistent keys doesn't panic | |
| func TestLRUCache_DeleteNonexistent(t *testing.T) { | |
| lru := cache.NewLRUCache(100) | |
| // Should not panic | |
| lru.Delete("nonexistent") | |
| } | |
| // TestCacheMissError tests the error type | |
| func TestCacheMissError(t *testing.T) { | |
| err := cache.ErrCacheMiss | |
| if err.Error() != "cache miss" { | |
| t.Errorf("Expected 'cache miss', got '%s'", err.Error()) | |
| } | |
| } | |
| // TestMultiTierCache_LocalOnly tests cache without Redis connection | |
| func TestMultiTierCache_LocalOnly(t *testing.T) { | |
| // Create cache with invalid Redis URL to ensure Redis is not used | |
| cfg := cache.Config{ | |
| RedisURL: "redis://invalid:6379", // Will fail connection | |
| LocalSize: 100, | |
| TTL: time.Hour, | |
| MaxRetries: 1, | |
| PoolSize: 1, | |
| } | |
| c, err := cache.New(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to create cache: %v", err) | |
| } | |
| defer c.Close() | |
| ctx := context.Background() | |
| // Set and get should work with local cache | |
| err = c.Set(ctx, "key1", []byte("value1"), 0) | |
| if err != nil { | |
| t.Fatalf("Set failed: %v", err) | |
| } | |
| value, err := c.Get(ctx, "key1") | |
| if err != nil { | |
| t.Fatalf("Get failed: %v", err) | |
| } | |
| if string(value) != "value1" { | |
| t.Errorf("Expected 'value1', got '%s'", string(value)) | |
| } | |
| } | |
| // TestMultiTierCache_CacheMiss tests cache miss behavior | |
| func TestMultiTierCache_CacheMiss(t *testing.T) { | |
| cfg := cache.Config{ | |
| RedisURL: "redis://invalid:6379", | |
| LocalSize: 100, | |
| TTL: time.Hour, | |
| MaxRetries: 1, | |
| PoolSize: 1, | |
| } | |
| c, err := cache.New(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to create cache: %v", err) | |
| } | |
| defer c.Close() | |
| ctx := context.Background() | |
| _, err = c.Get(ctx, "nonexistent") | |
| if err == nil { | |
| t.Error("Expected cache miss error") | |
| } | |
| } | |
| // TestMultiTierCache_Delete tests deletion | |
| func TestMultiTierCache_Delete(t *testing.T) { | |
| cfg := cache.Config{ | |
| RedisURL: "redis://invalid:6379", | |
| LocalSize: 100, | |
| TTL: time.Hour, | |
| MaxRetries: 1, | |
| PoolSize: 1, | |
| } | |
| c, err := cache.New(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to create cache: %v", err) | |
| } | |
| defer c.Close() | |
| ctx := context.Background() | |
| // Set then delete | |
| c.Set(ctx, "key1", []byte("value1"), 0) | |
| err = c.Delete(ctx, "key1") | |
| if err != nil { | |
| t.Fatalf("Delete failed: %v", err) | |
| } | |
| // Should be gone | |
| _, err = c.Get(ctx, "key1") | |
| if err == nil { | |
| t.Error("Expected cache miss after delete") | |
| } | |
| } | |
| // TestMultiTierCache_JSON tests JSON operations | |
| func TestMultiTierCache_JSON(t *testing.T) { | |
| cfg := cache.Config{ | |
| RedisURL: "redis://invalid:6379", | |
| LocalSize: 100, | |
| TTL: time.Hour, | |
| MaxRetries: 1, | |
| PoolSize: 1, | |
| } | |
| c, err := cache.New(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to create cache: %v", err) | |
| } | |
| defer c.Close() | |
| ctx := context.Background() | |
| type testData struct { | |
| Name string `json:"name"` | |
| Value int `json:"value"` | |
| } | |
| original := testData{Name: "test", Value: 42} | |
| err = c.SetJSON(ctx, "json-key", original, 0) | |
| if err != nil { | |
| t.Fatalf("SetJSON failed: %v", err) | |
| } | |
| var result testData | |
| err = c.GetJSON(ctx, "json-key", &result) | |
| if err != nil { | |
| t.Fatalf("GetJSON failed: %v", err) | |
| } | |
| if result.Name != original.Name || result.Value != original.Value { | |
| t.Errorf("JSON mismatch: expected %+v, got %+v", original, result) | |
| } | |
| } | |
| // TestMultiTierCache_Metrics tests metrics tracking | |
| func TestMultiTierCache_Metrics(t *testing.T) { | |
| cfg := cache.Config{ | |
| RedisURL: "redis://invalid:6379", | |
| LocalSize: 100, | |
| TTL: time.Hour, | |
| MaxRetries: 1, | |
| PoolSize: 1, | |
| } | |
| c, err := cache.New(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to create cache: %v", err) | |
| } | |
| defer c.Close() | |
| ctx := context.Background() | |
| // Set and get to generate metrics | |
| c.Set(ctx, "key1", []byte("value1"), 0) | |
| c.Get(ctx, "key1") // Hit | |
| c.Get(ctx, "key2") // Miss | |
| metrics := c.GetMetrics() | |
| if metrics.LocalHits != 1 { | |
| t.Errorf("Expected 1 local hit, got %d", metrics.LocalHits) | |
| } | |
| if metrics.LocalMisses != 1 { | |
| t.Errorf("Expected 1 local miss, got %d", metrics.LocalMisses) | |
| } | |
| } | |
| // BenchmarkLRUCache_Set benchmarks LRU set operations | |
| func BenchmarkLRUCache_Set(b *testing.B) { | |
| lru := cache.NewLRUCache(10000) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| lru.Set("key"+string(rune(i%1000)), []byte("value")) | |
| } | |
| } | |
| // BenchmarkLRUCache_Get benchmarks LRU get operations | |
| func BenchmarkLRUCache_Get(b *testing.B) { | |
| lru := cache.NewLRUCache(10000) | |
| // Pre-populate | |
| for i := 0; i < 1000; i++ { | |
| lru.Set("key"+string(rune(i)), []byte("value")) | |
| } | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| lru.Get("key" + string(rune(i%1000))) | |
| } | |
| } | |