github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/state_synchronization/requester/jobs/execution_data_reader_test.go (about) 1 package jobs 2 3 import ( 4 "context" 5 "errors" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/mock" 11 "github.com/stretchr/testify/require" 12 "github.com/stretchr/testify/suite" 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 exedatamock "github.com/onflow/flow-go/module/executiondatasync/execution_data/mock" 18 "github.com/onflow/flow-go/module/irrecoverable" 19 "github.com/onflow/flow-go/module/mempool/herocache" 20 "github.com/onflow/flow-go/module/metrics" 21 synctest "github.com/onflow/flow-go/module/state_synchronization/requester/unittest" 22 "github.com/onflow/flow-go/storage" 23 storagemock "github.com/onflow/flow-go/storage/mock" 24 "github.com/onflow/flow-go/utils/unittest" 25 ) 26 27 type ExecutionDataReaderSuite struct { 28 suite.Suite 29 30 reader *ExecutionDataReader 31 downloader *exedatamock.Downloader 32 headers *storagemock.Headers 33 results *storagemock.ExecutionResults 34 seals *storagemock.Seals 35 fetchTimeout time.Duration 36 37 executionDataID flow.Identifier 38 executionData *execution_data.BlockExecutionData 39 block *flow.Block 40 blocksByHeight map[uint64]*flow.Block 41 42 highestAvailableHeight func() uint64 43 } 44 45 func TestExecutionDataReaderSuite(t *testing.T) { 46 t.Parallel() 47 suite.Run(t, new(ExecutionDataReaderSuite)) 48 } 49 50 func (suite *ExecutionDataReaderSuite) SetupTest() { 51 suite.fetchTimeout = time.Second 52 suite.executionDataID = unittest.IdentifierFixture() 53 54 parent := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(1)) 55 suite.block = unittest.BlockWithParentFixture(parent) 56 suite.blocksByHeight = map[uint64]*flow.Block{ 57 suite.block.Header.Height: suite.block, 58 } 59 60 suite.executionData = unittest.BlockExecutionDataFixture(unittest.WithBlockExecutionDataBlockID(suite.block.ID())) 61 62 suite.highestAvailableHeight = func() uint64 { return suite.block.Header.Height + 1 } 63 64 suite.reset() 65 } 66 67 func (suite *ExecutionDataReaderSuite) reset() { 68 result := unittest.ExecutionResultFixture( 69 unittest.WithBlock(suite.block), 70 unittest.WithExecutionDataID(suite.executionDataID), 71 ) 72 73 seal := unittest.Seal.Fixture( 74 unittest.Seal.WithBlockID(suite.block.ID()), 75 unittest.Seal.WithResult(result), 76 ) 77 78 suite.headers = synctest.MockBlockHeaderStorage( 79 synctest.WithByHeight(suite.blocksByHeight), 80 synctest.WithBlockIDByHeight(suite.blocksByHeight), 81 ) 82 suite.results = synctest.MockResultsStorage( 83 synctest.WithResultByID(map[flow.Identifier]*flow.ExecutionResult{ 84 result.ID(): result, 85 }), 86 ) 87 suite.seals = synctest.MockSealsStorage( 88 synctest.WithSealsByBlockID(map[flow.Identifier]*flow.Seal{ 89 suite.block.ID(): seal, 90 }), 91 ) 92 93 suite.downloader = new(exedatamock.Downloader) 94 var executionDataCacheSize uint32 = 100 // Use local value to avoid cycle dependency on subscription package 95 96 heroCache := herocache.NewBlockExecutionData(executionDataCacheSize, unittest.Logger(), metrics.NewNoopCollector()) 97 cache := cache.NewExecutionDataCache(suite.downloader, suite.headers, suite.seals, suite.results, heroCache) 98 99 suite.reader = NewExecutionDataReader( 100 cache, 101 suite.fetchTimeout, 102 func() (uint64, error) { 103 return suite.highestAvailableHeight(), nil 104 }, 105 ) 106 } 107 108 func (suite *ExecutionDataReaderSuite) TestAtIndex() { 109 setExecutionDataGet := func(executionData *execution_data.BlockExecutionData, err error) { 110 suite.downloader.On("Get", mock.Anything, suite.executionDataID).Return( 111 func(ctx context.Context, id flow.Identifier) *execution_data.BlockExecutionData { 112 return executionData 113 }, 114 func(ctx context.Context, id flow.Identifier) error { 115 return err 116 }, 117 ) 118 } 119 120 suite.Run("returns not found when not initialized", func() { 121 // runTest not called, so context is never added 122 job, err := suite.reader.AtIndex(1) 123 assert.Nil(suite.T(), job, "job should be nil") 124 assert.Error(suite.T(), err, "error should be returned") 125 }) 126 127 suite.Run("returns not found when index out of range", func() { 128 suite.reset() 129 suite.runTest(func() { 130 job, err := suite.reader.AtIndex(suite.highestAvailableHeight() + 1) 131 assert.Nil(suite.T(), job, "job should be nil") 132 assert.Equal(suite.T(), storage.ErrNotFound, err, "expected not found error") 133 }) 134 }) 135 136 suite.Run("returns successfully", func() { 137 suite.reset() 138 suite.runTest(func() { 139 ed := unittest.BlockExecutionDataFixture() 140 setExecutionDataGet(ed, nil) 141 142 edEntity := execution_data.NewBlockExecutionDataEntity(suite.executionDataID, ed) 143 144 job, err := suite.reader.AtIndex(suite.block.Header.Height) 145 require.NoError(suite.T(), err) 146 147 entry, err := JobToBlockEntry(job) 148 assert.NoError(suite.T(), err) 149 150 assert.Equal(suite.T(), edEntity, entry.ExecutionData) 151 }) 152 }) 153 154 suite.Run("returns error from ExecutionDataService Get", func() { 155 suite.reset() 156 suite.runTest(func() { 157 // return an error while getting the execution data 158 expectedErr := errors.New("expected error: get failed") 159 setExecutionDataGet(nil, expectedErr) 160 161 job, err := suite.reader.AtIndex(suite.block.Header.Height) 162 assert.Nil(suite.T(), job, "job should be nil") 163 assert.ErrorIs(suite.T(), err, expectedErr) 164 }) 165 }) 166 167 suite.Run("returns error getting header", func() { 168 suite.reset() 169 suite.runTest(func() { 170 // search for an index that doesn't have a header in storage 171 job, err := suite.reader.AtIndex(suite.block.Header.Height + 1) 172 assert.Nil(suite.T(), job, "job should be nil") 173 assert.ErrorIs(suite.T(), err, storage.ErrNotFound) 174 }) 175 }) 176 177 suite.Run("returns error getting execution result", func() { 178 suite.reset() 179 suite.runTest(func() { 180 // add a new block without an execution result 181 newBlock := unittest.BlockWithParentFixture(suite.block.Header) 182 suite.blocksByHeight[newBlock.Header.Height] = newBlock 183 184 job, err := suite.reader.AtIndex(newBlock.Header.Height) 185 assert.Nil(suite.T(), job, "job should be nil") 186 assert.ErrorIs(suite.T(), err, storage.ErrNotFound) 187 }) 188 }) 189 } 190 191 func (suite *ExecutionDataReaderSuite) TestHead() { 192 suite.runTest(func() { 193 expectedIndex := uint64(15) 194 suite.highestAvailableHeight = func() uint64 { 195 return expectedIndex 196 } 197 index, err := suite.reader.Head() 198 assert.NoError(suite.T(), err) 199 assert.Equal(suite.T(), expectedIndex, index) 200 }) 201 } 202 203 func (suite *ExecutionDataReaderSuite) runTest(fn func()) { 204 ctx, cancel := context.WithCancel(context.Background()) 205 defer cancel() 206 207 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 208 209 suite.reader.AddContext(signalerCtx) 210 211 fn() 212 }