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  }