github.com/songzhibin97/go-baseutils@v0.0.2-0.20240302024150-487d8ce9c082/app/bcache/bcache_test.go (about) 1 package bcache 2 3 import ( 4 "runtime" 5 "strconv" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/songzhibin97/go-baseutils/base/bcomparator" 11 ) 12 13 type TestStruct struct { 14 Num int 15 Children []*TestStruct 16 } 17 18 func TestCache(t *testing.T) { 19 tc := New[string, any](bcomparator.StringComparator()) 20 21 a, found := tc.Get("a") 22 if found || a != nil { 23 t.Error("a exist:", a) 24 } 25 26 b, found := tc.Get("b") 27 if found || b != nil { 28 t.Error("b exist:", b) 29 } 30 31 c, found := tc.Get("c") 32 if found || c != nil { 33 t.Error("c exist::", c) 34 } 35 36 tc.Set("a", 1, DefaultExpire) 37 tc.Set("b", "b", DefaultExpire) 38 tc.Set("c", 3.5, DefaultExpire) 39 40 v, found := tc.Get("a") 41 if !found { 42 t.Error("a not exist") 43 } 44 if v == nil { 45 t.Error("a == nil") 46 } else if vv := v.(int); vv+2 != 3 { 47 t.Error("vv != 3", vv) 48 } 49 50 v, found = tc.Get("b") 51 if !found { 52 t.Error("b not exist") 53 } 54 if v == nil { 55 t.Error("b == nil") 56 } else if vv := v.(string); vv+"B" != "bB" { 57 t.Error("bb != bB:", vv) 58 } 59 60 v, found = tc.Get("c") 61 if !found { 62 t.Error("c not exist") 63 } 64 if v == nil { 65 t.Error("x for c is nil") 66 } else if vv := v.(float64); vv+1.2 != 4.7 { 67 t.Error("vv != 4,7:", vv) 68 } 69 } 70 71 func TestCacheTimes(t *testing.T) { 72 var found bool 73 tc := New[string, int](bcomparator.StringComparator(), SetDefaultExpire[string, int](50*time.Millisecond), SetInternal[string, int](time.Millisecond)) 74 tc.Set("a", 1, DefaultExpire) 75 tc.Set("b", 2, NoExpire) 76 tc.Set("c", 3, 20*time.Millisecond) 77 tc.Set("d", 4, 70*time.Millisecond) 78 79 <-time.After(25 * time.Millisecond) 80 _, found = tc.Get("c") 81 if found { 82 t.Error("Found c when it should have been automatically deleted") 83 } 84 85 <-time.After(30 * time.Millisecond) 86 _, found = tc.Get("a") 87 if found { 88 t.Error("Found a when it should have been automatically deleted") 89 } 90 91 _, found = tc.Get("b") 92 if !found { 93 t.Error("Did not find b even though it was set to never expire") 94 } 95 96 _, found = tc.Get("d") 97 if !found { 98 t.Error("Did not find d even though it was set to expire later than the default") 99 } 100 101 <-time.After(20 * time.Millisecond) 102 _, found = tc.Get("d") 103 if found { 104 t.Error("Found d when it should have been automatically deleted (later than the default)") 105 } 106 } 107 108 func TestStorePointerToStruct(t *testing.T) { 109 tc := New[string, any](bcomparator.StringComparator()) 110 tc.Set("foo", &TestStruct{Num: 1}, DefaultExpire) 111 x, found := tc.Get("foo") 112 if !found { 113 t.Fatal("*TestStruct was not found for foo") 114 } 115 foo := x.(*TestStruct) 116 foo.Num++ 117 118 y, found := tc.Get("foo") 119 if !found { 120 t.Fatal("*TestStruct was not found for foo (second time)") 121 } 122 bar := y.(*TestStruct) 123 if bar.Num != 2 { 124 t.Fatal("TestStruct.Num is not 2") 125 } 126 } 127 128 func TestAdd(t *testing.T) { 129 tc := New[string, string](bcomparator.StringComparator()) 130 ok := tc.SetIfAbsent("foo", "bar", DefaultExpire) 131 if !ok { 132 t.Error("Couldn't add foo even though it shouldn't exist") 133 } 134 ok = tc.SetIfAbsent("foo", "baz", DefaultExpire) 135 if ok { 136 t.Error("Successfully added another foo when it should have returned an error") 137 } 138 } 139 140 func TestReplace(t *testing.T) { 141 tc := New[string, string](bcomparator.StringComparator()) 142 ok := tc.Replace("foo", "bar", DefaultExpire) 143 if ok { 144 t.Error("Replaced foo when it shouldn't exist") 145 } 146 tc.Set("foo", "bar", DefaultExpire) 147 ok = tc.Replace("foo", "bar", DefaultExpire) 148 if !ok { 149 t.Error("Couldn't replace existing key foo") 150 } 151 } 152 153 func TestDelete(t *testing.T) { 154 tc := New[string, string](bcomparator.StringComparator()) 155 tc.Set("foo", "bar", DefaultExpire) 156 tc.Delete("foo") 157 x, found := tc.Get("foo") 158 if found { 159 t.Error("foo was found, but it should have been deleted") 160 } 161 if x != "" { 162 t.Error("x is not nil:", x) 163 } 164 } 165 166 func TestItemCount(t *testing.T) { 167 tc := New[string, string](bcomparator.StringComparator()) 168 tc.Set("foo", "1", DefaultExpire) 169 tc.Set("bar", "2", DefaultExpire) 170 tc.Set("baz", "3", DefaultExpire) 171 if n := tc.Count(); n != 3 { 172 t.Errorf("Item count is not 3: %d", n) 173 } 174 } 175 176 func TestFlush(t *testing.T) { 177 tc := New[string, string](bcomparator.StringComparator()) 178 tc.Set("foo", "bar", DefaultExpire) 179 tc.Set("baz", "yes", DefaultExpire) 180 tc.Clear() 181 x, found := tc.Get("foo") 182 if found { 183 t.Error("foo was found, but it should have been deleted") 184 } 185 if x != "" { 186 t.Error("x is not nil:", x) 187 } 188 x, found = tc.Get("baz") 189 if found { 190 t.Error("baz was found, but it should have been deleted") 191 } 192 if x != "" { 193 t.Error("x is not nil:", x) 194 } 195 } 196 197 func TestCacheSerialization(t *testing.T) { 198 tc := New[string, any](bcomparator.StringComparator()) 199 testFillAndSerialize(t, tc) 200 201 // Check if gob.Register behaves properly even after multiple gob.Register 202 // on c.Items (many of which will be the same type) 203 testFillAndSerialize(t, tc) 204 } 205 206 func testFillAndSerialize(t *testing.T, tc *BCache[string, any]) { 207 tc.Set("a", "a", DefaultExpire) 208 tc.Set("b", "b", DefaultExpire) 209 tc.Set("c", "c", DefaultExpire) 210 tc.Set("expired", "foo", 1*time.Millisecond) 211 tc.Set("*struct", &TestStruct{Num: 1}, DefaultExpire) 212 tc.Set("[]struct", []TestStruct{ 213 {Num: 2}, 214 {Num: 3}, 215 }, DefaultExpire) 216 tc.Set("[]*struct", []*TestStruct{ 217 {Num: 4}, 218 {Num: 5}, 219 }, DefaultExpire) 220 tc.Set("structuration", &TestStruct{ 221 Num: 42, 222 Children: []*TestStruct{ 223 {Num: 6174}, 224 {Num: 4716}, 225 }, 226 }, DefaultExpire) 227 228 data, err := tc.Marshal() 229 if err != nil { 230 t.Fatal("Couldn't save cache to fp:", err) 231 } 232 233 oc := New[string, any](bcomparator.StringComparator()) 234 err = oc.Load(data) 235 if err != nil { 236 t.Fatal("Couldn't load cache from fp:", err) 237 } 238 239 a, found := oc.Get("a") 240 if !found { 241 t.Error("a was not found") 242 } 243 if a.(string) != "a" { 244 t.Error("a is not a") 245 } 246 247 b, found := oc.Get("b") 248 if !found { 249 t.Error("b was not found") 250 } 251 if b.(string) != "b" { 252 t.Error("b is not b") 253 } 254 255 c, found := oc.Get("c") 256 if !found { 257 t.Error("c was not found") 258 } 259 if c.(string) != "c" { 260 t.Error("c is not c") 261 } 262 263 <-time.After(5 * time.Millisecond) 264 _, found = oc.Get("expired") 265 if found { 266 t.Error("expired was found") 267 } 268 269 s1, found := oc.Get("*struct") 270 if !found { 271 t.Error("*struct was not found") 272 } 273 if s1.(map[string]interface{})["Num"].(float64) != 1 { 274 t.Error("*struct.Num is not 1") 275 } 276 277 s2, found := oc.Get("[]struct") 278 if !found { 279 t.Error("[]struct was not found") 280 } 281 s2r := s2.([]interface{}) 282 if len(s2r) != 2 { 283 t.Error("Length of s2r is not 2") 284 } 285 if s2r[0].(map[string]interface{})["Num"].(float64) != 2 { 286 t.Error("s2r[0].Num is not 2") 287 } 288 if s2r[1].(map[string]interface{})["Num"].(float64) != 3 { 289 t.Error("s2r[1].Num is not 3") 290 } 291 292 s3, found := oc.Get("[]*struct") 293 if !found { 294 t.Error("[]*struct was not found") 295 } 296 s3r := s3.([]interface{}) 297 if len(s3r) != 2 { 298 t.Error("Length of s3r is not 2") 299 } 300 if s3r[0].(map[string]interface{})["Num"].(float64) != 4 { 301 t.Error("s3r[0].Num is not 4") 302 } 303 if s3r[1].(map[string]interface{})["Num"].(float64) != 5 { 304 t.Error("s3r[1].Num is not 5") 305 } 306 307 s4, found := oc.Get("structuration") 308 if !found { 309 t.Error("structuration was not found") 310 } 311 s4r := s4.(map[string]interface{}) 312 if len(s4r["Children"].([]interface{})) != 2 { 313 t.Error("Length of s4r.Children is not 2") 314 } 315 316 if s4r["Children"].([]interface{})[0].(map[string]interface{})["Num"].(float64) != 6174 { 317 t.Error("s4r.Children[0].Num is not 6174") 318 } 319 if s4r["Children"].([]interface{})[1].(map[string]interface{})["Num"].(float64) != 4716 { 320 t.Error("s4r.Children[1].Num is not 4716") 321 } 322 } 323 324 func BenchmarkCacheGetExpiring(b *testing.B) { 325 benchmarkCacheGet(b) 326 } 327 328 func BenchmarkCacheGetNotExpiring(b *testing.B) { 329 benchmarkCacheGet(b) 330 } 331 332 func benchmarkCacheGet(b *testing.B) { 333 b.StopTimer() 334 tc := New[string, string](bcomparator.StringComparator()) 335 tc.Set("foo", "bar", DefaultExpire) 336 b.StartTimer() 337 for i := 0; i < b.N; i++ { 338 tc.Get("foo") 339 } 340 } 341 342 func BenchmarkRWMutexMapGet(b *testing.B) { 343 b.StopTimer() 344 m := map[string]string{ 345 "foo": "bar", 346 } 347 mu := sync.RWMutex{} 348 b.StartTimer() 349 for i := 0; i < b.N; i++ { 350 mu.RLock() 351 _, _ = m["foo"] 352 mu.RUnlock() 353 } 354 } 355 356 func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) { 357 b.StopTimer() 358 s := struct{ name string }{name: "foo"} 359 m := map[interface{}]string{ 360 s: "bar", 361 } 362 mu := sync.RWMutex{} 363 b.StartTimer() 364 for i := 0; i < b.N; i++ { 365 mu.RLock() 366 _, _ = m[s] 367 mu.RUnlock() 368 } 369 } 370 371 func BenchmarkRWMutexInterfaceMapGetString(b *testing.B) { 372 b.StopTimer() 373 m := map[interface{}]string{ 374 "foo": "bar", 375 } 376 mu := sync.RWMutex{} 377 b.StartTimer() 378 for i := 0; i < b.N; i++ { 379 mu.RLock() 380 _, _ = m["foo"] 381 mu.RUnlock() 382 } 383 } 384 385 func BenchmarkCacheGetConcurrentExpiring(b *testing.B) { 386 benchmarkCacheGetConcurrent(b) 387 } 388 389 func BenchmarkCacheGetConcurrentNotExpiring(b *testing.B) { 390 benchmarkCacheGetConcurrent(b) 391 } 392 393 func benchmarkCacheGetConcurrent(b *testing.B) { 394 b.StopTimer() 395 tc := New[string, string](bcomparator.StringComparator()) 396 tc.Set("foo", "bar", DefaultExpire) 397 wg := new(sync.WaitGroup) 398 workers := runtime.NumCPU() 399 each := b.N / workers 400 wg.Add(workers) 401 b.StartTimer() 402 for i := 0; i < workers; i++ { 403 go func() { 404 for j := 0; j < each; j++ { 405 tc.Get("foo") 406 } 407 wg.Done() 408 }() 409 } 410 wg.Wait() 411 } 412 413 func BenchmarkRWMutexMapGetConcurrent(b *testing.B) { 414 b.StopTimer() 415 m := map[string]string{ 416 "foo": "bar", 417 } 418 mu := sync.RWMutex{} 419 wg := new(sync.WaitGroup) 420 workers := runtime.NumCPU() 421 each := b.N / workers 422 wg.Add(workers) 423 b.StartTimer() 424 for i := 0; i < workers; i++ { 425 go func() { 426 for j := 0; j < each; j++ { 427 mu.RLock() 428 _, _ = m["foo"] 429 mu.RUnlock() 430 } 431 wg.Done() 432 }() 433 } 434 wg.Wait() 435 } 436 437 func BenchmarkCacheGetManyConcurrentExpiring(b *testing.B) { 438 benchmarkCacheGetManyConcurrent(b) 439 } 440 441 func BenchmarkCacheGetManyConcurrentNotExpiring(b *testing.B) { 442 benchmarkCacheGetManyConcurrent(b) 443 } 444 445 func benchmarkCacheGetManyConcurrent(b *testing.B) { 446 // This is the same as BenchmarkCacheGetConcurrent, but its result 447 // can be compared against BenchmarkShardedCacheGetManyConcurrent 448 // in sharded_test.go. 449 b.StopTimer() 450 n := 10000 451 tc := New[string, string](bcomparator.StringComparator()) 452 keys := make([]string, n) 453 for i := 0; i < n; i++ { 454 k := "foo" + strconv.Itoa(i) 455 keys[i] = k 456 tc.Set(k, "bar", DefaultExpire) 457 } 458 each := b.N / n 459 wg := new(sync.WaitGroup) 460 wg.Add(n) 461 for _, v := range keys { 462 go func(k string) { 463 for j := 0; j < each; j++ { 464 tc.Get(k) 465 } 466 wg.Done() 467 }(v) 468 } 469 b.StartTimer() 470 wg.Wait() 471 } 472 473 func BenchmarkCacheSetExpiring(b *testing.B) { 474 benchmarkCacheSet(b) 475 } 476 477 func BenchmarkCacheSetNotExpiring(b *testing.B) { 478 benchmarkCacheSet(b) 479 } 480 481 func benchmarkCacheSet(b *testing.B) { 482 b.StopTimer() 483 tc := New[string, string](bcomparator.StringComparator()) 484 b.StartTimer() 485 for i := 0; i < b.N; i++ { 486 tc.Set("foo", "bar", DefaultExpire) 487 } 488 } 489 490 func BenchmarkRWMutexMapSet(b *testing.B) { 491 b.StopTimer() 492 m := map[string]string{} 493 mu := sync.RWMutex{} 494 b.StartTimer() 495 for i := 0; i < b.N; i++ { 496 mu.Lock() 497 m["foo"] = "bar" 498 mu.Unlock() 499 } 500 } 501 502 func BenchmarkCacheSetDelete(b *testing.B) { 503 b.StopTimer() 504 tc := New[string, string](bcomparator.StringComparator(), SetCapture[string, string](nil)) 505 b.StartTimer() 506 for i := 0; i < b.N; i++ { 507 tc.Set("foo", "bar", DefaultExpire) 508 tc.Delete("foo") 509 } 510 } 511 512 func BenchmarkRWMutexMapSetDelete(b *testing.B) { 513 b.StopTimer() 514 m := map[string]string{} 515 mu := sync.RWMutex{} 516 b.StartTimer() 517 for i := 0; i < b.N; i++ { 518 mu.Lock() 519 m["foo"] = "bar" 520 mu.Unlock() 521 mu.Lock() 522 delete(m, "foo") 523 mu.Unlock() 524 } 525 } 526 527 func BenchmarkCacheSetDeleteSingleLock(b *testing.B) { 528 b.StopTimer() 529 tc := New[string, string](bcomparator.StringComparator()) 530 b.StartTimer() 531 for i := 0; i < b.N; i++ { 532 tc.Lock() 533 tc.set("foo", "bar", DefaultExpire) 534 tc.delete("foo") 535 tc.Unlock() 536 } 537 } 538 539 func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) { 540 b.StopTimer() 541 m := map[string]string{} 542 mu := sync.RWMutex{} 543 b.StartTimer() 544 for i := 0; i < b.N; i++ { 545 mu.Lock() 546 m["foo"] = "bar" 547 delete(m, "foo") 548 mu.Unlock() 549 } 550 } 551 552 func BenchmarkDeleteExpiredLoop(b *testing.B) { 553 b.StopTimer() 554 tc := New[string, string](bcomparator.StringComparator(), SetDefaultExpire[string, string](5*time.Minute), SetCapture[string, string](nil)) 555 for i := 0; i < 100000; i++ { 556 tc.set(strconv.Itoa(i), "bar", DefaultExpire) 557 } 558 b.StartTimer() 559 for i := 0; i < b.N; i++ { 560 tc.deleteExpire() 561 } 562 } 563 564 func TestGetWithExpiration(t *testing.T) { 565 tc := New[string, any](bcomparator.StringComparator()) 566 567 a, expiration, found := tc.GetWithExpire("a") 568 if found || a != nil || !expiration.IsZero() { 569 t.Error("Getting A found value that shouldn't exist:", a) 570 } 571 572 b, expiration, found := tc.GetWithExpire("b") 573 if found || b != nil || !expiration.IsZero() { 574 t.Error("Getting B found value that shouldn't exist:", b) 575 } 576 577 c, expiration, found := tc.GetWithExpire("c") 578 if found || c != nil || !expiration.IsZero() { 579 t.Error("Getting C found value that shouldn't exist:", c) 580 } 581 582 tc.Set("a", 1, DefaultExpire) 583 tc.Set("b", "b", DefaultExpire) 584 tc.Set("c", 3.5, DefaultExpire) 585 tc.Set("d", 1, NoExpire) 586 tc.Set("e", 1, 50*time.Millisecond) 587 588 x, expiration, found := tc.GetWithExpire("a") 589 if !found { 590 t.Error("a was not found while getting a2") 591 } 592 if x == nil { 593 t.Error("x for a is nil") 594 } else if a2 := x.(int); a2+2 != 3 { 595 t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) 596 } 597 if !expiration.IsZero() { 598 t.Error("expiration for a is not a zeroed time") 599 } 600 601 x, expiration, found = tc.GetWithExpire("b") 602 if !found { 603 t.Error("b was not found while getting b2") 604 } 605 if x == nil { 606 t.Error("x for b is nil") 607 } else if b2 := x.(string); b2+"B" != "bB" { 608 t.Error("b2 (which should be b) plus B does not equal bB; value:", b2) 609 } 610 if !expiration.IsZero() { 611 t.Error("expiration for b is not a zeroed time") 612 } 613 614 x, expiration, found = tc.GetWithExpire("c") 615 if !found { 616 t.Error("c was not found while getting c2") 617 } 618 if x == nil { 619 t.Error("x for c is nil") 620 } else if c2 := x.(float64); c2+1.2 != 4.7 { 621 t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) 622 } 623 if !expiration.IsZero() { 624 t.Error("expiration for c is not a zeroed time") 625 } 626 627 x, expiration, found = tc.GetWithExpire("d") 628 if !found { 629 t.Error("d was not found while getting d2") 630 } 631 if x == nil { 632 t.Error("x for d is nil") 633 } else if d2 := x.(int); d2+2 != 3 { 634 t.Error("d (which should be 1) plus 2 does not equal 3; value:", d2) 635 } 636 if !expiration.IsZero() { 637 t.Error("expiration for d is not a zeroed time") 638 } 639 640 x, expiration, found = tc.GetWithExpire("e") 641 if !found { 642 t.Error("e was not found while getting e2") 643 } 644 if x == nil { 645 t.Error("x for e is nil") 646 } else if e2 := x.(int); e2+2 != 3 { 647 t.Error("e (which should be 1) plus 2 does not equal 3; value:", e2) 648 } 649 v, _ := tc.member.Get("e") 650 if expiration.UnixNano() != v.Expire { 651 t.Error("expiration for e is not the correct time") 652 } 653 if expiration.UnixNano() < time.Now().UnixNano() { 654 t.Error("expiration for e is in the past") 655 } 656 }