github.com/onflow/flow-go@v0.33.17/module/mempool/queue/heroQueue_test.go (about) 1 package queue_test 2 3 import ( 4 "sync" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/onflow/flow-go/module/mempool/queue" 11 "github.com/onflow/flow-go/module/metrics" 12 "github.com/onflow/flow-go/utils/unittest" 13 ) 14 15 // TestHeroQueue_Sequential evaluates correctness of queue implementation against sequential push and pop. 16 func TestHeroQueue_Sequential(t *testing.T) { 17 sizeLimit := 100 18 q := queue.NewHeroQueue(uint32(sizeLimit), unittest.Logger(), metrics.NewNoopCollector()) 19 20 // initially queue must be zero 21 require.Zero(t, q.Size()) 22 23 // initially there should be nothing to pop 24 entity, ok := q.Pop() 25 require.False(t, ok) 26 require.Nil(t, entity) 27 28 entities := unittest.MockEntityListFixture(sizeLimit) 29 // pushing entities sequentially. 30 for i, e := range entities { 31 require.True(t, q.Push(*e)) 32 33 // duplicate push should fail 34 require.False(t, q.Push(*e)) 35 36 require.Equal(t, q.Size(), uint(i+1)) 37 } 38 39 // once queue meets the size limit, any extra push should fail. 40 for i := 0; i < 100; i++ { 41 require.False(t, q.Push(*unittest.MockEntityFixture())) 42 43 // size should not change 44 require.Equal(t, q.Size(), uint(sizeLimit)) 45 } 46 47 // pop-ing entities sequentially. 48 for i, e := range entities { 49 popedE, ok := q.Pop() 50 require.True(t, ok) 51 require.Equal(t, *e, popedE) 52 require.Equal(t, e.ID(), popedE.ID()) 53 54 require.Equal(t, q.Size(), uint(len(entities)-i-1)) 55 } 56 } 57 58 // TestHeroQueue_Concurrent evaluates correctness of queue implementation against concurrent push and pop. 59 func TestHeroQueue_Concurrent(t *testing.T) { 60 sizeLimit := 100 61 q := queue.NewHeroQueue(uint32(sizeLimit), unittest.Logger(), metrics.NewNoopCollector()) 62 // initially queue must be zero 63 require.Zero(t, q.Size()) 64 // initially there should be nothing to pop 65 entity, ok := q.Pop() 66 require.False(t, ok) 67 require.Nil(t, entity) 68 69 pushWG := &sync.WaitGroup{} 70 pushWG.Add(sizeLimit) 71 72 entities := unittest.MockEntityListFixture(sizeLimit) 73 // pushing entities concurrently. 74 for _, e := range entities { 75 e := e // suppress loop variable 76 go func() { 77 require.True(t, q.Push(*e)) 78 pushWG.Done() 79 }() 80 } 81 unittest.RequireReturnsBefore(t, pushWG.Wait, 100*time.Millisecond, "could not push all entities on time") 82 83 // once queue meets the size limit, any extra push should fail. 84 pushWG.Add(sizeLimit) 85 for i := 0; i < sizeLimit; i++ { 86 go func() { 87 require.False(t, q.Push(*unittest.MockEntityFixture())) 88 pushWG.Done() 89 }() 90 } 91 unittest.RequireReturnsBefore(t, pushWG.Wait, 100*time.Millisecond, "could not push all entities on time") 92 93 popWG := &sync.WaitGroup{} 94 popWG.Add(sizeLimit) 95 matchLock := &sync.Mutex{} 96 97 // pop-ing entities concurrently. 98 for i := 0; i < sizeLimit; i++ { 99 go func() { 100 popedE, ok := q.Pop() 101 require.True(t, ok) 102 103 matchLock.Lock() 104 matchAndRemoveEntity(t, entities, popedE.(unittest.MockEntity)) 105 matchLock.Unlock() 106 107 popWG.Done() 108 }() 109 } 110 unittest.RequireReturnsBefore(t, popWG.Wait, 100*time.Millisecond, "could not pop all entities on time") 111 112 // queue must be empty after pop-ing all 113 require.Zero(t, q.Size()) 114 } 115 116 // matchAndRemove checks existence of the entity in the "entities" array, and if a match is found, it is removed. 117 // If no match is found for an entity, it fails the test. 118 func matchAndRemoveEntity(t *testing.T, entities []*unittest.MockEntity, entity unittest.MockEntity) []*unittest.MockEntity { 119 for i, e := range entities { 120 if *e == entity { 121 // removes the matched entity from the list 122 entities = append(entities[:i], entities[i+1:]...) 123 return entities 124 } 125 } 126 127 // no entity found in the list to match 128 require.Fail(t, "could not find a match for an entity") 129 return nil 130 }