lrucache_test.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"sync"
   7  	"testing"
   8  )
   9  
  10  func TestLRUCache_BasicOperations(t *testing.T) {
  11  	c := NewLRUCache[string, int](10)
  12  
  13  	// Test Put and Get
  14  	c.Put("a", 1)
  15  	c.Put("b", 2)
  16  	c.Put("c", 3)
  17  
  18  	if v, ok := c.Get("a"); !ok || v != 1 {
  19  		t.Errorf("Get('a') = %d, %v; want 1, true", v, ok)
  20  	}
  21  	if v, ok := c.Get("b"); !ok || v != 2 {
  22  		t.Errorf("Get('b') = %d, %v; want 2, true", v, ok)
  23  	}
  24  	if v, ok := c.Get("c"); !ok || v != 3 {
  25  		t.Errorf("Get('c') = %d, %v; want 3, true", v, ok)
  26  	}
  27  
  28  	// Test non-existent key
  29  	if _, ok := c.Get("d"); ok {
  30  		t.Error("Get('d') should return false for non-existent key")
  31  	}
  32  
  33  	// Test Len
  34  	if c.Len() != 3 {
  35  		t.Errorf("Len() = %d; want 3", c.Len())
  36  	}
  37  }
  38  
  39  func TestLRUCache_Update(t *testing.T) {
  40  	c := NewLRUCache[string, int](10)
  41  
  42  	c.Put("a", 1)
  43  	c.Put("a", 2) // Update
  44  
  45  	if v, ok := c.Get("a"); !ok || v != 2 {
  46  		t.Errorf("Get('a') = %d, %v; want 2, true", v, ok)
  47  	}
  48  	if c.Len() != 1 {
  49  		t.Errorf("Len() = %d; want 1 (update should not add new entry)", c.Len())
  50  	}
  51  }
  52  
  53  func TestLRUCache_Eviction(t *testing.T) {
  54  	c := NewLRUCache[int, string](3)
  55  
  56  	// Fill cache
  57  	c.Put(1, "one")
  58  	c.Put(2, "two")
  59  	c.Put(3, "three")
  60  
  61  	// All should be present
  62  	if c.Len() != 3 {
  63  		t.Errorf("Len() = %d; want 3", c.Len())
  64  	}
  65  
  66  	// Add one more - should evict "1" (oldest)
  67  	c.Put(4, "four")
  68  
  69  	if c.Len() != 3 {
  70  		t.Errorf("Len() = %d; want 3 after eviction", c.Len())
  71  	}
  72  
  73  	// "1" should be evicted
  74  	if _, ok := c.Get(1); ok {
  75  		t.Error("Key 1 should have been evicted")
  76  	}
  77  
  78  	// Others should still be present
  79  	if _, ok := c.Get(2); !ok {
  80  		t.Error("Key 2 should still be present")
  81  	}
  82  	if _, ok := c.Get(3); !ok {
  83  		t.Error("Key 3 should still be present")
  84  	}
  85  	if _, ok := c.Get(4); !ok {
  86  		t.Error("Key 4 should be present")
  87  	}
  88  }
  89  
  90  func TestLRUCache_LRUOrder(t *testing.T) {
  91  	c := NewLRUCache[int, string](3)
  92  
  93  	// Fill cache
  94  	c.Put(1, "one")
  95  	c.Put(2, "two")
  96  	c.Put(3, "three")
  97  
  98  	// Access "1" - makes it most recent
  99  	c.Get(1)
 100  
 101  	// Add "4" - should evict "2" (now oldest)
 102  	c.Put(4, "four")
 103  
 104  	// "1" should still be present (was accessed recently)
 105  	if _, ok := c.Get(1); !ok {
 106  		t.Error("Key 1 should still be present after being accessed")
 107  	}
 108  
 109  	// "2" should be evicted
 110  	if _, ok := c.Get(2); ok {
 111  		t.Error("Key 2 should have been evicted (oldest)")
 112  	}
 113  }
 114  
 115  func TestLRUCache_Delete(t *testing.T) {
 116  	c := NewLRUCache[string, int](10)
 117  
 118  	c.Put("a", 1)
 119  	c.Put("b", 2)
 120  
 121  	c.Delete("a")
 122  
 123  	if _, ok := c.Get("a"); ok {
 124  		t.Error("Key 'a' should be deleted")
 125  	}
 126  	if c.Len() != 1 {
 127  		t.Errorf("Len() = %d; want 1", c.Len())
 128  	}
 129  
 130  	// Delete non-existent key should not panic
 131  	c.Delete("nonexistent")
 132  }
 133  
 134  func TestLRUCache_Clear(t *testing.T) {
 135  	c := NewLRUCache[int, int](10)
 136  
 137  	for i := 0; i < 5; i++ {
 138  		c.Put(i, i*10)
 139  	}
 140  
 141  	c.Clear()
 142  
 143  	if c.Len() != 0 {
 144  		t.Errorf("Len() = %d; want 0 after Clear()", c.Len())
 145  	}
 146  
 147  	// Should be able to add after clear
 148  	c.Put(100, 1000)
 149  	if v, ok := c.Get(100); !ok || v != 1000 {
 150  		t.Errorf("Get(100) = %d, %v; want 1000, true", v, ok)
 151  	}
 152  }
 153  
 154  func TestLRUCache_Contains(t *testing.T) {
 155  	c := NewLRUCache[string, int](10)
 156  
 157  	c.Put("a", 1)
 158  
 159  	if !c.Contains("a") {
 160  		t.Error("Contains('a') should return true")
 161  	}
 162  	if c.Contains("b") {
 163  		t.Error("Contains('b') should return false")
 164  	}
 165  }
 166  
 167  func TestLRUCache_ByteArrayKey(t *testing.T) {
 168  	// Test with [32]byte keys (like pubkeys/event IDs)
 169  	c := NewLRUCache[[32]byte, uint64](100)
 170  
 171  	var key1, key2 [32]byte
 172  	key1[0] = 1
 173  	key2[0] = 2
 174  
 175  	c.Put(key1, 100)
 176  	c.Put(key2, 200)
 177  
 178  	if v, ok := c.Get(key1); !ok || v != 100 {
 179  		t.Errorf("Get(key1) = %d, %v; want 100, true", v, ok)
 180  	}
 181  	if v, ok := c.Get(key2); !ok || v != 200 {
 182  		t.Errorf("Get(key2) = %d, %v; want 200, true", v, ok)
 183  	}
 184  }
 185  
 186  func TestLRUCache_Concurrent(t *testing.T) {
 187  	c := NewLRUCache[int, int](1000)
 188  	var wg sync.WaitGroup
 189  
 190  	// Concurrent writes
 191  	for i := 0; i < 10; i++ {
 192  		wg.Add(1)
 193  		go func(base int) {
 194  			defer wg.Done()
 195  			for j := 0; j < 100; j++ {
 196  				c.Put(base*100+j, j)
 197  			}
 198  		}(i)
 199  	}
 200  
 201  	// Concurrent reads
 202  	for i := 0; i < 10; i++ {
 203  		wg.Add(1)
 204  		go func(base int) {
 205  			defer wg.Done()
 206  			for j := 0; j < 100; j++ {
 207  				c.Get(base*100 + j)
 208  			}
 209  		}(i)
 210  	}
 211  
 212  	wg.Wait()
 213  
 214  	// Cache should not exceed max size
 215  	if c.Len() > c.MaxSize() {
 216  		t.Errorf("Len() = %d exceeds MaxSize() = %d", c.Len(), c.MaxSize())
 217  	}
 218  }
 219  
 220  func BenchmarkLRUCache_Put(b *testing.B) {
 221  	c := NewLRUCache[uint64, []byte](10000)
 222  	value := make([]byte, 32)
 223  
 224  	b.ReportAllocs()
 225  	b.ResetTimer()
 226  
 227  	for i := 0; i < b.N; i++ {
 228  		c.Put(uint64(i%10000), value)
 229  	}
 230  }
 231  
 232  func BenchmarkLRUCache_Get(b *testing.B) {
 233  	c := NewLRUCache[uint64, []byte](10000)
 234  	value := make([]byte, 32)
 235  
 236  	// Pre-fill cache
 237  	for i := 0; i < 10000; i++ {
 238  		c.Put(uint64(i), value)
 239  	}
 240  
 241  	b.ReportAllocs()
 242  	b.ResetTimer()
 243  
 244  	for i := 0; i < b.N; i++ {
 245  		c.Get(uint64(i % 10000))
 246  	}
 247  }
 248  
 249  func BenchmarkLRUCache_PutGet(b *testing.B) {
 250  	c := NewLRUCache[uint64, []byte](10000)
 251  	value := make([]byte, 32)
 252  
 253  	b.ReportAllocs()
 254  	b.ResetTimer()
 255  
 256  	for i := 0; i < b.N; i++ {
 257  		key := uint64(i % 10000)
 258  		c.Put(key, value)
 259  		c.Get(key)
 260  	}
 261  }
 262