github.com/MetalBlockchain/metalgo@v1.11.9/x/sync/workheap_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package sync 5 6 import ( 7 "bytes" 8 "math/rand" 9 "slices" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 15 "github.com/MetalBlockchain/metalgo/ids" 16 "github.com/MetalBlockchain/metalgo/utils/maybe" 17 ) 18 19 // Tests Insert and GetWork 20 func Test_WorkHeap_Insert_GetWork(t *testing.T) { 21 require := require.New(t) 22 h := newWorkHeap() 23 24 lowPriorityItem := &workItem{ 25 start: maybe.Some([]byte{4}), 26 end: maybe.Some([]byte{5}), 27 priority: lowPriority, 28 localRootID: ids.GenerateTestID(), 29 } 30 mediumPriorityItem := &workItem{ 31 start: maybe.Some([]byte{0}), 32 end: maybe.Some([]byte{1}), 33 priority: medPriority, 34 localRootID: ids.GenerateTestID(), 35 } 36 highPriorityItem := &workItem{ 37 start: maybe.Some([]byte{2}), 38 end: maybe.Some([]byte{3}), 39 priority: highPriority, 40 localRootID: ids.GenerateTestID(), 41 } 42 h.Insert(highPriorityItem) 43 h.Insert(mediumPriorityItem) 44 h.Insert(lowPriorityItem) 45 require.Equal(3, h.Len()) 46 47 // Ensure [sortedItems] is in right order. 48 got := []*workItem{} 49 h.sortedItems.Ascend( 50 func(i *workItem) bool { 51 got = append(got, i) 52 return true 53 }, 54 ) 55 require.Equal( 56 []*workItem{mediumPriorityItem, highPriorityItem, lowPriorityItem}, 57 got, 58 ) 59 60 // Ensure priorities are in right order. 61 gotItem := h.GetWork() 62 require.Equal(highPriorityItem, gotItem) 63 gotItem = h.GetWork() 64 require.Equal(mediumPriorityItem, gotItem) 65 gotItem = h.GetWork() 66 require.Equal(lowPriorityItem, gotItem) 67 gotItem = h.GetWork() 68 require.Nil(gotItem) 69 70 require.Zero(h.Len()) 71 } 72 73 func Test_WorkHeap_remove(t *testing.T) { 74 require := require.New(t) 75 76 h := newWorkHeap() 77 78 lowPriorityItem := &workItem{ 79 start: maybe.Some([]byte{0}), 80 end: maybe.Some([]byte{1}), 81 priority: lowPriority, 82 localRootID: ids.GenerateTestID(), 83 } 84 85 mediumPriorityItem := &workItem{ 86 start: maybe.Some([]byte{2}), 87 end: maybe.Some([]byte{3}), 88 priority: medPriority, 89 localRootID: ids.GenerateTestID(), 90 } 91 92 highPriorityItem := &workItem{ 93 start: maybe.Some([]byte{4}), 94 end: maybe.Some([]byte{5}), 95 priority: highPriority, 96 localRootID: ids.GenerateTestID(), 97 } 98 99 h.Insert(lowPriorityItem) 100 101 wrappedLowPriorityItem, ok := h.innerHeap.Peek() 102 require.True(ok) 103 h.remove(wrappedLowPriorityItem) 104 105 require.Zero(h.Len()) 106 require.Zero(h.sortedItems.Len()) 107 108 h.Insert(lowPriorityItem) 109 h.Insert(mediumPriorityItem) 110 h.Insert(highPriorityItem) 111 112 wrappedhighPriorityItem, ok := h.innerHeap.Peek() 113 require.True(ok) 114 require.Equal(highPriorityItem, wrappedhighPriorityItem) 115 h.remove(wrappedhighPriorityItem) 116 require.Equal(2, h.Len()) 117 require.Equal(2, h.sortedItems.Len()) 118 got, ok := h.innerHeap.Peek() 119 require.True(ok) 120 require.Equal(mediumPriorityItem, got) 121 122 wrappedMediumPriorityItem, ok := h.innerHeap.Peek() 123 require.True(ok) 124 require.Equal(mediumPriorityItem, wrappedMediumPriorityItem) 125 h.remove(wrappedMediumPriorityItem) 126 require.Equal(1, h.Len()) 127 require.Equal(1, h.sortedItems.Len()) 128 got, ok = h.innerHeap.Peek() 129 require.True(ok) 130 require.Equal(lowPriorityItem, got) 131 132 wrappedLowPriorityItem, ok = h.innerHeap.Peek() 133 require.True(ok) 134 require.Equal(lowPriorityItem, wrappedLowPriorityItem) 135 h.remove(wrappedLowPriorityItem) 136 require.Zero(h.Len()) 137 require.Zero(h.sortedItems.Len()) 138 } 139 140 func Test_WorkHeap_Merge_Insert(t *testing.T) { 141 // merge with range before 142 syncHeap := newWorkHeap() 143 144 syncHeap.MergeInsert(&workItem{start: maybe.Nothing[[]byte](), end: maybe.Some([]byte{63})}) 145 require.Equal(t, 1, syncHeap.Len()) 146 147 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{127}), end: maybe.Some([]byte{192})}) 148 require.Equal(t, 2, syncHeap.Len()) 149 150 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{193}), end: maybe.Nothing[[]byte]()}) 151 require.Equal(t, 3, syncHeap.Len()) 152 153 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{63}), end: maybe.Some([]byte{126}), priority: lowPriority}) 154 require.Equal(t, 3, syncHeap.Len()) 155 156 // merge with range after 157 syncHeap = newWorkHeap() 158 159 syncHeap.MergeInsert(&workItem{start: maybe.Nothing[[]byte](), end: maybe.Some([]byte{63})}) 160 require.Equal(t, 1, syncHeap.Len()) 161 162 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{127}), end: maybe.Some([]byte{192})}) 163 require.Equal(t, 2, syncHeap.Len()) 164 165 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{193}), end: maybe.Nothing[[]byte]()}) 166 require.Equal(t, 3, syncHeap.Len()) 167 168 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{64}), end: maybe.Some([]byte{127}), priority: lowPriority}) 169 require.Equal(t, 3, syncHeap.Len()) 170 171 // merge both sides at the same time 172 syncHeap = newWorkHeap() 173 174 syncHeap.MergeInsert(&workItem{start: maybe.Nothing[[]byte](), end: maybe.Some([]byte{63})}) 175 require.Equal(t, 1, syncHeap.Len()) 176 177 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{127}), end: maybe.Nothing[[]byte]()}) 178 require.Equal(t, 2, syncHeap.Len()) 179 180 syncHeap.MergeInsert(&workItem{start: maybe.Some([]byte{63}), end: maybe.Some([]byte{127}), priority: lowPriority}) 181 require.Equal(t, 1, syncHeap.Len()) 182 } 183 184 func TestWorkHeapMergeInsertRandom(t *testing.T) { 185 var ( 186 require = require.New(t) 187 seed = time.Now().UnixNano() 188 rand = rand.New(rand.NewSource(seed)) // #nosec G404 189 numRanges = 1_000 190 bounds = [][]byte{} 191 rootID = ids.GenerateTestID() 192 ) 193 t.Logf("seed: %d", seed) 194 195 // Create start and end bounds 196 for i := 0; i < numRanges; i++ { 197 bound := make([]byte, 32) 198 _, _ = rand.Read(bound) 199 bounds = append(bounds, bound) 200 } 201 slices.SortFunc(bounds, bytes.Compare) 202 203 // Note that start < end for all ranges. 204 // It is possible but extremely unlikely that 205 // two elements of [bounds] are equal. 206 ranges := []workItem{} 207 for i := 0; i < numRanges/2; i++ { 208 start := bounds[i*2] 209 end := bounds[i*2+1] 210 ranges = append(ranges, workItem{ 211 start: maybe.Some(start), 212 end: maybe.Some(end), 213 priority: lowPriority, 214 // Note they all share the same root ID. 215 localRootID: rootID, 216 }) 217 } 218 // Set beginning of first range to Nothing. 219 ranges[0].start = maybe.Nothing[[]byte]() 220 // Set end of last range to Nothing. 221 ranges[len(ranges)-1].end = maybe.Nothing[[]byte]() 222 223 setup := func() *workHeap { 224 // Insert all the ranges into the heap. 225 h := newWorkHeap() 226 for i, r := range ranges { 227 require.Equal(i, h.Len()) 228 rCopy := r 229 h.MergeInsert(&rCopy) 230 } 231 return h 232 } 233 234 { 235 // Case 1: Merging an item with the range before and after 236 h := setup() 237 // Keep merging ranges until there's only one range left. 238 for i := 0; i < len(ranges)-1; i++ { 239 // Merge ranges[i] with ranges[i+1] 240 h.MergeInsert(&workItem{ 241 start: ranges[i].end, 242 end: ranges[i+1].start, 243 priority: lowPriority, 244 localRootID: rootID, 245 }) 246 require.Equal(len(ranges)-i-1, h.Len()) 247 } 248 got := h.GetWork() 249 require.True(got.start.IsNothing()) 250 require.True(got.end.IsNothing()) 251 } 252 253 { 254 // Case 2: Merging an item with the range before 255 h := setup() 256 for i := 0; i < len(ranges)-1; i++ { 257 // Extend end of ranges[i] 258 newEnd := slices.Clone(ranges[i].end.Value()) 259 newEnd = append(newEnd, 0) 260 h.MergeInsert(&workItem{ 261 start: ranges[i].end, 262 end: maybe.Some(newEnd), 263 priority: lowPriority, 264 localRootID: rootID, 265 }) 266 267 // Shouldn't cause number of elements to change 268 require.Equal(len(ranges), h.Len()) 269 270 start := ranges[i].start 271 if i == 0 { 272 start = maybe.Nothing[[]byte]() 273 } 274 // Make sure end is updated 275 got, ok := h.sortedItems.Get(&workItem{ 276 start: start, 277 }) 278 require.True(ok) 279 require.Equal(newEnd, got.end.Value()) 280 } 281 } 282 283 { 284 // Case 3: Merging an item with the range after 285 h := setup() 286 for i := 1; i < len(ranges); i++ { 287 // Extend start of ranges[i] 288 newStartBytes := slices.Clone(ranges[i].start.Value()) 289 newStartBytes = newStartBytes[:len(newStartBytes)-1] 290 newStart := maybe.Some(newStartBytes) 291 292 h.MergeInsert(&workItem{ 293 start: newStart, 294 end: ranges[i].start, 295 priority: lowPriority, 296 localRootID: rootID, 297 }) 298 299 // Shouldn't cause number of elements to change 300 require.Equal(len(ranges), h.Len()) 301 302 // Make sure start is updated 303 got, ok := h.sortedItems.Get(&workItem{ 304 start: newStart, 305 }) 306 require.True(ok) 307 require.Equal(newStartBytes, got.start.Value()) 308 } 309 } 310 }