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