Spaces:
Sleeping
Sleeping
Download internal/cache/cache_test.go from AmaniQuery/amaniquery-agent: direct link, hf CLI and curl.
- Browser
- Download file 9.77 kB
-
https://huggingface.co/spaces/AmaniQuery/amaniquery-agent/resolve/main/internal/cache/cache_test.go
- Command line
-
hf download hf://spaces/AmaniQuery/amaniquery-agent/internal/cache/cache_test.go
-
curl -L -o cache_test.go https://huggingface.co/spaces/AmaniQuery/amaniquery-agent/resolve/main/internal/cache/cache_test.go
9.77 kB
| package cache_test | |
| import ( | |
| "context" | |
| "strings" | |
| "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"), time.Hour) | |
| 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"), time.Hour) | |
| lru.Set("key1", []byte("updated"), time.Hour) | |
| 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"), time.Hour) | |
| lru.Set("key2", []byte("value2"), time.Hour) | |
| lru.Set("key3", []byte("value3"), time.Hour) | |
| // Access key1 to make it recently used | |
| lru.Get("key1") | |
| // Add new key, should evict key2 (least recently used) | |
| lru.Set("key4", []byte("value4"), time.Hour) | |
| // 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"), time.Hour) | |
| lru.Set("key2", []byte("value2"), time.Hour) | |
| 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) | |
| } | |
| } | |
| // TestLRUCache_TTLExpiry verifies entries are not served past their TTL | |
| // (regression guard for stale L1 data outliving the Redis expiry). | |
| func TestLRUCache_TTLExpiry(t *testing.T) { | |
| lru := cache.NewLRUCache(10) | |
| lru.Set("key1", []byte("value1"), 50*time.Millisecond) | |
| lru.Set("key2", []byte("value2"), time.Hour) | |
| // Immediately readable | |
| if _, found := lru.Get("key1"); !found { | |
| t.Fatal("Expected key1 to be present before expiry") | |
| } | |
| time.Sleep(60 * time.Millisecond) | |
| if _, found := lru.Get("key1"); found { | |
| t.Error("Expected key1 to have expired") | |
| } | |
| if _, found := lru.Get("key2"); !found { | |
| t.Error("Expected key2 (long TTL) to still be present") | |
| } | |
| } | |
| // TestLRUCache_DeletePrefix verifies prefix-based eviction removes only the | |
| // matching keys (regression guard for pattern/prefix invalidation). | |
| func TestLRUCache_DeletePrefix(t *testing.T) { | |
| lru := cache.NewLRUCache(10) | |
| lru.Set("L1:nbo:rag:a", []byte("1"), time.Hour) | |
| lru.Set("L1:nbo:rag:b", []byte("2"), time.Hour) | |
| lru.Set("L1:nbo:legal:c", []byte("3"), time.Hour) | |
| lru.DeletePrefix("L1:nbo:rag:") | |
| if _, found := lru.Get("L1:nbo:rag:a"); found { | |
| t.Error("Expected rag:a to be deleted") | |
| } | |
| if _, found := lru.Get("L1:nbo:rag:b"); found { | |
| t.Error("Expected rag:b to be deleted") | |
| } | |
| if _, found := lru.Get("L1:nbo:legal:c"); !found { | |
| t.Error("Expected non-matching key to survive") | |
| } | |
| } | |
| // TestMultiTierCache_DeleteByPrefixPattern verifies prefix invalidation matches | |
| // readable NormalizeKey keys (regression guard: hashed keys could never be | |
| // matched by prefix patterns, silently no-op'ing admin invalidations). | |
| func TestMultiTierCache_DeleteByPrefixPattern(t *testing.T) { | |
| cfg := cache.Config{ | |
| RedisURL: "redis://invalid:6379", | |
| LocalSize: 100, | |
| TTL: time.Hour, | |
| MaxRetries: 1, | |
| PoolSize: 1, | |
| KeyPrefix: "L1:nbo:", | |
| } | |
| c, err := cache.New(cfg) | |
| if err != nil { | |
| t.Fatalf("Failed to create cache: %v", err) | |
| } | |
| defer c.Close() | |
| ctx := context.Background() | |
| ragKey := c.NormalizeKey("rag", "who owns the land registry") | |
| legalKey := c.NormalizeKey("legal", "statute of limitations kenya") | |
| if !strings.HasPrefix(ragKey, "L1:nbo:rag:") { | |
| t.Fatalf("expected readable key prefix, got %q", ragKey) | |
| } | |
| if err := c.Set(ctx, ragKey, []byte("rag-answer"), time.Hour); err != nil { | |
| t.Fatalf("Set failed: %v", err) | |
| } | |
| if err := c.Set(ctx, legalKey, []byte("legal-answer"), time.Hour); err != nil { | |
| t.Fatalf("Set failed: %v", err) | |
| } | |
| if err := c.DeleteByPrefix(ctx, "rag:"); err != nil { | |
| t.Fatalf("DeleteByPrefix failed: %v", err) | |
| } | |
| if _, err := c.Get(ctx, ragKey); err == nil { | |
| t.Error("Expected rag key to be invalidated by prefix") | |
| } | |
| if _, err := c.Get(ctx, legalKey); err != nil { | |
| t.Error("Expected legal key to survive prefix invalidation") | |
| } | |
| } | |
| // 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"), time.Hour) | |
| } | |
| } | |
| // 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"), time.Hour) | |
| } | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| lru.Get("key" + string(rune(i%1000))) | |
| } | |
| } | |