Skip to content

Commit

Permalink
Refactored test interface to enable parallelization
Browse files Browse the repository at this point in the history
  • Loading branch information
silaslenihan committed Dec 11, 2024
1 parent b2ea802 commit f12df7a
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 162 deletions.
23 changes: 12 additions & 11 deletions core/services/relay/evm/chain_components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,32 +209,29 @@ func TestContractReaderEventsInitValidation(t *testing.T) {
func TestChainComponents(t *testing.T) {
// add new subtests here so that it can be run on real chains too
t.Run("RunChainComponentsEvmTests", func(t *testing.T) {
t.Parallel()
helper := &helper{}
// shared helper for separate parallel testers
it := &EVMChainComponentsInterfaceTester[*testing.T]{Helper: helper}
it.Init(t)
RunChainComponentsEvmTests(t, it)
})

t.Run("RunChainComponentsInLoopEvmTests", func(t *testing.T) {
t.Parallel()
helper := &helper{}
// shared helper for separate parallel testers
it := &EVMChainComponentsInterfaceTester[*testing.T]{Helper: helper}
it.Init(t)
RunChainComponentsInLoopEvmTests[*testing.T](t, commontestutils.WrapContractReaderTesterForLoop(it))
wrapped := commontestutils.WrapContractReaderTesterForLoop(it)
wrapped.Init(t)
fmt.Printf("Type of tester: %T", wrapped)
RunChainComponentsInLoopEvmTests[*testing.T](t, wrapped)
})

t.Run("RunChainComponentsInLoopEvmTestsWithBindings", func(t *testing.T) {
t.Parallel()
helper := &helper{}
// shared helper for separate parallel testers
helper.Init(t)
it := &EVMChainComponentsInterfaceTester[*testing.T]{Helper: helper}
wrapped := WrapContractReaderTesterWithBindings(t, it)
wrapped.Init(t)
// TODO, generated binding tests are broken
it.DisableTests([]string{interfacetests.ContractReaderGetLatestValue})
RunChainComponentsInLoopEvmTests(t, WrapContractReaderTesterWithBindings(t, it))
wrapped.DisableTests([]string{interfacetests.ContractReaderGetLatestValue})
RunChainComponentsInLoopEvmTests(t, wrapped)
})
}

Expand Down Expand Up @@ -317,6 +314,10 @@ func (h *helper) ChainID() *big.Int {
return testutils.SimulatedChainID
}

func (h *helper) Database() *sqlx.DB {
return h.db
}

func (h *helper) NewSqlxDB(t *testing.T) *sqlx.DB {
return pgtest.NewSqlxDB(t)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func newBindingsMapping() bindingsMapping {
}

func getChainReaderConfig(wrapped *EVMChainComponentsInterfaceTester[*testing.T]) types.ChainReaderConfig {
fmt.Println("wrapped config")
testStruct := interfacetests.CreateTestStruct[*testing.T](0, wrapped)
chainReaderConfig := bindings.NewChainReaderConfig()
chainReaderConfig.Contracts["ChainReaderTester"].Configs["ReturnSeen"] = &types.ChainReaderDefinition{
Expand Down
185 changes: 85 additions & 100 deletions core/services/relay/evm/evmtesting/chain_components_interface_tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package evmtesting
import (
"context"
"encoding/json"
"fmt"
"math/big"
"sync"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
Expand Down Expand Up @@ -48,6 +50,7 @@ type EVMChainComponentsInterfaceTesterHelper[T TestingT[T]] interface {
Client(t T) client.Client
Commit()
Backend() bind.ContractBackend
Database() *sqlx.DB
ChainID() *big.Int
Context(t T) context.Context
NewSqlxDB(t T) *sqlx.DB
Expand All @@ -64,61 +67,26 @@ type EVMChainComponentsInterfaceTester[T TestingT[T]] struct {
TestSelectionSupport
Helper EVMChainComponentsInterfaceTesterHelper[T]
client client.Client
address string
address2 string
contractTesters map[string]*chain_reader_tester.ChainReaderTester
chainReaderConfig types.ChainReaderConfig
chainWriterConfig types.ChainWriterConfig
deployerAuth *bind.TransactOpts
senderAuth *bind.TransactOpts
cr evm.ChainReaderService
cw evm.ChainWriterService
dirtyContracts bool
txm evmtxmgr.TxManager
gasEstimator gas.EvmFeeEstimator
chainReaderConfigSupplier func(t T) types.ChainReaderConfig
chainWriterConfigSupplier func(t T) types.ChainWriterConfig
dirtyConfig bool
nonceMu sync.Mutex
setupMu sync.Mutex
lp logpoller.LogPoller
ht logpoller.HeadTracker
}

func (it *EVMChainComponentsInterfaceTester[T]) Setup(t T) {
t.Cleanup(func() {
// DB may be closed by the test already, ignore errors
if it.cr != nil {
_ = it.cr.Close()
}
it.cr = nil

if it.dirtyContracts {
it.contractTesters = nil
}

if it.cw != nil {
_ = it.cw.Close()
}
it.cw = nil
})

// can re-use the same chain for tests, just make new contract for each test
if it.client != nil && !it.dirtyConfig {
it.deployNewContracts(t)
return
}

// Need to separate accounts to ensure the nonce doesn't get misaligned after the
// contract deployments.
accounts := it.Helper.Accounts(t)
it.deployerAuth = accounts[0]
it.senderAuth = accounts[1]

it.chainReaderConfig = it.chainReaderConfigSupplier(t)
it.GetContractReader(t)

it.txm = it.Helper.TXM(t, it.client)
it.chainWriterConfig = it.chainWriterConfigSupplier(t)
}

it.deployNewContracts(t)
it.dirtyConfig = false
func (it *EVMChainComponentsInterfaceTester[T]) GetBindings(t T) []clcommontypes.BoundContract {
return it.deployNewContracts(t)
}

func (it *EVMChainComponentsInterfaceTester[T]) getChainReaderConfig(t T) types.ChainReaderConfig {
Expand Down Expand Up @@ -263,6 +231,7 @@ func (it *EVMChainComponentsInterfaceTester[T]) getChainReaderConfig(t T) types.
}

func (it *EVMChainComponentsInterfaceTester[T]) getChainWriterConfig(t T) types.ChainWriterConfig {
fmt.Println("unwrapped config")
return types.ChainWriterConfig{
Contracts: map[string]*types.ContractConfig{
AnyContractName: {
Expand Down Expand Up @@ -356,73 +325,52 @@ func (it *EVMChainComponentsInterfaceTester[T]) GetAccountString(i int) string {
}

func (it *EVMChainComponentsInterfaceTester[T]) GetContractReader(t T) clcommontypes.ContractReader {
it.setupMu.Lock()
defer it.setupMu.Unlock()
ctx := it.Helper.Context(t)
if it.cr != nil {
return it.cr
}

lggr := logger.NullLogger
db := it.Helper.NewSqlxDB(t)
lpOpts := logpoller.Opts{
PollPeriod: time.Millisecond,
FinalityDepth: finalityDepth,
BackfillBatchSize: 1,
RpcBatchSize: 1,
KeepFinalizedBlocksDepth: 10000,
}
ht := headtracker.NewSimulatedHeadTracker(it.Helper.Client(t), lpOpts.UseFinalityTag, lpOpts.FinalityDepth)
lp := logpoller.NewLogPoller(logpoller.NewORM(it.Helper.ChainID(), db, lggr), it.Helper.Client(t), lggr, ht, lpOpts)
require.NoError(t, lp.Start(ctx))

// encode and decode the config to ensure the test covers type issues
confBytes, err := json.Marshal(it.chainReaderConfig)
require.NoError(t, err)

conf, err := types.ChainReaderConfigFromBytes(confBytes)
cr, err := evm.NewChainReaderService(ctx, lggr, it.lp, it.ht, it.client, it.chainReaderConfig)
require.NoError(t, err)
require.NoError(t, cr.Start(ctx))

cwh := it.Helper.ChainReaderEVMClient(ctx, t, ht, conf)
it.client = cwh
t.Cleanup(func() {
cr.Close()
})

cr, err := evm.NewChainReaderService(ctx, lggr, lp, ht, it.client, conf)
require.NoError(t, err)
require.NoError(t, cr.Start(ctx))
it.cr = cr
return cr
}

// This function is no longer necessary for Simulated Backend or Testnet tests.
func (it *EVMChainComponentsInterfaceTester[T]) GenerateBlocksTillConfidenceLevel(t T, contractName, readName string, confidenceLevel primitives.ConfidenceLevel) {
}

func (it *EVMChainComponentsInterfaceTester[T]) GetContractWriter(t T) clcommontypes.ContractWriter {
it.setupMu.Lock()
defer it.setupMu.Unlock()

ctx := it.Helper.Context(t)
if it.cw != nil {
return it.cw
}

cw, err := evm.NewChainWriterService(logger.NullLogger, it.client, it.txm, it.gasEstimator, it.chainWriterConfig)
require.NoError(t, err)
it.cw = it.Helper.WrappedChainWriter(cw, it.client)

cw = it.Helper.WrappedChainWriter(cw, it.client)

require.NoError(t, err)
require.NoError(t, cw.Start(ctx))
return it.cw

t.Cleanup(func() {
cw.Close()
})

return cw
}

func (it *EVMChainComponentsInterfaceTester[T]) GetBindings(_ T) []clcommontypes.BoundContract {
return []clcommontypes.BoundContract{
{Name: AnyContractName, Address: it.address},
{Name: AnySecondContractName, Address: it.address2},
}
// This function is no longer necessary for Simulated Backend or Testnet tests.
func (it *EVMChainComponentsInterfaceTester[T]) GenerateBlocksTillConfidenceLevel(t T, contractName, readName string, confidenceLevel primitives.ConfidenceLevel) {
}

func (it *EVMChainComponentsInterfaceTester[T]) DirtyContracts() {
it.dirtyContracts = true
}

func (it *EVMChainComponentsInterfaceTester[T]) GetAuthWithGasSet(t T) *bind.TransactOpts {
gasPrice, err := it.client.SuggestGasPrice(it.Helper.Context(t))
gasPrice, err := it.Helper.Client(t).SuggestGasPrice(it.Helper.Context(t))
require.NoError(t, err)
extra := new(big.Int).Mul(gasPrice, big.NewInt(it.Helper.GasPriceBufferPercent()))
extra = extra.Div(extra, big.NewInt(100))
Expand All @@ -440,35 +388,33 @@ func (it *EVMChainComponentsInterfaceTester[T]) IncNonce() {

func (it *EVMChainComponentsInterfaceTester[T]) AwaitTx(t T, tx *gethtypes.Transaction) {
ctx := it.Helper.Context(t)
receipt, err := bind.WaitMined(ctx, it.client, tx)
receipt, err := bind.WaitMined(ctx, it.Helper.Client(t), tx)
require.NoError(t, err)
require.Equal(t, gethtypes.ReceiptStatusSuccessful, receipt.Status)
}

func (it *EVMChainComponentsInterfaceTester[T]) deployNewContracts(t T) {
// First test deploy both contracts, otherwise only deploy contracts if cleanup decides that we need to.
if it.address == "" || it.contractTesters == nil {
it.contractTesters = make(map[string]*chain_reader_tester.ChainReaderTester, 2)
address, ts1 := it.deployNewContract(t)
address2, ts2 := it.deployNewContract(t)
it.address, it.address2 = address, address2
it.contractTesters[it.address] = ts1
it.contractTesters[it.address2] = ts2
it.dirtyContracts = false
func (it *EVMChainComponentsInterfaceTester[T]) deployNewContracts(t T) []clcommontypes.BoundContract {
it.nonceMu.Lock()
defer it.nonceMu.Unlock()
address := it.deployNewContract(t)
address2 := it.deployNewContract(t)
return []clcommontypes.BoundContract{
{Name: AnyContractName, Address: address},
{Name: AnySecondContractName, Address: address2},
}
}

func (it *EVMChainComponentsInterfaceTester[T]) deployNewContract(t T) (string, *chain_reader_tester.ChainReaderTester) {
func (it *EVMChainComponentsInterfaceTester[T]) deployNewContract(t T) string {
// 105528 was in the error: gas too low: have 0, want 105528
// Not sure if there's a better way to get it.
it.deployerAuth.GasLimit = 10552800

address, tx, ts, err := chain_reader_tester.DeployChainReaderTester(it.GetAuthWithGasSet(t), it.Helper.Backend())
address, tx, _, err := chain_reader_tester.DeployChainReaderTester(it.GetAuthWithGasSet(t), it.Helper.Backend())
require.NoError(t, err)

it.IncNonce()
it.AwaitTx(t, tx)
return address.String(), ts
return address.String()
}

func (it *EVMChainComponentsInterfaceTester[T]) MaxWaitTimeForEvents() time.Duration {
Expand All @@ -477,8 +423,47 @@ func (it *EVMChainComponentsInterfaceTester[T]) MaxWaitTimeForEvents() time.Dura

func (it *EVMChainComponentsInterfaceTester[T]) Init(t T) {
it.Helper.Init(t)
it.chainWriterConfigSupplier = func(t T) types.ChainWriterConfig { return it.getChainWriterConfig(t) }
it.chainReaderConfigSupplier = func(t T) types.ChainReaderConfig { return it.getChainReaderConfig(t) }
if it.chainReaderConfigSupplier == nil {
it.chainReaderConfigSupplier = func(t T) types.ChainReaderConfig { return it.getChainReaderConfig(t) }
}
if it.chainWriterConfigSupplier == nil {
it.chainWriterConfigSupplier = func(t T) types.ChainWriterConfig { return it.getChainWriterConfig(t) }
}
// Need to separate accounts to ensure the nonce doesn't get misaligned after the
// contract deployments.
accounts := it.Helper.Accounts(t)
ctx := it.Helper.Context(t)
it.deployerAuth = accounts[0]
it.senderAuth = accounts[1]

lggr := logger.NullLogger
db := it.Helper.Database()
lpOpts := logpoller.Opts{
PollPeriod: time.Millisecond,
FinalityDepth: finalityDepth,
BackfillBatchSize: 1,
RpcBatchSize: 1,
KeepFinalizedBlocksDepth: 10000,
}

it.ht = headtracker.NewSimulatedHeadTracker(it.Helper.Client(t), lpOpts.UseFinalityTag, lpOpts.FinalityDepth)
it.lp = logpoller.NewLogPoller(logpoller.NewORM(it.Helper.ChainID(), db, lggr), it.Helper.Client(t), lggr, it.ht, lpOpts)
require.NoError(t, it.lp.Start(ctx))

it.chainReaderConfig = it.chainReaderConfigSupplier(t)
it.chainWriterConfig = it.chainWriterConfigSupplier(t)

// encode and decode the config to ensure the test covers type issues
confBytes, err := json.Marshal(it.chainReaderConfig)
require.NoError(t, err)

conf, err := types.ChainReaderConfigFromBytes(confBytes)
require.NoError(t, err)

it.client = it.Helper.ChainReaderEVMClient(ctx, t, it.ht, conf)
it.txm = it.Helper.TXM(t, it.client)

it.dirtyConfig = false
}

func (it *EVMChainComponentsInterfaceTester[T]) SetChainReaderConfigSupplier(chainReaderConfigSupplier func(t T) types.ChainReaderConfig) {
Expand Down
Loading

0 comments on commit f12df7a

Please sign in to comment.