github.com/phuslu/lru@v1.0.16-0.20240421170520-46288a2fd47c/lru_cache_test.go (about) 1 package lru 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "runtime" 8 "strings" 9 "sync" 10 "sync/atomic" 11 "testing" 12 "time" 13 "unsafe" 14 ) 15 16 func TestLRUCacheCompactness(t *testing.T) { 17 if runtime.GOARCH != "amd64" { 18 return 19 } 20 21 compact := isamd64 22 defer func() { 23 isamd64 = compact 24 }() 25 26 for _, b := range []bool{true, false} { 27 isamd64 = b 28 cache := NewLRUCache[string, []byte](32 * 1024) 29 if length := cache.Len(); length != 0 { 30 t.Fatalf("bad cache length: %v", length) 31 } 32 } 33 } 34 35 func TestLRUCacheDefaultkey(t *testing.T) { 36 cache := NewLRUCache[string, int](1) 37 var k string 38 var i int = 10 39 40 if prev, replaced := cache.Set(k, i); replaced { 41 t.Fatalf("value %v should not be replaced", prev) 42 } 43 44 if v, ok := cache.Get(k); !ok || v != i { 45 t.Fatalf("bad returned value: %v != %v", v, i) 46 } 47 } 48 49 func TestLRUCacheGetSet(t *testing.T) { 50 cache := NewLRUCache[int, int](128) 51 52 if v, ok := cache.Get(5); ok { 53 t.Fatalf("bad returned value: %v", v) 54 } 55 56 if _, replaced := cache.Set(5, 10); replaced { 57 t.Fatal("should not have replaced") 58 } 59 60 if v, ok := cache.Get(5); !ok || v != 10 { 61 t.Fatalf("bad returned value: %v != %v", v, 10) 62 } 63 64 if v, replaced := cache.Set(5, 9); v != 10 || !replaced { 65 t.Fatal("old value should be evicted") 66 } 67 68 if v, replaced := cache.Set(5, 9); v != 9 || !replaced { 69 t.Fatal("old value should be evicted") 70 } 71 72 if v, ok := cache.Get(5); !ok || v != 9 { 73 t.Fatalf("bad returned value: %v != %v", v, 10) 74 } 75 } 76 77 func TestLRUCacheSetIfAbsent(t *testing.T) { 78 cache := NewLRUCache[int, int](128) 79 80 cache.Set(5, 5) 81 82 if _, replaced := cache.SetIfAbsent(5, 10); replaced { 83 t.Fatal("should not have replaced") 84 } 85 86 if v, ok := cache.Get(5); !ok || v != 5 { 87 t.Fatalf("bad returned value: %v = %v", v, 5) 88 } 89 90 cache.Delete(5) 91 92 if _, replaced := cache.SetIfAbsent(5, 10); replaced { 93 t.Fatal("should not have replaced") 94 } 95 96 if v, ok := cache.Get(5); !ok || v != 10 { 97 t.Fatalf("bad returned value: %v = %v", v, 10) 98 } 99 100 cache.Delete(5) 101 102 if _, replaced := cache.SetIfAbsent(5, 10); replaced { 103 t.Fatal("should not have replaced") 104 } 105 106 if v, ok := cache.Get(5); !ok || v != 10 { 107 t.Fatalf("bad returned value: %v = %v", v, 10) 108 } 109 } 110 111 func TestLRUCacheEviction(t *testing.T) { 112 cache := NewLRUCache[int, *int](256, WithShards[int, *int](1024)) 113 if cache.mask+1 != uint32(cap(cache.shards)) { 114 t.Fatalf("bad shard mask: %v", cache.mask) 115 } 116 117 cache = NewLRUCache[int, *int](256, WithShards[int, *int](1)) 118 119 evictedCounter := 0 120 for i := 0; i < 512; i++ { 121 if v, _ := cache.Set(i, &i); v != nil { 122 evictedCounter++ 123 } 124 } 125 126 if cache.Len() != 256 { 127 t.Fatalf("bad len: %v", cache.Len()) 128 } 129 130 if evictedCounter != 256 { 131 t.Fatalf("bad evicted count: %v", evictedCounter) 132 } 133 134 for i := 0; i < 256; i++ { 135 if v, ok := cache.Get(i); ok || v != nil { 136 t.Fatalf("key %v value %v should be evicted", i, *v) 137 } 138 } 139 140 for i := 256; i < 512; i++ { 141 if v, ok := cache.Get(i); !ok { 142 t.Fatalf("key %v value %v should not be evicted", i, *v) 143 } 144 } 145 146 for i := 256; i < 384; i++ { 147 cache.Delete(i) 148 if v, ok := cache.Get(i); ok { 149 t.Fatalf("old key %v value %v should be deleted", i, *v) 150 } 151 } 152 153 for i := 384; i < 512; i++ { 154 if v, ok := cache.Get(i); !ok || v == nil { 155 t.Fatalf("old key %v value %v should not be deleted", i, *v) 156 } 157 } 158 159 if got, want := cache.Len(), 128; got != want { 160 t.Fatalf("curent cache length %v should be %v", got, want) 161 } 162 163 cache.Set(400, &evictedCounter) 164 165 if got, want := len(cache.AppendKeys(nil)), 128; got != want { 166 t.Fatalf("curent cache keys length %v should be %v", got, want) 167 } 168 } 169 170 func TestLRUCachePeek(t *testing.T) { 171 cache := NewLRUCache[int, int](64) 172 173 cache.Set(10, 10) 174 cache.Set(20, 20) 175 if v, ok := cache.Peek(10); !ok || v != 10 { 176 t.Errorf("10 should be set to 10: %v", v) 177 } 178 179 if v, ok := cache.Peek(20); !ok || v != 20 { 180 t.Errorf("20 should be set to 20: %v,", v) 181 } 182 183 if v, ok := cache.Peek(30); ok || v != 0 { 184 t.Errorf("30 should be set to 0: %v,", v) 185 } 186 187 for k := 3; k < 1024; k++ { 188 cache.Set(k, k) 189 } 190 if v, ok := cache.Peek(10); ok || v == 10 { 191 t.Errorf("%v should not have updated recent-ness of 10", v) 192 } 193 if v, ok := cache.Peek(30); ok || v != 0 { 194 t.Errorf("%v should have updated recent-ness of 30", v) 195 } 196 } 197 198 func TestLRUCacheHasher(t *testing.T) { 199 cache := NewLRUCache[string, int](1024, 200 WithHasher[string, int](func(key unsafe.Pointer, seed uintptr) (x uintptr) { 201 x = 5381 202 for _, c := range []byte(*(*string)(key)) { 203 x = x*33 + uintptr(c) 204 } 205 return 206 }), 207 WithShards[string, int](1), 208 ) 209 210 if v, ok := cache.Get("abcde"); ok { 211 t.Fatalf("bad returned value: %v", v) 212 } 213 214 if _, replaced := cache.Set("abcde", 10); replaced { 215 t.Fatal("should not have replaced") 216 } 217 218 if v, ok := cache.Get("abcde"); !ok || v != 10 { 219 t.Fatalf("bad returned value: %v != %v", v, 10) 220 } 221 } 222 223 func TestLRUCacheSliding(t *testing.T) { 224 defer func() { 225 if r := recover(); r != nil { 226 if !strings.Contains(fmt.Sprint(r), "not_supported") { 227 t.Errorf("should be not_supported") 228 } 229 } 230 }() 231 _ = NewLRUCache[string, int](1024, WithSliding[string, int](true)) 232 t.Errorf("should be panic above") 233 } 234 235 func TestLRUCacheLoader(t *testing.T) { 236 cache := NewLRUCache[string, int](1024) 237 if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); ok || err == nil || v != 0 { 238 t.Errorf("cache.GetOrLoad(\"a\", nil) again should be return error: %v, %v, %v", v, err, ok) 239 } 240 241 cache = NewLRUCache[string, int](1024, WithLoader[string, int](func(ctx context.Context, key string) (int, error) { 242 if key == "" { 243 return 0, fmt.Errorf("invalid key: %v", key) 244 } 245 i := int(key[0] - 'a' + 1) 246 return i, nil 247 })) 248 249 if v, err, ok := cache.GetOrLoad(context.Background(), "", nil); ok || err == nil || v != 0 { 250 t.Errorf("cache.GetOrLoad(\"a\", nil) again should be return error: %v, %v, %v", v, err, ok) 251 } 252 253 if v, err, ok := cache.GetOrLoad(context.Background(), "b", nil); ok || err != nil || v != 2 { 254 t.Errorf("cache.GetOrLoad(\"b\", nil) again should be return 2: %v, %v, %v", v, err, ok) 255 } 256 257 if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); ok || err != nil || v != 1 { 258 t.Errorf("cache.GetOrLoad(\"a\", nil) should be return 1: %v, %v, %v", v, err, ok) 259 } 260 261 if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); !ok || err != nil || v != 1 { 262 t.Errorf("cache.GetOrLoad(\"a\", nil) again should be return 1: %v, %v, %v", v, err, ok) 263 } 264 } 265 266 func TestLRUCacheLoaderPanic(t *testing.T) { 267 defer func() { 268 if r := recover(); r != nil { 269 if !strings.Contains(fmt.Sprint(r), "not_supported") { 270 t.Errorf("should be not_supported") 271 } 272 } 273 }() 274 _ = NewLRUCache[string, int](1024, WithLoader[string, int](func(ctx context.Context, key string) (int, time.Duration, error) { 275 return 1, time.Hour, nil 276 })) 277 t.Errorf("should be panic above") 278 } 279 280 func TestLRUCacheLoaderSingleflight(t *testing.T) { 281 var loads uint32 282 283 cache := NewLRUCache[string, int](1024, WithLoader[string, int](func(ctx context.Context, key string) (int, error) { 284 atomic.AddUint32(&loads, 1) 285 time.Sleep(100 * time.Millisecond) 286 return int(key[0] - 'a' + 1), nil 287 })) 288 289 var wg sync.WaitGroup 290 wg.Add(10) 291 for i := 0; i < 10; i++ { 292 go func(i int) { 293 defer wg.Done() 294 v, err, ok := cache.GetOrLoad(context.Background(), "a", nil) 295 if v != 1 || err != nil || !ok { 296 t.Errorf("a should be set to 1: %v,%v,%v", v, err, ok) 297 } 298 }(i) 299 } 300 wg.Wait() 301 302 if n := atomic.LoadUint32(&loads); n != 1 { 303 t.Errorf("a should be loaded only once: %v", n) 304 } 305 } 306 307 func TestLRUCacheStats(t *testing.T) { 308 cache := NewLRUCache[string, int](256, WithShards[string, int](1)) 309 310 cache.Set("a", 1) 311 cache.Set("b", 2) 312 cache.Set("c", 3) 313 cache.Set("d", 3) 314 315 stats := cache.Stats() 316 if got, want := stats.EntriesCount, uint64(4); got != want { 317 t.Fatalf("cache entries should be %v: %v", want, got) 318 } 319 if got, want := stats.GetCalls, uint64(0); got != want { 320 t.Fatalf("cache get calls should be %v: %v", want, got) 321 } 322 if got, want := stats.SetCalls, uint64(4); got != want { 323 t.Fatalf("cache set calls should be %v: %v", want, got) 324 } 325 if got, want := stats.Misses, uint64(0); got != want { 326 t.Fatalf("cache misses should be %v: %v", want, got) 327 } 328 329 cache.Get("a") 330 cache.Get("b") 331 cache.Get("x") 332 cache.Get("y") 333 cache.Get("z") 334 cache.Set("c", 13) 335 336 stats = cache.Stats() 337 if got, want := stats.EntriesCount, uint64(4); got != want { 338 t.Fatalf("cache entries should be %v: %v", want, got) 339 } 340 if got, want := stats.GetCalls, uint64(5); got != want { 341 t.Fatalf("cache get calls should be %v: %v", want, got) 342 } 343 if got, want := stats.SetCalls, uint64(5); got != want { 344 t.Fatalf("cache set calls should be %v: %v", want, got) 345 } 346 if got, want := stats.Misses, uint64(3); got != want { 347 t.Fatalf("cache misses should be %v: %v", want, got) 348 } 349 } 350 351 func BenchmarkLRUCacheRand(b *testing.B) { 352 cache := NewLRUCache[int64, int64](8192) 353 354 trace := make([]int64, b.N*2) 355 for i := 0; i < b.N*2; i++ { 356 trace[i] = rand.Int63() % 32768 357 } 358 359 b.ReportAllocs() 360 b.ResetTimer() 361 362 var hit, miss int 363 for i := 0; i < 2*b.N; i++ { 364 if i%2 == 0 { 365 cache.Set(trace[i], trace[i]) 366 } else { 367 if _, ok := cache.Get(trace[i]); ok { 368 hit++ 369 } else { 370 miss++ 371 } 372 } 373 } 374 b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) 375 } 376 377 func BenchmarkLRUCacheFreq(b *testing.B) { 378 cache := NewLRUCache[int64, int64](8192) 379 380 trace := make([]int64, b.N*2) 381 for i := 0; i < b.N*2; i++ { 382 if i%2 == 0 { 383 trace[i] = rand.Int63() % 16384 384 } else { 385 trace[i] = rand.Int63() % 32768 386 } 387 } 388 389 b.ReportAllocs() 390 b.ResetTimer() 391 392 for i := 0; i < b.N; i++ { 393 cache.Set(trace[i], trace[i]) 394 } 395 var hit, miss int 396 for i := 0; i < b.N; i++ { 397 if _, ok := cache.Get(trace[i]); ok { 398 hit++ 399 } else { 400 miss++ 401 } 402 } 403 b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss)) 404 }