github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/verification/fetcher/chunkconsumer/consumer_test.go (about) 1 package chunkconsumer_test 2 3 import ( 4 "fmt" 5 "math/rand" 6 "sync" 7 "testing" 8 9 "github.com/dgraph-io/badger/v2" 10 "github.com/stretchr/testify/require" 11 "go.uber.org/atomic" 12 13 "github.com/onflow/flow-go/engine/verification/fetcher/chunkconsumer" 14 "github.com/onflow/flow-go/model/chunks" 15 "github.com/onflow/flow-go/module" 16 "github.com/onflow/flow-go/module/metrics" 17 storage "github.com/onflow/flow-go/storage/badger" 18 "github.com/onflow/flow-go/utils/unittest" 19 ) 20 21 // TestChunkLocatorToJob evaluates that a chunk locator can be converted to a job, 22 // and its corresponding job can be converted back to the same locator. 23 func TestChunkLocatorToJob(t *testing.T) { 24 locator := unittest.ChunkLocatorFixture(unittest.IdentifierFixture(), rand.Uint64()) 25 actual, err := chunkconsumer.JobToChunkLocator(chunkconsumer.ChunkLocatorToJob(locator)) 26 require.NoError(t, err) 27 require.Equal(t, locator, actual) 28 } 29 30 // TestProduceConsume evaluates different scenarios on passing jobs to chunk queue with 3 workers on the consumer side. It evaluates blocking and 31 // none-blocking engines attached to the workers in sequential and concurrent scenarios. 32 func TestProduceConsume(t *testing.T) { 33 // pushing 10 jobs sequentially to chunk queue, with 3 workers on consumer and the engine blocking on the jobs, 34 // results in engine only receiving 3 jobs. 35 t.Run("pushing 10 jobs receive 3", func(t *testing.T) { 36 var called chunks.LocatorList 37 lock := &sync.Mutex{} 38 neverFinish := func(notifier module.ProcessingNotifier, locator *chunks.Locator) { 39 lock.Lock() 40 defer lock.Unlock() 41 called = append(called, locator) 42 } 43 WithConsumer(t, neverFinish, func(consumer *chunkconsumer.ChunkConsumer, chunksQueue *storage.ChunksQueue) { 44 <-consumer.Ready() 45 46 locators := unittest.ChunkLocatorListFixture(10) 47 48 for i, locator := range locators { 49 ok, err := chunksQueue.StoreChunkLocator(locator) 50 require.NoError(t, err, fmt.Sprintf("chunk locator %v can't be stored", i)) 51 require.True(t, ok) 52 consumer.Check() // notify the consumer 53 } 54 55 <-consumer.Done() 56 57 // expect the mock engine receive only the first 3 calls (since it is blocked on those, hence no 58 // new job is fetched to process). 59 require.Equal(t, locators[:3], called) 60 }) 61 }) 62 63 // pushing 10 jobs sequentially to chunk queue, with 3 workers on consumer and the engine immediately finishing the job, 64 // results in engine eventually receiving all 10 jobs. 65 t.Run("pushing 10 receive 10", func(t *testing.T) { 66 var called chunks.LocatorList 67 lock := &sync.Mutex{} 68 var finishAll sync.WaitGroup 69 alwaysFinish := func(notifier module.ProcessingNotifier, locator *chunks.Locator) { 70 lock.Lock() 71 defer lock.Unlock() 72 called = append(called, locator) 73 finishAll.Add(1) 74 go func() { 75 notifier.Notify(locator.ID()) 76 finishAll.Done() 77 }() 78 } 79 WithConsumer(t, alwaysFinish, func(consumer *chunkconsumer.ChunkConsumer, chunksQueue *storage.ChunksQueue) { 80 <-consumer.Ready() 81 82 locators := unittest.ChunkLocatorListFixture(10) 83 84 for i, locator := range locators { 85 ok, err := chunksQueue.StoreChunkLocator(locator) 86 require.NoError(t, err, fmt.Sprintf("chunk locator %v can't be stored", i)) 87 require.True(t, ok) 88 consumer.Check() // notify the consumer 89 } 90 91 <-consumer.Done() 92 finishAll.Wait() // wait until all finished 93 // expect the mock engine receives all 10 calls 94 require.Equal(t, locators, called) 95 }) 96 }) 97 98 // pushing 100 jobs concurrently to chunk queue, with 3 workers on consumer and the engine immediately finishing the job, 99 // results in engine eventually receiving all 100 jobs. 100 t.Run("pushing 100 concurrently receive 100", func(t *testing.T) { 101 var called chunks.LocatorList 102 lock := &sync.Mutex{} 103 var finishAll sync.WaitGroup 104 finishAll.Add(100) 105 alwaysFinish := func(notifier module.ProcessingNotifier, locator *chunks.Locator) { 106 lock.Lock() 107 defer lock.Unlock() 108 called = append(called, locator) 109 go func() { 110 notifier.Notify(locator.ID()) 111 finishAll.Done() 112 }() 113 } 114 WithConsumer(t, alwaysFinish, func(consumer *chunkconsumer.ChunkConsumer, chunksQueue *storage.ChunksQueue) { 115 <-consumer.Ready() 116 total := atomic.NewUint32(0) 117 118 locators := unittest.ChunkLocatorListFixture(100) 119 120 for i := 0; i < len(locators); i++ { 121 go func(i int) { 122 ok, err := chunksQueue.StoreChunkLocator(locators[i]) 123 require.NoError(t, err, fmt.Sprintf("chunk locator %v can't be stored", i)) 124 require.True(t, ok) 125 total.Inc() 126 consumer.Check() // notify the consumer 127 }(i) 128 } 129 130 finishAll.Wait() 131 <-consumer.Done() 132 133 // expect the mock engine receives all 100 calls 134 require.Equal(t, uint32(100), total.Load()) 135 }) 136 }) 137 } 138 139 func WithConsumer( 140 t *testing.T, 141 process func(module.ProcessingNotifier, *chunks.Locator), 142 withConsumer func(*chunkconsumer.ChunkConsumer, *storage.ChunksQueue), 143 ) { 144 unittest.RunWithBadgerDB(t, func(db *badger.DB) { 145 maxProcessing := uint64(3) 146 147 processedIndex := storage.NewConsumerProgress(db, module.ConsumeProgressVerificationChunkIndex) 148 chunksQueue := storage.NewChunkQueue(db) 149 ok, err := chunksQueue.Init(chunkconsumer.DefaultJobIndex) 150 require.NoError(t, err) 151 require.True(t, ok) 152 153 engine := &mockChunkProcessor{ 154 process: process, 155 } 156 157 collector := &metrics.NoopCollector{} 158 consumer, err := chunkconsumer.NewChunkConsumer( 159 unittest.Logger(), 160 collector, 161 processedIndex, 162 chunksQueue, 163 engine, 164 maxProcessing, 165 ) 166 require.NoError(t, err) 167 168 withConsumer(consumer, chunksQueue) 169 }) 170 } 171 172 // mockChunkProcessor provides an AssignedChunkProcessor with a plug-and-play process method. 173 type mockChunkProcessor struct { 174 notifier module.ProcessingNotifier 175 process func(notifier module.ProcessingNotifier, locator *chunks.Locator) 176 } 177 178 func (e *mockChunkProcessor) Ready() <-chan struct{} { 179 ready := make(chan struct{}) 180 close(ready) 181 return ready 182 } 183 184 func (e *mockChunkProcessor) Done() <-chan struct{} { 185 done := make(chan struct{}) 186 close(done) 187 return done 188 } 189 190 func (e *mockChunkProcessor) ProcessAssignedChunk(locator *chunks.Locator) { 191 e.process(e.notifier, locator) 192 } 193 194 func (e *mockChunkProcessor) WithChunkConsumerNotifier(notifier module.ProcessingNotifier) { 195 e.notifier = notifier 196 }