1
2
3
4 package expirable
5
6 import (
7 "crypto/rand"
8 "fmt"
9 "math"
10 "math/big"
11 "reflect"
12 "sync"
13 "testing"
14 "time"
15
16 "github.com/hashicorp/golang-lru/v2/simplelru"
17 )
18
19 func BenchmarkLRU_Rand_NoExpire(b *testing.B) {
20 l := NewLRU[int64, int64](8192, nil, 0)
21
22 trace := make([]int64, b.N*2)
23 for i := 0; i < b.N*2; i++ {
24 trace[i] = getRand(b) % 32768
25 }
26
27 b.ResetTimer()
28
29 var hit, miss int
30 for i := 0; i < 2*b.N; i++ {
31 if i%2 == 0 {
32 l.Add(trace[i], trace[i])
33 } else {
34 if _, ok := l.Get(trace[i]); ok {
35 hit++
36 } else {
37 miss++
38 }
39 }
40 }
41 b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
42 }
43
44 func BenchmarkLRU_Freq_NoExpire(b *testing.B) {
45 l := NewLRU[int64, int64](8192, nil, 0)
46
47 trace := make([]int64, b.N*2)
48 for i := 0; i < b.N*2; i++ {
49 if i%2 == 0 {
50 trace[i] = getRand(b) % 16384
51 } else {
52 trace[i] = getRand(b) % 32768
53 }
54 }
55
56 b.ResetTimer()
57
58 for i := 0; i < b.N; i++ {
59 l.Add(trace[i], trace[i])
60 }
61 var hit, miss int
62 for i := 0; i < b.N; i++ {
63 if _, ok := l.Get(trace[i]); ok {
64 hit++
65 } else {
66 miss++
67 }
68 }
69 b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
70 }
71
72 func BenchmarkLRU_Rand_WithExpire(b *testing.B) {
73 l := NewLRU[int64, int64](8192, nil, time.Millisecond*10)
74
75 trace := make([]int64, b.N*2)
76 for i := 0; i < b.N*2; i++ {
77 trace[i] = getRand(b) % 32768
78 }
79
80 b.ResetTimer()
81
82 var hit, miss int
83 for i := 0; i < 2*b.N; i++ {
84 if i%2 == 0 {
85 l.Add(trace[i], trace[i])
86 } else {
87 if _, ok := l.Get(trace[i]); ok {
88 hit++
89 } else {
90 miss++
91 }
92 }
93 }
94 b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
95 }
96
97 func BenchmarkLRU_Freq_WithExpire(b *testing.B) {
98 l := NewLRU[int64, int64](8192, nil, time.Millisecond*10)
99
100 trace := make([]int64, b.N*2)
101 for i := 0; i < b.N*2; i++ {
102 if i%2 == 0 {
103 trace[i] = getRand(b) % 16384
104 } else {
105 trace[i] = getRand(b) % 32768
106 }
107 }
108
109 b.ResetTimer()
110
111 for i := 0; i < b.N; i++ {
112 l.Add(trace[i], trace[i])
113 }
114 var hit, miss int
115 for i := 0; i < b.N; i++ {
116 if _, ok := l.Get(trace[i]); ok {
117 hit++
118 } else {
119 miss++
120 }
121 }
122 b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
123 }
124
125 func TestLRUInterface(_ *testing.T) {
126 var _ simplelru.LRUCache[int, int] = &LRU[int, int]{}
127 }
128
129 func TestLRUNoPurge(t *testing.T) {
130 lc := NewLRU[string, string](10, nil, 0)
131
132 lc.Add("key1", "val1")
133 if lc.Len() != 1 {
134 t.Fatalf("length differs from expected")
135 }
136
137 v, ok := lc.Peek("key1")
138 if v != "val1" {
139 t.Fatalf("value differs from expected")
140 }
141 if !ok {
142 t.Fatalf("should be true")
143 }
144
145 if !lc.Contains("key1") {
146 t.Fatalf("should contain key1")
147 }
148 if lc.Contains("key2") {
149 t.Fatalf("should not contain key2")
150 }
151
152 v, ok = lc.Peek("key2")
153 if v != "" {
154 t.Fatalf("should be empty")
155 }
156 if ok {
157 t.Fatalf("should be false")
158 }
159
160 if !reflect.DeepEqual(lc.Keys(), []string{"key1"}) {
161 t.Fatalf("value differs from expected")
162 }
163
164 if lc.Resize(0) != 0 {
165 t.Fatalf("evicted count differs from expected")
166 }
167 if lc.Resize(2) != 0 {
168 t.Fatalf("evicted count differs from expected")
169 }
170 lc.Add("key2", "val2")
171 if lc.Resize(1) != 1 {
172 t.Fatalf("evicted count differs from expected")
173 }
174 }
175
176 func TestLRUEdgeCases(t *testing.T) {
177 lc := NewLRU[string, *string](2, nil, 0)
178
179
180 lc.Add("key1", nil)
181
182 value, exists := lc.Get("key1")
183 if value != nil || !exists {
184 t.Fatalf("unexpected value or existence flag for key1: value=%v, exists=%v", value, exists)
185 }
186
187
188 newVal := "val1"
189 lc.Add("key1", &newVal)
190
191 value, exists = lc.Get("key1")
192 if value != &newVal || !exists {
193 t.Fatalf("unexpected value or existence flag for key1: value=%v, exists=%v", value, exists)
194 }
195 }
196
197 func TestLRU_Values(t *testing.T) {
198 lc := NewLRU[string, string](3, nil, 0)
199
200 lc.Add("key1", "val1")
201 lc.Add("key2", "val2")
202 lc.Add("key3", "val3")
203
204 values := lc.Values()
205 if !reflect.DeepEqual(values, []string{"val1", "val2", "val3"}) {
206 t.Fatalf("values differs from expected")
207 }
208 }
209
210
211
212
213
214
215
216
217 func TestLRUWithPurge(t *testing.T) {
218 var evicted []string
219 lc := NewLRU(10, func(key string, value string) { evicted = append(evicted, key, value) }, 150*time.Millisecond)
220
221 k, v, ok := lc.GetOldest()
222 if k != "" {
223 t.Fatalf("should be empty")
224 }
225 if v != "" {
226 t.Fatalf("should be empty")
227 }
228 if ok {
229 t.Fatalf("should be false")
230 }
231
232 lc.Add("key1", "val1")
233
234 time.Sleep(100 * time.Millisecond)
235 if lc.Len() != 1 {
236 t.Fatalf("length differs from expected")
237 }
238
239 v, ok = lc.Get("key1")
240 if v != "val1" {
241 t.Fatalf("value differs from expected")
242 }
243 if !ok {
244 t.Fatalf("should be true")
245 }
246
247 time.Sleep(200 * time.Millisecond)
248 v, ok = lc.Get("key1")
249 if ok {
250 t.Fatalf("should be false")
251 }
252 if v != "" {
253 t.Fatalf("should be nil")
254 }
255
256 if lc.Len() != 0 {
257 t.Fatalf("length differs from expected")
258 }
259 if !reflect.DeepEqual(evicted, []string{"key1", "val1"}) {
260 t.Fatalf("value differs from expected")
261 }
262
263
264 lc.Add("key2", "val2")
265 if lc.Len() != 1 {
266 t.Fatalf("length differs from expected")
267 }
268
269 k, v, ok = lc.GetOldest()
270 if k != "key2" {
271 t.Fatalf("value differs from expected")
272 }
273 if v != "val2" {
274 t.Fatalf("value differs from expected")
275 }
276 if !ok {
277 t.Fatalf("should be true")
278 }
279
280
281 lc.deleteExpired()
282 if lc.Len() != 1 {
283 t.Fatalf("length differs from expected")
284 }
285 if !reflect.DeepEqual(evicted, []string{"key1", "val1"}) {
286 t.Fatalf("value differs from expected")
287 }
288
289
290 lc.Purge()
291 if lc.Len() != 0 {
292 t.Fatalf("length differs from expected")
293 }
294 if !reflect.DeepEqual(evicted, []string{"key1", "val1", "key2", "val2"}) {
295 t.Fatalf("value differs from expected")
296 }
297 }
298
299 func TestLRUWithPurgeEnforcedBySize(t *testing.T) {
300 lc := NewLRU[string, string](10, nil, time.Hour)
301
302 for i := 0; i < 100; i++ {
303 i := i
304 lc.Add(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i))
305 v, ok := lc.Get(fmt.Sprintf("key%d", i))
306 if v != fmt.Sprintf("val%d", i) {
307 t.Fatalf("value differs from expected")
308 }
309 if !ok {
310 t.Fatalf("should be true")
311 }
312 if lc.Len() > 20 {
313 t.Fatalf("length should be less than 20")
314 }
315 }
316
317 if lc.Len() != 10 {
318 t.Fatalf("length differs from expected")
319 }
320 }
321
322 func TestLRUConcurrency(t *testing.T) {
323 lc := NewLRU[string, string](0, nil, 0)
324 wg := sync.WaitGroup{}
325 wg.Add(1000)
326 for i := 0; i < 1000; i++ {
327 go func(i int) {
328 lc.Add(fmt.Sprintf("key-%d", i/10), fmt.Sprintf("val-%d", i/10))
329 wg.Done()
330 }(i)
331 }
332 wg.Wait()
333 if lc.Len() != 100 {
334 t.Fatalf("length differs from expected")
335 }
336 }
337
338 func TestLRUInvalidateAndEvict(t *testing.T) {
339 var evicted int
340 lc := NewLRU(-1, func(_, _ string) { evicted++ }, 0)
341
342 lc.Add("key1", "val1")
343 lc.Add("key2", "val2")
344
345 val, ok := lc.Get("key1")
346 if !ok {
347 t.Fatalf("should be true")
348 }
349 if val != "val1" {
350 t.Fatalf("value differs from expected")
351 }
352 if evicted != 0 {
353 t.Fatalf("value differs from expected")
354 }
355
356 lc.Remove("key1")
357 if evicted != 1 {
358 t.Fatalf("value differs from expected")
359 }
360 val, ok = lc.Get("key1")
361 if val != "" {
362 t.Fatalf("should be empty")
363 }
364 if ok {
365 t.Fatalf("should be false")
366 }
367 }
368
369 func TestLoadingExpired(t *testing.T) {
370 lc := NewLRU[string, string](0, nil, time.Millisecond*5)
371
372 lc.Add("key1", "val1")
373 if lc.Len() != 1 {
374 t.Fatalf("length differs from expected")
375 }
376
377 v, ok := lc.Peek("key1")
378 if v != "val1" {
379 t.Fatalf("value differs from expected")
380 }
381 if !ok {
382 t.Fatalf("should be true")
383 }
384
385 v, ok = lc.Get("key1")
386 if v != "val1" {
387 t.Fatalf("value differs from expected")
388 }
389 if !ok {
390 t.Fatalf("should be true")
391 }
392
393 for {
394 result, ok := lc.Get("key1")
395 if ok && result == "" {
396 t.Fatalf("ok should return a result")
397 }
398 if !ok {
399 break
400 }
401 }
402
403 time.Sleep(time.Millisecond * 100)
404 if lc.Len() != 0 {
405 t.Fatalf("length differs from expected")
406 }
407
408 v, ok = lc.Peek("key1")
409 if v != "" {
410 t.Fatalf("should be empty")
411 }
412 if ok {
413 t.Fatalf("should be false")
414 }
415
416 v, ok = lc.Get("key1")
417 if v != "" {
418 t.Fatalf("should be empty")
419 }
420 if ok {
421 t.Fatalf("should be false")
422 }
423 }
424
425 func TestLRURemoveOldest(t *testing.T) {
426 lc := NewLRU[string, string](2, nil, 0)
427
428 k, v, ok := lc.RemoveOldest()
429 if k != "" {
430 t.Fatalf("should be empty")
431 }
432 if v != "" {
433 t.Fatalf("should be empty")
434 }
435 if ok {
436 t.Fatalf("should be false")
437 }
438
439 ok = lc.Remove("non_existent")
440 if ok {
441 t.Fatalf("should be false")
442 }
443
444 lc.Add("key1", "val1")
445 if lc.Len() != 1 {
446 t.Fatalf("length differs from expected")
447 }
448
449 v, ok = lc.Get("key1")
450 if !ok {
451 t.Fatalf("should be true")
452 }
453 if v != "val1" {
454 t.Fatalf("value differs from expected")
455 }
456
457 if !reflect.DeepEqual(lc.Keys(), []string{"key1"}) {
458 t.Fatalf("value differs from expected")
459 }
460 if lc.Len() != 1 {
461 t.Fatalf("length differs from expected")
462 }
463
464 lc.Add("key2", "val2")
465 if !reflect.DeepEqual(lc.Keys(), []string{"key1", "key2"}) {
466 t.Fatalf("value differs from expected")
467 }
468 if lc.Len() != 2 {
469 t.Fatalf("length differs from expected")
470 }
471
472 k, v, ok = lc.RemoveOldest()
473 if k != "key1" {
474 t.Fatalf("value differs from expected")
475 }
476 if v != "val1" {
477 t.Fatalf("value differs from expected")
478 }
479 if !ok {
480 t.Fatalf("should be true")
481 }
482
483 if !reflect.DeepEqual(lc.Keys(), []string{"key2"}) {
484 t.Fatalf("value differs from expected")
485 }
486 if lc.Len() != 1 {
487 t.Fatalf("length differs from expected")
488 }
489 }
490
491 func ExampleLRU() {
492
493 cache := NewLRU[string, string](5, nil, time.Millisecond*10)
494
495
496 cache.Add("key1", "val1")
497
498
499 r, ok := cache.Get("key1")
500
501
502 if ok {
503 fmt.Printf("value before expiration is found: %v, value: %q\n", ok, r)
504 }
505
506
507 time.Sleep(time.Millisecond * 100)
508
509
510 r, ok = cache.Get("key1")
511 fmt.Printf("value after expiration is found: %v, value: %q\n", ok, r)
512
513
514 cache.Add("key2", "val2")
515
516 fmt.Printf("Cache len: %d\n", cache.Len())
517
518
519
520
521 }
522
523 func getRand(tb testing.TB) int64 {
524 out, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
525 if err != nil {
526 tb.Fatal(err)
527 }
528 return out.Int64()
529 }
530
531 func (c *LRU[K, V]) wantKeys(t *testing.T, want []K) {
532 t.Helper()
533 got := c.Keys()
534 if !reflect.DeepEqual(got, want) {
535 t.Errorf("wrong keys got: %v, want: %v ", got, want)
536 }
537 }
538
539 func TestCache_EvictionSameKey(t *testing.T) {
540 var evictedKeys []int
541
542 cache := NewLRU[int, struct{}](
543 2,
544 func(key int, _ struct{}) {
545 evictedKeys = append(evictedKeys, key)
546 },
547 0)
548
549 if evicted := cache.Add(1, struct{}{}); evicted {
550 t.Error("First 1: got unexpected eviction")
551 }
552 cache.wantKeys(t, []int{1})
553
554 if evicted := cache.Add(2, struct{}{}); evicted {
555 t.Error("2: got unexpected eviction")
556 }
557 cache.wantKeys(t, []int{1, 2})
558
559 if evicted := cache.Add(1, struct{}{}); evicted {
560 t.Error("Second 1: got unexpected eviction")
561 }
562 cache.wantKeys(t, []int{2, 1})
563
564 if evicted := cache.Add(3, struct{}{}); !evicted {
565 t.Error("3: did not get expected eviction")
566 }
567 cache.wantKeys(t, []int{1, 3})
568
569 want := []int{2}
570 if !reflect.DeepEqual(evictedKeys, want) {
571 t.Errorf("evictedKeys got: %v want: %v", evictedKeys, want)
572 }
573 }
574
View as plain text