github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/herocache/backdata/cache_test.go (about) 1 package herocache 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool" 13 "github.com/onflow/flow-go/module/metrics" 14 "github.com/onflow/flow-go/utils/unittest" 15 ) 16 17 // TestArrayBackData_SingleBucket evaluates health of state transition for storing 10 entities in a Cache with only 18 // a single bucket (of 16). It also evaluates all stored items are retrievable. 19 func TestArrayBackData_SingleBucket(t *testing.T) { 20 limit := 16 21 22 bd := NewCache(uint32(limit), 23 1, 24 heropool.LRUEjection, 25 unittest.Logger(), 26 metrics.NewNoopCollector()) 27 28 entities := unittest.EntityListFixture(uint(limit)) 29 30 // adds all entities to backdata 31 testAddEntities(t, bd, entities, heropool.LRUEjection) 32 33 // sanity checks 34 for i := heropool.EIndex(0); i < heropool.EIndex(len(entities)); i++ { 35 // since we are below limit, elements should be added sequentially at bucket 0. 36 // the ith added element has a key index of i+1, 37 // since 0 means unused key index in implementation. 38 require.Equal(t, bd.buckets[0].slots[i].slotAge, uint64(i+1)) 39 // also, since we have not yet over-limited, 40 // entities are assigned their entityIndex in the same order they are added. 41 require.Equal(t, bd.buckets[0].slots[i].entityIndex, i) 42 _, _, owner := bd.entities.Get(i) 43 require.Equal(t, owner, uint64(i)) 44 } 45 46 // all stored items must be retrievable 47 testRetrievableFrom(t, bd, entities, 0) 48 } 49 50 // TestArrayBackData_Adjust evaluates that Adjust method correctly updates the value of 51 // the desired entity while preserving the integrity of BackData. 52 func TestArrayBackData_Adjust(t *testing.T) { 53 limit := 100_000 54 55 bd := NewCache(uint32(limit), 56 8, 57 heropool.LRUEjection, 58 unittest.Logger(), 59 metrics.NewNoopCollector()) 60 61 entities := unittest.EntityListFixture(uint(limit)) 62 63 // adds all entities to backdata 64 testAddEntities(t, bd, entities, heropool.LRUEjection) 65 66 // picks a random entity from BackData and adjusts its identifier to a new one. 67 entityIndex := rand.Int() % limit 68 // checking integrity of retrieving entity 69 oldEntity, ok := bd.ByID(entities[entityIndex].ID()) 70 require.True(t, ok) 71 oldEntityID := oldEntity.ID() 72 require.Equal(t, entities[entityIndex].ID(), oldEntityID) 73 require.Equal(t, entities[entityIndex], oldEntity) 74 75 // picks a new identifier for the entity and makes sure it is different than its current one. 76 newEntityID := unittest.IdentifierFixture() 77 require.NotEqual(t, oldEntityID, newEntityID) 78 79 // adjusts old entity to a new entity with a new identifier 80 newEntity, ok := bd.Adjust(oldEntity.ID(), func(entity flow.Entity) flow.Entity { 81 mockEntity, ok := entity.(*unittest.MockEntity) 82 require.True(t, ok) 83 // oldEntity must be passed to func parameter of adjust. 84 require.Equal(t, oldEntityID, mockEntity.ID()) 85 require.Equal(t, oldEntity, mockEntity) 86 87 return &unittest.MockEntity{Identifier: newEntityID} 88 }) 89 90 // adjustment must be successful, and identifier must be updated. 91 require.True(t, ok) 92 require.Equal(t, newEntityID, newEntity.ID()) 93 newMockEntity, ok := newEntity.(*unittest.MockEntity) 94 require.True(t, ok) 95 96 // replaces new entity in the original reference list and 97 // retrieves all. 98 entities[entityIndex] = newMockEntity 99 testRetrievableFrom(t, bd, entities, 0) 100 101 // re-adjusting old entity must fail, since its identifier must no longer exist 102 entity, ok := bd.Adjust(oldEntityID, func(entity flow.Entity) flow.Entity { 103 require.Fail(t, "function must not be invoked on a non-existing entity") 104 return entity 105 }) 106 require.False(t, ok) 107 require.Nil(t, entity) 108 109 // similarly, retrieving old entity must fail 110 entity, ok = bd.ByID(oldEntityID) 111 require.False(t, ok) 112 require.Nil(t, entity) 113 114 ok = bd.Has(oldEntityID) 115 require.False(t, ok) 116 117 // adjusting any random non-existing identifier must fail 118 entity, ok = bd.Adjust(unittest.IdentifierFixture(), func(entity flow.Entity) flow.Entity { 119 require.Fail(t, "function must not be invoked on a non-existing entity") 120 return entity 121 }) 122 require.False(t, ok) 123 require.Nil(t, entity) 124 125 // adjustment must be idempotent for size 126 require.Equal(t, bd.Size(), uint(limit)) 127 } 128 129 // TestArrayBackData_AdjustWitInit evaluates that AdjustWithInit method. It should initialize and then adjust the value of 130 // non-existing entity while preserving the integrity of BackData on just adjusting the value of existing entity. 131 func TestArrayBackData_AdjustWitInit(t *testing.T) { 132 limit := 100_000 133 134 bd := NewCache(uint32(limit), 135 8, 136 heropool.LRUEjection, 137 unittest.Logger(), 138 metrics.NewNoopCollector()) 139 140 entities := unittest.EntityListFixture(uint(limit)) 141 for _, e := range entities { 142 adjustedEntity, adjusted := bd.AdjustWithInit(e.ID(), func(entity flow.Entity) flow.Entity { 143 // adjust logic, increments the nonce of the entity 144 mockEntity, ok := entity.(*unittest.MockEntity) 145 require.True(t, ok) 146 mockEntity.Nonce++ 147 return mockEntity 148 }, func() flow.Entity { 149 return e // initialize with the entity 150 }) 151 require.True(t, adjusted) 152 require.Equal(t, e.ID(), adjustedEntity.ID()) 153 require.Equal(t, uint64(1), adjustedEntity.(*unittest.MockEntity).Nonce) 154 } 155 156 // picks a random entity from BackData and adjusts its identifier to a new one. 157 entityIndex := rand.Int() % limit 158 // checking integrity of retrieving entity 159 oldEntity, ok := bd.ByID(entities[entityIndex].ID()) 160 require.True(t, ok) 161 oldEntityID := oldEntity.ID() 162 require.Equal(t, entities[entityIndex].ID(), oldEntityID) 163 require.Equal(t, entities[entityIndex], oldEntity) 164 165 // picks a new identifier for the entity and makes sure it is different than its current one. 166 newEntityID := unittest.IdentifierFixture() 167 require.NotEqual(t, oldEntityID, newEntityID) 168 169 // adjusts old entity to a new entity with a new identifier 170 newEntity, ok := bd.Adjust(oldEntity.ID(), func(entity flow.Entity) flow.Entity { 171 mockEntity, ok := entity.(*unittest.MockEntity) 172 require.True(t, ok) 173 // oldEntity must be passed to func parameter of adjust. 174 require.Equal(t, oldEntityID, mockEntity.ID()) 175 require.Equal(t, oldEntity, mockEntity) 176 177 // adjust logic, adjsuts the nonce of the entity 178 return &unittest.MockEntity{Identifier: newEntityID, Nonce: 2} 179 }) 180 181 // adjustment must be successful, and identifier must be updated. 182 require.True(t, ok) 183 require.Equal(t, newEntityID, newEntity.ID()) 184 require.Equal(t, uint64(2), newEntity.(*unittest.MockEntity).Nonce) 185 newMockEntity, ok := newEntity.(*unittest.MockEntity) 186 require.True(t, ok) 187 188 // replaces new entity in the original reference list and 189 // retrieves all. 190 entities[entityIndex] = newMockEntity 191 testRetrievableFrom(t, bd, entities, 0) 192 193 // re-adjusting old entity must fail, since its identifier must no longer exist 194 entity, ok := bd.Adjust(oldEntityID, func(entity flow.Entity) flow.Entity { 195 require.Fail(t, "function must not be invoked on a non-existing entity") 196 return entity 197 }) 198 require.False(t, ok) 199 require.Nil(t, entity) 200 201 // similarly, retrieving old entity must fail 202 entity, ok = bd.ByID(oldEntityID) 203 require.False(t, ok) 204 require.Nil(t, entity) 205 206 ok = bd.Has(oldEntityID) 207 require.False(t, ok) 208 } 209 210 // TestArrayBackData_GetWithInit evaluates that GetWithInit method. It should initialize and then retrieve the value of 211 // non-existing entity while preserving the integrity of BackData on just retrieving the value of existing entity. 212 func TestArrayBackData_GetWithInit(t *testing.T) { 213 limit := 1000 214 215 bd := NewCache(uint32(limit), 8, heropool.LRUEjection, unittest.Logger(), metrics.NewNoopCollector()) 216 217 entities := unittest.EntityListFixture(uint(limit)) 218 219 // GetWithInit 220 for _, e := range entities { 221 // all entities must be initialized retrieved successfully 222 actual, ok := bd.GetWithInit(e.ID(), func() flow.Entity { 223 return e // initialize with the entity 224 }) 225 require.True(t, ok) 226 require.Equal(t, e, actual) 227 } 228 229 // All 230 all := bd.All() 231 require.Equal(t, len(entities), len(all)) 232 for _, expected := range entities { 233 actual, ok := bd.ByID(expected.ID()) 234 require.True(t, ok) 235 require.Equal(t, expected, actual) 236 } 237 238 // Identifiers 239 ids := bd.Identifiers() 240 require.Equal(t, len(entities), len(ids)) 241 for _, id := range ids { 242 require.True(t, bd.Has(id)) 243 } 244 245 // Entities 246 actualEntities := bd.Entities() 247 require.Equal(t, len(entities), len(actualEntities)) 248 require.ElementsMatch(t, entities, actualEntities) 249 250 // Adjust 251 for _, e := range entities { 252 // all entities must be adjusted successfully 253 actual, ok := bd.Adjust(e.ID(), func(entity flow.Entity) flow.Entity { 254 // increment nonce of the entity 255 entity.(*unittest.MockEntity).Nonce++ 256 return entity 257 }) 258 require.True(t, ok) 259 require.Equal(t, e, actual) 260 } 261 262 // ByID; should return the latest version of the entity 263 for _, e := range entities { 264 // all entities must be retrieved successfully 265 actual, ok := bd.ByID(e.ID()) 266 require.True(t, ok) 267 require.Equal(t, e.ID(), actual.ID()) 268 require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) 269 } 270 271 // GetWithInit; should return the latest version of the entity, than increment the nonce 272 for _, e := range entities { 273 // all entities must be retrieved successfully 274 actual, ok := bd.GetWithInit(e.ID(), func() flow.Entity { 275 require.Fail(t, "should not be called") // entity has already been initialized 276 return e 277 }) 278 require.True(t, ok) 279 require.Equal(t, e.ID(), actual.ID()) 280 } 281 } 282 283 // TestArrayBackData_WriteHeavy evaluates correctness of Cache under the writing and retrieving 284 // a heavy load of entities up to its limit. All data must be written successfully and then retrievable. 285 func TestArrayBackData_WriteHeavy(t *testing.T) { 286 limit := 100_000 287 288 bd := NewCache(uint32(limit), 289 8, 290 heropool.LRUEjection, 291 unittest.Logger(), 292 metrics.NewNoopCollector()) 293 294 entities := unittest.EntityListFixture(uint(limit)) 295 296 // adds all entities to backdata 297 testAddEntities(t, bd, entities, heropool.LRUEjection) 298 299 // retrieves all entities from backdata 300 testRetrievableFrom(t, bd, entities, 0) 301 } 302 303 // TestArrayBackData_LRU_Ejection evaluates correctness of Cache under the writing and retrieving 304 // a heavy load of entities beyond its limit. With LRU ejection, only most recently written data must be maintained 305 // by mempool. 306 func TestArrayBackData_LRU_Ejection(t *testing.T) { 307 // mempool has the limit of 100K, but we put 1M 308 // (10 time more than its capacity) 309 limit := 100_000 310 items := uint(1_000_000) 311 312 bd := NewCache(uint32(limit), 313 8, 314 heropool.LRUEjection, 315 unittest.Logger(), 316 metrics.NewNoopCollector()) 317 318 entities := unittest.EntityListFixture(items) 319 320 // adds all entities to backdata 321 testAddEntities(t, bd, entities, heropool.LRUEjection) 322 323 // only last 100K (i.e., 900Kth forward) items must be retrievable, and 324 // the rest must be ejected. 325 testRetrievableFrom(t, bd, entities, 900_000) 326 } 327 328 // TestArrayBackData_No_Ejection evaluates correctness of Cache under the writing and retrieving 329 // a heavy load of entities beyond its limit. With NoEjection mode, the cache should refuse to add extra entities beyond 330 // its limit. 331 func TestArrayBackData_No_Ejection(t *testing.T) { 332 // mempool has the limit of 100K, but we put 1M 333 // (10 time more than its capacity) 334 limit := 100_000 335 items := uint(1_000_000) 336 337 bd := NewCache(uint32(limit), 338 8, 339 heropool.NoEjection, 340 unittest.Logger(), 341 metrics.NewNoopCollector()) 342 343 entities := unittest.EntityListFixture(items) 344 345 // adds all entities to backdata 346 testAddEntities(t, bd, entities, heropool.NoEjection) 347 348 // only last 100K (i.e., 900Kth forward) items must be retrievable, and 349 // the rest must be ejected. 350 testRetrievableInRange(t, bd, entities, 0, limit) 351 } 352 353 // TestArrayBackData_Random_Ejection evaluates correctness of Cache under the writing and retrieving 354 // a heavy load of entities beyond its limit. With random ejection, only as many entities as capacity of 355 // Cache must be retrievable. 356 func TestArrayBackData_Random_Ejection(t *testing.T) { 357 // mempool has the limit of 100K, but we put 1M 358 // (10 time more than its capacity) 359 limit := 100_000 360 items := uint(1_000_000) 361 362 bd := NewCache(uint32(limit), 363 8, 364 heropool.RandomEjection, 365 unittest.Logger(), 366 metrics.NewNoopCollector()) 367 368 entities := unittest.EntityListFixture(items) 369 370 // adds all entities to backdata 371 testAddEntities(t, bd, entities, heropool.RandomEjection) 372 373 // only 100K (random) items must be retrievable, as the rest 374 // are randomly ejected to make room. 375 testRetrievableCount(t, bd, entities, 100_000) 376 } 377 378 // TestArrayBackData_AddDuplicate evaluates that adding duplicate entity to Cache will fail without 379 // altering the internal state of it. 380 func TestArrayBackData_AddDuplicate(t *testing.T) { 381 limit := 100 382 383 bd := NewCache(uint32(limit), 384 8, 385 heropool.LRUEjection, 386 unittest.Logger(), 387 metrics.NewNoopCollector()) 388 389 entities := unittest.EntityListFixture(uint(limit)) 390 391 // adds all entities to backdata 392 testAddEntities(t, bd, entities, heropool.LRUEjection) 393 394 // adding duplicate entity should fail 395 for _, entity := range entities { 396 require.False(t, bd.Add(entity.ID(), entity)) 397 } 398 399 // still all entities must be retrievable from Cache. 400 testRetrievableFrom(t, bd, entities, 0) 401 } 402 403 // TestArrayBackData_Clear evaluates that calling Clear method removes all entities stored in BackData. 404 func TestArrayBackData_Clear(t *testing.T) { 405 limit := 100 406 407 bd := NewCache(uint32(limit), 408 8, 409 heropool.LRUEjection, 410 unittest.Logger(), 411 metrics.NewNoopCollector()) 412 413 entities := unittest.EntityListFixture(uint(limit)) 414 415 // adds all entities to backdata 416 testAddEntities(t, bd, entities, heropool.LRUEjection) 417 418 // still all must be retrievable from backdata 419 testRetrievableFrom(t, bd, entities, 0) 420 require.Equal(t, bd.Size(), uint(limit)) 421 require.Len(t, bd.All(), limit) 422 423 // calling clear must shrink size of BackData to zero 424 bd.Clear() 425 require.Equal(t, bd.Size(), uint(0)) 426 require.Len(t, bd.All(), 0) 427 428 // none of stored elements must be retrievable any longer 429 testRetrievableCount(t, bd, entities, 0) 430 } 431 432 // TestArrayBackData_All checks correctness of All method in returning all stored entities in it. 433 func TestArrayBackData_All(t *testing.T) { 434 tt := []struct { 435 limit uint32 436 items uint32 437 ejectionMode heropool.EjectionMode 438 }{ 439 { // mempool has the limit of 1000, but we put 100. 440 limit: 1000, 441 items: 100, 442 ejectionMode: heropool.LRUEjection, 443 }, 444 { // mempool has the limit of 1000, and we put exactly 1000 items. 445 limit: 1000, 446 items: 1000, 447 ejectionMode: heropool.LRUEjection, 448 }, 449 { // mempool has the limit of 1000, and we put 10K items with LRU ejection. 450 limit: 1000, 451 items: 10_000, 452 ejectionMode: heropool.LRUEjection, 453 }, 454 { // mempool has the limit of 1000, and we put 10K items with random ejection. 455 limit: 1000, 456 items: 10_000, 457 ejectionMode: heropool.RandomEjection, 458 }, 459 } 460 461 for _, tc := range tt { 462 t.Run(fmt.Sprintf("%d-limit-%d-items-%s-ejection", tc.limit, tc.items, tc.ejectionMode), func(t *testing.T) { 463 bd := NewCache(tc.limit, 464 8, 465 tc.ejectionMode, 466 unittest.Logger(), 467 metrics.NewNoopCollector()) 468 entities := unittest.EntityListFixture(uint(tc.items)) 469 470 testAddEntities(t, bd, entities, tc.ejectionMode) 471 472 if tc.ejectionMode == heropool.RandomEjection { 473 // in random ejection mode we count total number of matched entities 474 // with All map. 475 testMapMatchCount(t, bd.All(), entities, int(tc.limit)) 476 testEntitiesMatchCount(t, bd.Entities(), entities, int(tc.limit)) 477 testIdentifiersMatchCount(t, bd.Identifiers(), entities, int(tc.limit)) 478 } else { 479 // in LRU ejection mode we match All items based on a from index (i.e., last "from" items). 480 from := int(tc.items) - int(tc.limit) 481 if from < 0 { 482 // we are below limit, hence we start matching from index 0 483 from = 0 484 } 485 testMapMatchFrom(t, bd.All(), entities, from) 486 testEntitiesMatchFrom(t, bd.Entities(), entities, from) 487 testIdentifiersMatchFrom(t, bd.Identifiers(), entities, from) 488 } 489 }) 490 } 491 } 492 493 // TestArrayBackData_Remove checks correctness of removing elements from Cache. 494 func TestArrayBackData_Remove(t *testing.T) { 495 tt := []struct { 496 limit uint32 497 items uint32 498 from int // index start to be removed (set -1 to remove randomly) 499 count int // total elements to be removed 500 }{ 501 { // removing range with total items below the limit 502 limit: 100_000, 503 items: 10_000, 504 from: 188, 505 count: 2012, 506 }, 507 { // removing range from full Cache 508 limit: 100_000, 509 items: 100_000, 510 from: 50_333, 511 count: 6667, 512 }, 513 { // removing random from Cache with total items below its limit 514 limit: 100_000, 515 items: 10_000, 516 from: -1, 517 count: 6888, 518 }, 519 { // removing random from full Cache 520 limit: 100_000, 521 items: 10_000, 522 from: -1, 523 count: 7328, 524 }, 525 } 526 527 for _, tc := range tt { 528 t.Run(fmt.Sprintf("%d-limit-%d-items-%dfrom-%dcount", tc.limit, tc.items, tc.from, tc.count), func(t *testing.T) { 529 bd := NewCache( 530 tc.limit, 531 8, 532 heropool.RandomEjection, 533 unittest.Logger(), 534 metrics.NewNoopCollector()) 535 entities := unittest.EntityListFixture(uint(tc.items)) 536 537 testAddEntities(t, bd, entities, heropool.RandomEjection) 538 539 if tc.from == -1 { 540 // random removal 541 testRemoveAtRandom(t, bd, entities, tc.count) 542 // except removed ones, the rest must be retrievable 543 testRetrievableCount(t, bd, entities, uint64(int(tc.items)-tc.count)) 544 } else { 545 // removing a range 546 testRemoveRange(t, bd, entities, tc.from, tc.from+tc.count) 547 testCheckRangeRemoved(t, bd, entities, tc.from, tc.from+tc.count) 548 } 549 }) 550 } 551 } 552 553 // testAddEntities is a test helper that checks entities are added successfully to the Cache. 554 // and each entity is retrievable right after it is written to backdata. 555 func testAddEntities(t *testing.T, bd *Cache, entities []*unittest.MockEntity, ejection heropool.EjectionMode) { 556 // initially, head should be undefined 557 e, ok := bd.Head() 558 require.False(t, ok) 559 require.Nil(t, e) 560 561 // adding elements 562 for i, e := range entities { 563 if ejection == heropool.NoEjection && uint32(i) >= bd.sizeLimit { 564 // with no ejection when it goes beyond limit, the writes should be unsuccessful. 565 require.False(t, bd.Add(e.ID(), e)) 566 567 // the head should retrieve the first added entity. 568 headEntity, headExists := bd.Head() 569 require.True(t, headExists) 570 require.Equal(t, headEntity.ID(), entities[0].ID()) 571 } else { 572 // adding each element must be successful. 573 require.True(t, bd.Add(e.ID(), e)) 574 575 if uint32(i) < bd.sizeLimit { 576 // when we are below limit the size of 577 // Cache should be incremented by each addition. 578 require.Equal(t, bd.Size(), uint(i+1)) 579 580 // in case cache is not full, the head should retrieve the first added entity. 581 headEntity, headExists := bd.Head() 582 require.True(t, headExists) 583 require.Equal(t, headEntity.ID(), entities[0].ID()) 584 } else { 585 // when we cross the limit, the ejection kicks in, and 586 // size must be steady at the limit. 587 require.Equal(t, uint32(bd.Size()), bd.sizeLimit) 588 } 589 590 // entity should be immediately retrievable 591 actual, ok := bd.ByID(e.ID()) 592 require.True(t, ok) 593 require.Equal(t, e, actual) 594 } 595 } 596 } 597 598 // testRetrievableInRange is a test helper that evaluates that all entities starting from given index are retrievable from Cache. 599 func testRetrievableFrom(t *testing.T, bd *Cache, entities []*unittest.MockEntity, from int) { 600 testRetrievableInRange(t, bd, entities, from, len(entities)) 601 } 602 603 // testRetrievableInRange is a test helper that evaluates within given range [from, to) are retrievable from Cache. 604 func testRetrievableInRange(t *testing.T, bd *Cache, entities []*unittest.MockEntity, from int, to int) { 605 for i := range entities { 606 expected := entities[i] 607 actual, ok := bd.ByID(expected.ID()) 608 if i < from || i >= to { 609 require.False(t, ok, i) 610 require.Nil(t, actual) 611 } else { 612 require.True(t, ok) 613 require.Equal(t, expected, actual) 614 } 615 } 616 } 617 618 // testRemoveAtRandom is a test helper removes specified number of entities from Cache at random. 619 func testRemoveAtRandom(t *testing.T, bd *Cache, entities []*unittest.MockEntity, count int) { 620 for removedCount := 0; removedCount < count; { 621 unittest.RequireReturnsBefore(t, func() { 622 index := rand.Int() % len(entities) 623 expected, removed := bd.Remove(entities[index].ID()) 624 if !removed { 625 return 626 } 627 require.Equal(t, entities[index], expected) 628 removedCount++ 629 // size sanity check after removal 630 require.Equal(t, bd.Size(), uint(len(entities)-removedCount)) 631 }, 100*time.Millisecond, "could not find element to remove") 632 } 633 } 634 635 // testRemoveRange is a test helper that removes specified range of entities from Cache. 636 func testRemoveRange(t *testing.T, bd *Cache, entities []*unittest.MockEntity, from int, to int) { 637 for i := from; i < to; i++ { 638 expected, removed := bd.Remove(entities[i].ID()) 639 require.True(t, removed) 640 require.Equal(t, entities[i], expected) 641 // size sanity check after removal 642 require.Equal(t, bd.Size(), uint(len(entities)-(i-from)-1)) 643 } 644 } 645 646 // testCheckRangeRemoved is a test helper that evaluates the specified range of entities have been removed from Cache. 647 func testCheckRangeRemoved(t *testing.T, bd *Cache, entities []*unittest.MockEntity, from int, to int) { 648 for i := from; i < to; i++ { 649 // both removal and retrieval must fail 650 expected, removed := bd.Remove(entities[i].ID()) 651 require.False(t, removed) 652 require.Nil(t, expected) 653 654 expected, exists := bd.ByID(entities[i].ID()) 655 require.False(t, exists) 656 require.Nil(t, expected) 657 } 658 } 659 660 // testMapMatchFrom is a test helper that checks entities are retrievable from entitiesMap starting specified index. 661 func testMapMatchFrom(t *testing.T, entitiesMap map[flow.Identifier]flow.Entity, entities []*unittest.MockEntity, from int) { 662 require.Len(t, entitiesMap, len(entities)-from) 663 664 for i := range entities { 665 expected := entities[i] 666 actual, ok := entitiesMap[expected.ID()] 667 if i < from { 668 require.False(t, ok, i) 669 require.Nil(t, actual) 670 } else { 671 require.True(t, ok) 672 require.Equal(t, expected, actual) 673 } 674 } 675 } 676 677 // testEntitiesMatchFrom is a test helper that checks entities are retrievable from given list starting specified index. 678 func testEntitiesMatchFrom(t *testing.T, expectedEntities []flow.Entity, actualEntities []*unittest.MockEntity, from int) { 679 require.Len(t, expectedEntities, len(actualEntities)-from) 680 681 for i, actual := range actualEntities { 682 if i < from { 683 require.NotContains(t, expectedEntities, actual) 684 } else { 685 require.Contains(t, expectedEntities, actual) 686 } 687 } 688 } 689 690 // testIdentifiersMatchFrom is a test helper that checks identifiers of entities are retrievable from given list starting specified index. 691 func testIdentifiersMatchFrom(t *testing.T, expectedIdentifiers flow.IdentifierList, actualEntities []*unittest.MockEntity, from int) { 692 require.Len(t, expectedIdentifiers, len(actualEntities)-from) 693 694 for i, actual := range actualEntities { 695 if i < from { 696 require.NotContains(t, expectedIdentifiers, actual.ID()) 697 } else { 698 require.Contains(t, expectedIdentifiers, actual.ID()) 699 } 700 } 701 } 702 703 // testMapMatchFrom is a test helper that checks specified number of entities are retrievable from entitiesMap. 704 func testMapMatchCount(t *testing.T, entitiesMap map[flow.Identifier]flow.Entity, entities []*unittest.MockEntity, count int) { 705 require.Len(t, entitiesMap, count) 706 actualCount := 0 707 for i := range entities { 708 expected := entities[i] 709 actual, ok := entitiesMap[expected.ID()] 710 if !ok { 711 continue 712 } 713 require.Equal(t, expected, actual) 714 actualCount++ 715 } 716 require.Equal(t, count, actualCount) 717 } 718 719 // testEntitiesMatchCount is a test helper that checks specified number of entities are retrievable from given list. 720 func testEntitiesMatchCount(t *testing.T, expectedEntities []flow.Entity, actualEntities []*unittest.MockEntity, count int) { 721 entitiesMap := make(map[flow.Identifier]flow.Entity) 722 723 // converts expected entities list to a map in order to utilize a test helper. 724 for _, expected := range expectedEntities { 725 entitiesMap[expected.ID()] = expected 726 } 727 728 testMapMatchCount(t, entitiesMap, actualEntities, count) 729 } 730 731 // testIdentifiersMatchCount is a test helper that checks specified number of entities are retrievable from given list. 732 func testIdentifiersMatchCount(t *testing.T, expectedIdentifiers flow.IdentifierList, actualEntities []*unittest.MockEntity, count int) { 733 idMap := make(map[flow.Identifier]struct{}) 734 735 // converts expected identifiers to a map. 736 for _, expectedId := range expectedIdentifiers { 737 idMap[expectedId] = struct{}{} 738 } 739 740 require.Len(t, idMap, count) 741 actualCount := 0 742 for _, e := range actualEntities { 743 _, ok := idMap[e.ID()] 744 if !ok { 745 continue 746 } 747 actualCount++ 748 } 749 require.Equal(t, count, actualCount) 750 } 751 752 // testRetrievableCount is a test helper that checks the number of retrievable entities from backdata exactly matches 753 // the expectedCount. 754 func testRetrievableCount(t *testing.T, bd *Cache, entities []*unittest.MockEntity, expectedCount uint64) { 755 actualCount := 0 756 757 for i := range entities { 758 expected := entities[i] 759 actual, ok := bd.ByID(expected.ID()) 760 if !ok { 761 continue 762 } 763 require.Equal(t, expected, actual) 764 actualCount++ 765 } 766 767 require.Equal(t, int(expectedCount), actualCount) 768 }