github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/state_synchronization/indexer/indexer_test.go (about) 1 package indexer 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 mocks "github.com/stretchr/testify/mock" 11 "github.com/stretchr/testify/require" 12 "go.uber.org/atomic" 13 14 "github.com/onflow/flow-go/model/flow" 15 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 16 "github.com/onflow/flow-go/module/executiondatasync/execution_data/cache" 17 "github.com/onflow/flow-go/module/executiondatasync/execution_data/mock" 18 "github.com/onflow/flow-go/module/irrecoverable" 19 mempool "github.com/onflow/flow-go/module/mempool/mock" 20 storagemock "github.com/onflow/flow-go/storage/mock" 21 "github.com/onflow/flow-go/utils/unittest" 22 ) 23 24 const testTimeout = 300 * time.Millisecond 25 26 type indexerTest struct { 27 blocks []*flow.Block 28 progress *mockProgress 29 registers *storagemock.RegisterIndex 30 indexTest *indexCoreTest 31 worker *Indexer 32 executionData *mempool.ExecutionData 33 t *testing.T 34 } 35 36 // newIndexerTest set up a jobqueue integration test with the worker. 37 // It will create blocks fixtures with the length provided as availableBlocks, and it will set heights already 38 // indexed to lastIndexedIndex value. Using run it should index all the remaining blocks up to all available blocks. 39 func newIndexerTest(t *testing.T, availableBlocks int, lastIndexedIndex int) *indexerTest { 40 blocks := unittest.BlockchainFixture(availableBlocks) 41 // we use 5th index as the latest indexed height, so we leave 5 more blocks to be indexed by the indexer in this test 42 lastIndexedHeight := blocks[lastIndexedIndex].Header.Height 43 progress := newMockProgress() 44 err := progress.SetProcessedIndex(lastIndexedHeight) 45 require.NoError(t, err) 46 47 registers := storagemock.NewRegisterIndex(t) 48 49 indexerCoreTest := newIndexCoreTest(t, blocks, nil). 50 setLastHeight(func(t *testing.T) uint64 { 51 i, err := progress.ProcessedIndex() 52 require.NoError(t, err) 53 54 return i 55 }). 56 useDefaultBlockByHeight(). 57 useDefaultEvents(). 58 useDefaultTransactionResults(). 59 initIndexer() 60 61 executionData := mempool.NewExecutionData(t) 62 exeCache := cache.NewExecutionDataCache( 63 mock.NewExecutionDataStore(t), 64 indexerCoreTest.indexer.headers, 65 nil, 66 nil, 67 executionData, 68 ) 69 70 test := &indexerTest{ 71 t: t, 72 blocks: blocks, 73 progress: progress, 74 indexTest: indexerCoreTest, 75 executionData: executionData, 76 } 77 78 test.worker, err = NewIndexer( 79 unittest.Logger(), 80 test.first().Header.Height, 81 registers, 82 indexerCoreTest.indexer, 83 exeCache, 84 test.latestHeight, 85 progress, 86 ) 87 require.NoError(t, err) 88 89 return test 90 } 91 92 func (w *indexerTest) setBlockDataByID(f func(ID flow.Identifier) (*execution_data.BlockExecutionDataEntity, bool)) { 93 w.executionData. 94 On("ByID", mocks.AnythingOfType("flow.Identifier")). 95 Return(f) 96 } 97 98 func (w *indexerTest) latestHeight() (uint64, error) { 99 return w.last().Header.Height, nil 100 } 101 102 func (w *indexerTest) last() *flow.Block { 103 return w.blocks[len(w.blocks)-1] 104 } 105 106 func (w *indexerTest) first() *flow.Block { 107 return w.blocks[0] 108 } 109 110 func (w *indexerTest) run(ctx irrecoverable.SignalerContext, reachHeight uint64, cancel context.CancelFunc) { 111 w.worker.Start(ctx) 112 113 unittest.RequireComponentsReadyBefore(w.t, testTimeout, w.worker) 114 115 w.worker.OnExecutionData(nil) 116 117 // wait for end to be reached 118 <-w.progress.WaitForIndex(reachHeight) 119 cancel() 120 121 unittest.RequireCloseBefore(w.t, w.worker.Done(), testTimeout, "timeout waiting for the consumer to be done") 122 } 123 124 type mockProgress struct { 125 index *atomic.Uint64 126 doneIndex *atomic.Uint64 127 // signal to mark the progress reached an index set with WaitForIndex 128 doneChan chan struct{} 129 } 130 131 func newMockProgress() *mockProgress { 132 return &mockProgress{ 133 index: atomic.NewUint64(0), 134 doneIndex: atomic.NewUint64(0), 135 doneChan: make(chan struct{}), 136 } 137 } 138 139 func (w *mockProgress) ProcessedIndex() (uint64, error) { 140 return w.index.Load(), nil 141 } 142 143 func (w *mockProgress) SetProcessedIndex(index uint64) error { 144 w.index.Store(index) 145 146 if index > 0 && index == w.doneIndex.Load() { 147 close(w.doneChan) 148 } 149 150 return nil 151 } 152 153 func (w *mockProgress) InitProcessedIndex(index uint64) error { 154 w.index.Store(index) 155 return nil 156 } 157 158 // WaitForIndex will trigger a signal to the consumer, so they know the test reached a certain point 159 func (w *mockProgress) WaitForIndex(n uint64) <-chan struct{} { 160 w.doneIndex.Store(n) 161 return w.doneChan 162 } 163 164 func TestIndexer_Success(t *testing.T) { 165 // we use 5th index as the latest indexed height, so we leave 5 more blocks to be indexed by the indexer in this test 166 blocks := 10 167 lastIndexedIndex := 5 168 test := newIndexerTest(t, blocks, lastIndexedIndex) 169 170 test.setBlockDataByID(func(ID flow.Identifier) (*execution_data.BlockExecutionDataEntity, bool) { 171 trie := trieUpdateFixture(t) 172 collection := unittest.CollectionFixture(0) 173 ed := &execution_data.BlockExecutionData{ 174 BlockID: ID, 175 ChunkExecutionDatas: []*execution_data.ChunkExecutionData{{ 176 Collection: &collection, 177 TrieUpdate: trie, 178 }}, 179 } 180 181 // create this to capture the closure of the creation of block execution data, so we can for each returned 182 // block execution data make sure the store of registers will match what the execution data returned and 183 // also that the height was correct 184 test.indexTest.setStoreRegisters(func(t *testing.T, entries flow.RegisterEntries, height uint64) error { 185 var blockHeight uint64 186 for _, b := range test.blocks { 187 if b.ID() == ID { 188 blockHeight = b.Header.Height 189 } 190 } 191 192 assert.Equal(t, blockHeight, height) 193 trieRegistersPayloadComparer(t, trie.Payloads, entries) 194 return nil 195 }) 196 197 return execution_data.NewBlockExecutionDataEntity(ID, ed), true 198 }) 199 200 signalerCtx, cancel := irrecoverable.NewMockSignalerContextWithCancel(t, context.Background()) 201 lastHeight := test.blocks[len(test.blocks)-1].Header.Height 202 test.run(signalerCtx, lastHeight, cancel) 203 204 // make sure store was called correct number of times 205 test.indexTest.registers.AssertNumberOfCalls(t, "Store", blocks-lastIndexedIndex-1) 206 } 207 208 func TestIndexer_Failure(t *testing.T) { 209 // we use 5th index as the latest indexed height, so we leave 5 more blocks to be indexed by the indexer in this test 210 blocks := 10 211 lastIndexedIndex := 5 212 test := newIndexerTest(t, blocks, lastIndexedIndex) 213 214 test.setBlockDataByID(func(ID flow.Identifier) (*execution_data.BlockExecutionDataEntity, bool) { 215 trie := trieUpdateFixture(t) 216 collection := unittest.CollectionFixture(0) 217 ed := &execution_data.BlockExecutionData{ 218 BlockID: ID, 219 ChunkExecutionDatas: []*execution_data.ChunkExecutionData{{ 220 Collection: &collection, 221 TrieUpdate: trie, 222 }}, 223 } 224 225 // fail when trying to persist registers 226 test.indexTest.setStoreRegisters(func(t *testing.T, entries flow.RegisterEntries, height uint64) error { 227 return fmt.Errorf("error persisting data") 228 }) 229 230 return execution_data.NewBlockExecutionDataEntity(ID, ed), true 231 }) 232 233 // make sure the error returned is as expected 234 expectedErr := fmt.Errorf( 235 "failed to index block data at height %d: %w", 236 test.blocks[lastIndexedIndex].Header.Height+1, 237 fmt.Errorf( 238 "could not index register payloads at height %d: %w", test.blocks[lastIndexedIndex].Header.Height+1, fmt.Errorf("error persisting data")), 239 ) 240 241 _, cancel := context.WithCancel(context.Background()) 242 signalerCtx := irrecoverable.NewMockSignalerContextExpectError(t, context.Background(), expectedErr) 243 lastHeight := test.blocks[lastIndexedIndex].Header.Height 244 test.run(signalerCtx, lastHeight, cancel) 245 246 // make sure store was called correct number of times 247 test.indexTest.registers.AssertNumberOfCalls(t, "Store", 1) // it fails after first run 248 }