github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/stdmap/identifier_map_test.go (about)

     1  package stdmap
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/onflow/flow-go/utils/unittest"
    13  )
    14  
    15  func TestIdentiferMap(t *testing.T) {
    16  	idMap, err := NewIdentifierMap(10)
    17  
    18  	t.Run("creating new mempool", func(t *testing.T) {
    19  		require.NoError(t, err)
    20  	})
    21  
    22  	key1 := unittest.IdentifierFixture()
    23  	id1 := unittest.IdentifierFixture()
    24  	t.Run("appending id to new key", func(t *testing.T) {
    25  		err := idMap.Append(key1, id1)
    26  		require.NoError(t, err)
    27  
    28  		// checks the existence of id1 for key
    29  		ids, ok := idMap.Get(key1)
    30  		require.True(t, ok)
    31  		require.Contains(t, ids, id1)
    32  	})
    33  
    34  	id2 := unittest.IdentifierFixture()
    35  	t.Run("appending the second id", func(t *testing.T) {
    36  		err := idMap.Append(key1, id2)
    37  		require.NoError(t, err)
    38  
    39  		// checks the existence of both id1 and id2 for key1
    40  		ids, ok := idMap.Get(key1)
    41  		require.True(t, ok)
    42  		// both ids should exist
    43  		assert.Contains(t, ids, id1)
    44  		assert.Contains(t, ids, id2)
    45  	})
    46  
    47  	// tests against existence of another key, with a shared id (id1)
    48  	key2 := unittest.IdentifierFixture()
    49  	t.Run("appending shared id to another key", func(t *testing.T) {
    50  		err := idMap.Append(key2, id1)
    51  		require.NoError(t, err)
    52  
    53  		// checks the existence of both id1 and id2 for key1
    54  		ids, ok := idMap.Get(key1)
    55  		require.True(t, ok)
    56  		// both ids should exist
    57  		assert.Contains(t, ids, id1)
    58  		assert.Contains(t, ids, id2)
    59  
    60  		// checks the existence of  id1 for key2
    61  		ids, ok = idMap.Get(key2)
    62  		require.True(t, ok)
    63  		assert.Contains(t, ids, id1)
    64  		assert.NotContains(t, ids, id2)
    65  	})
    66  
    67  	t.Run("getting all keys", func(t *testing.T) {
    68  		keys, ok := idMap.Keys()
    69  
    70  		// Keys should return all keys in mempool
    71  		require.True(t, ok)
    72  		assert.Contains(t, keys, key1)
    73  		assert.Contains(t, keys, key2)
    74  	})
    75  
    76  	// tests against removing a key
    77  	t.Run("removing key", func(t *testing.T) {
    78  		ok := idMap.Remove(key1)
    79  		require.True(t, ok)
    80  
    81  		// getting removed key should return false
    82  		ids, ok := idMap.Get(key1)
    83  		require.False(t, ok)
    84  		require.Nil(t, ids)
    85  
    86  		// since key1 is removed, Has on it should return false
    87  		require.False(t, idMap.Has(key1))
    88  
    89  		// checks the existence of  id1 for key2
    90  		// removing key1 should not alter key2
    91  		ids, ok = idMap.Get(key2)
    92  		require.True(t, ok)
    93  		assert.Contains(t, ids, id1)
    94  		assert.NotContains(t, ids, id2)
    95  
    96  		// since key2 exists, Has on it should return true
    97  		require.True(t, idMap.Has(key2))
    98  
    99  		// Keys method should only return key2
   100  		keys, ok := idMap.Keys()
   101  		require.True(t, ok)
   102  		assert.NotContains(t, keys, key1)
   103  		assert.Contains(t, keys, key2)
   104  	})
   105  
   106  	// tests against appending an existing id for a key
   107  	t.Run("duplicate id for a key", func(t *testing.T) {
   108  		ids, ok := idMap.Get(key2)
   109  		require.True(t, ok)
   110  		assert.Contains(t, ids, id1)
   111  
   112  		err := idMap.Append(key2, id1)
   113  		require.NoError(t, err)
   114  	})
   115  
   116  	t.Run("removing id from a key test", func(t *testing.T) {
   117  		// creates key3 and adds id1 and id2 to it.
   118  		key3 := unittest.IdentifierFixture()
   119  		err := idMap.Append(key3, id1)
   120  		require.NoError(t, err)
   121  		err = idMap.Append(key3, id2)
   122  		require.NoError(t, err)
   123  
   124  		// removes id1 and id2 from key3
   125  		// removing id1
   126  		err = idMap.RemoveIdFromKey(key3, id1)
   127  		require.NoError(t, err)
   128  
   129  		// key3 should still reside on idMap and id2 should be attached to it
   130  		require.True(t, idMap.Has(key3))
   131  		ids, ok := idMap.Get(key3)
   132  		require.True(t, ok)
   133  		require.Contains(t, ids, id2)
   134  
   135  		// removing id2
   136  		err = idMap.RemoveIdFromKey(key3, id2)
   137  		require.NoError(t, err)
   138  
   139  		// by removing id2 from key3, it is left out of id
   140  		// so it should be automatically cleaned up
   141  		require.False(t, idMap.Has(key3))
   142  
   143  		ids, ok = idMap.Get(key3)
   144  		require.False(t, ok)
   145  		require.Empty(t, ids)
   146  
   147  		// it however should not affect any other keys
   148  		require.True(t, idMap.Has(key2))
   149  	})
   150  }
   151  
   152  // TestRaceCondition is meant for running with `-race` flag.
   153  // It performs Append, Has, Get, and RemoveIdFromKey methods of IdentifierMap concurrently
   154  // each in a different goroutine.
   155  // Running this test with `-race` flag detects and reports the existence of race condition if
   156  // it is the case.
   157  func TestRaceCondition(t *testing.T) {
   158  	idMap, err := NewIdentifierMap(10)
   159  	require.NoError(t, err)
   160  
   161  	wg := sync.WaitGroup{}
   162  
   163  	key := unittest.IdentifierFixture()
   164  	id := unittest.IdentifierFixture()
   165  	wg.Add(4)
   166  
   167  	go func() {
   168  		defer wg.Done()
   169  		require.NoError(t, idMap.Append(key, id))
   170  	}()
   171  
   172  	go func() {
   173  		defer wg.Done()
   174  		idMap.Has(key)
   175  	}()
   176  
   177  	go func() {
   178  		defer wg.Done()
   179  		idMap.Get(key)
   180  	}()
   181  
   182  	go func() {
   183  		defer wg.Done()
   184  		require.NoError(t, idMap.RemoveIdFromKey(key, id))
   185  	}()
   186  
   187  	unittest.RequireReturnsBefore(t, wg.Wait, 1*time.Second, "test could not finish on time")
   188  }
   189  
   190  // TestCapacity defines an identifier map with size limit of `limit`. It then
   191  // starts adding `swarm`-many items concurrently to the map each on a separate goroutine,
   192  // where `swarm` > `limit`,
   193  // and evaluates that size of the map stays within the limit.
   194  func TestCapacity(t *testing.T) {
   195  	const (
   196  		limit = 20
   197  		swarm = 20
   198  	)
   199  	idMap, err := NewIdentifierMap(limit)
   200  	require.NoError(t, err)
   201  
   202  	wg := sync.WaitGroup{}
   203  	wg.Add(swarm)
   204  
   205  	for i := 0; i < swarm; i++ {
   206  		go func() {
   207  			// adds an item on a separate goroutine
   208  			key := unittest.IdentifierFixture()
   209  			id := unittest.IdentifierFixture()
   210  			err := idMap.Append(key, id)
   211  			require.NoError(t, err)
   212  
   213  			// evaluates that the size remains in the permissible range
   214  			require.True(t, idMap.Size() <= uint(limit),
   215  				fmt.Sprintf("size violation: should be at most: %d, got: %d", limit, idMap.Size()))
   216  			wg.Done()
   217  		}()
   218  	}
   219  
   220  	unittest.RequireReturnsBefore(t, wg.Wait, 1*time.Second, "test could not finish on time")
   221  }