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