You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
4.9 KiB
261 lines
4.9 KiB
//go:build !(js && wasm) |
|
|
|
package database |
|
|
|
import ( |
|
"sync" |
|
"testing" |
|
) |
|
|
|
func TestLRUCache_BasicOperations(t *testing.T) { |
|
c := NewLRUCache[string, int](10) |
|
|
|
// Test Put and Get |
|
c.Put("a", 1) |
|
c.Put("b", 2) |
|
c.Put("c", 3) |
|
|
|
if v, ok := c.Get("a"); !ok || v != 1 { |
|
t.Errorf("Get('a') = %d, %v; want 1, true", v, ok) |
|
} |
|
if v, ok := c.Get("b"); !ok || v != 2 { |
|
t.Errorf("Get('b') = %d, %v; want 2, true", v, ok) |
|
} |
|
if v, ok := c.Get("c"); !ok || v != 3 { |
|
t.Errorf("Get('c') = %d, %v; want 3, true", v, ok) |
|
} |
|
|
|
// Test non-existent key |
|
if _, ok := c.Get("d"); ok { |
|
t.Error("Get('d') should return false for non-existent key") |
|
} |
|
|
|
// Test Len |
|
if c.Len() != 3 { |
|
t.Errorf("Len() = %d; want 3", c.Len()) |
|
} |
|
} |
|
|
|
func TestLRUCache_Update(t *testing.T) { |
|
c := NewLRUCache[string, int](10) |
|
|
|
c.Put("a", 1) |
|
c.Put("a", 2) // Update |
|
|
|
if v, ok := c.Get("a"); !ok || v != 2 { |
|
t.Errorf("Get('a') = %d, %v; want 2, true", v, ok) |
|
} |
|
if c.Len() != 1 { |
|
t.Errorf("Len() = %d; want 1 (update should not add new entry)", c.Len()) |
|
} |
|
} |
|
|
|
func TestLRUCache_Eviction(t *testing.T) { |
|
c := NewLRUCache[int, string](3) |
|
|
|
// Fill cache |
|
c.Put(1, "one") |
|
c.Put(2, "two") |
|
c.Put(3, "three") |
|
|
|
// All should be present |
|
if c.Len() != 3 { |
|
t.Errorf("Len() = %d; want 3", c.Len()) |
|
} |
|
|
|
// Add one more - should evict "1" (oldest) |
|
c.Put(4, "four") |
|
|
|
if c.Len() != 3 { |
|
t.Errorf("Len() = %d; want 3 after eviction", c.Len()) |
|
} |
|
|
|
// "1" should be evicted |
|
if _, ok := c.Get(1); ok { |
|
t.Error("Key 1 should have been evicted") |
|
} |
|
|
|
// Others should still be present |
|
if _, ok := c.Get(2); !ok { |
|
t.Error("Key 2 should still be present") |
|
} |
|
if _, ok := c.Get(3); !ok { |
|
t.Error("Key 3 should still be present") |
|
} |
|
if _, ok := c.Get(4); !ok { |
|
t.Error("Key 4 should be present") |
|
} |
|
} |
|
|
|
func TestLRUCache_LRUOrder(t *testing.T) { |
|
c := NewLRUCache[int, string](3) |
|
|
|
// Fill cache |
|
c.Put(1, "one") |
|
c.Put(2, "two") |
|
c.Put(3, "three") |
|
|
|
// Access "1" - makes it most recent |
|
c.Get(1) |
|
|
|
// Add "4" - should evict "2" (now oldest) |
|
c.Put(4, "four") |
|
|
|
// "1" should still be present (was accessed recently) |
|
if _, ok := c.Get(1); !ok { |
|
t.Error("Key 1 should still be present after being accessed") |
|
} |
|
|
|
// "2" should be evicted |
|
if _, ok := c.Get(2); ok { |
|
t.Error("Key 2 should have been evicted (oldest)") |
|
} |
|
} |
|
|
|
func TestLRUCache_Delete(t *testing.T) { |
|
c := NewLRUCache[string, int](10) |
|
|
|
c.Put("a", 1) |
|
c.Put("b", 2) |
|
|
|
c.Delete("a") |
|
|
|
if _, ok := c.Get("a"); ok { |
|
t.Error("Key 'a' should be deleted") |
|
} |
|
if c.Len() != 1 { |
|
t.Errorf("Len() = %d; want 1", c.Len()) |
|
} |
|
|
|
// Delete non-existent key should not panic |
|
c.Delete("nonexistent") |
|
} |
|
|
|
func TestLRUCache_Clear(t *testing.T) { |
|
c := NewLRUCache[int, int](10) |
|
|
|
for i := 0; i < 5; i++ { |
|
c.Put(i, i*10) |
|
} |
|
|
|
c.Clear() |
|
|
|
if c.Len() != 0 { |
|
t.Errorf("Len() = %d; want 0 after Clear()", c.Len()) |
|
} |
|
|
|
// Should be able to add after clear |
|
c.Put(100, 1000) |
|
if v, ok := c.Get(100); !ok || v != 1000 { |
|
t.Errorf("Get(100) = %d, %v; want 1000, true", v, ok) |
|
} |
|
} |
|
|
|
func TestLRUCache_Contains(t *testing.T) { |
|
c := NewLRUCache[string, int](10) |
|
|
|
c.Put("a", 1) |
|
|
|
if !c.Contains("a") { |
|
t.Error("Contains('a') should return true") |
|
} |
|
if c.Contains("b") { |
|
t.Error("Contains('b') should return false") |
|
} |
|
} |
|
|
|
func TestLRUCache_ByteArrayKey(t *testing.T) { |
|
// Test with [32]byte keys (like pubkeys/event IDs) |
|
c := NewLRUCache[[32]byte, uint64](100) |
|
|
|
var key1, key2 [32]byte |
|
key1[0] = 1 |
|
key2[0] = 2 |
|
|
|
c.Put(key1, 100) |
|
c.Put(key2, 200) |
|
|
|
if v, ok := c.Get(key1); !ok || v != 100 { |
|
t.Errorf("Get(key1) = %d, %v; want 100, true", v, ok) |
|
} |
|
if v, ok := c.Get(key2); !ok || v != 200 { |
|
t.Errorf("Get(key2) = %d, %v; want 200, true", v, ok) |
|
} |
|
} |
|
|
|
func TestLRUCache_Concurrent(t *testing.T) { |
|
c := NewLRUCache[int, int](1000) |
|
var wg sync.WaitGroup |
|
|
|
// Concurrent writes |
|
for i := 0; i < 10; i++ { |
|
wg.Add(1) |
|
go func(base int) { |
|
defer wg.Done() |
|
for j := 0; j < 100; j++ { |
|
c.Put(base*100+j, j) |
|
} |
|
}(i) |
|
} |
|
|
|
// Concurrent reads |
|
for i := 0; i < 10; i++ { |
|
wg.Add(1) |
|
go func(base int) { |
|
defer wg.Done() |
|
for j := 0; j < 100; j++ { |
|
c.Get(base*100 + j) |
|
} |
|
}(i) |
|
} |
|
|
|
wg.Wait() |
|
|
|
// Cache should not exceed max size |
|
if c.Len() > c.MaxSize() { |
|
t.Errorf("Len() = %d exceeds MaxSize() = %d", c.Len(), c.MaxSize()) |
|
} |
|
} |
|
|
|
func BenchmarkLRUCache_Put(b *testing.B) { |
|
c := NewLRUCache[uint64, []byte](10000) |
|
value := make([]byte, 32) |
|
|
|
b.ReportAllocs() |
|
b.ResetTimer() |
|
|
|
for i := 0; i < b.N; i++ { |
|
c.Put(uint64(i%10000), value) |
|
} |
|
} |
|
|
|
func BenchmarkLRUCache_Get(b *testing.B) { |
|
c := NewLRUCache[uint64, []byte](10000) |
|
value := make([]byte, 32) |
|
|
|
// Pre-fill cache |
|
for i := 0; i < 10000; i++ { |
|
c.Put(uint64(i), value) |
|
} |
|
|
|
b.ReportAllocs() |
|
b.ResetTimer() |
|
|
|
for i := 0; i < b.N; i++ { |
|
c.Get(uint64(i % 10000)) |
|
} |
|
} |
|
|
|
func BenchmarkLRUCache_PutGet(b *testing.B) { |
|
c := NewLRUCache[uint64, []byte](10000) |
|
value := make([]byte, 32) |
|
|
|
b.ReportAllocs() |
|
b.ResetTimer() |
|
|
|
for i := 0; i < b.N; i++ { |
|
key := uint64(i % 10000) |
|
c.Put(key, value) |
|
c.Get(key) |
|
} |
|
}
|
|
|