github.com/koko1123/flow-go-1@v0.29.6/module/state_synchronization/requester/jobs/execution_data_reader_test.go (about)

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