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 }