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  }