Skip to content

Commit

Permalink
Generalize precompile test suite (#759)
Browse files Browse the repository at this point in the history
* restore old functions, generalize paths in sync subnet creation

* add subnet suit struct and methods

* move constants

* fix reviews

* add comment

* Update subnet.go

Signed-off-by: Ceyhun Onur <ceyhun.onur@avalabs.org>

---------

Signed-off-by: Ceyhun Onur <ceyhun.onur@avalabs.org>
  • Loading branch information
ceyonur committed Jul 31, 2023
1 parent dc9cc5c commit 402510e
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 147 deletions.
8 changes: 2 additions & 6 deletions tests/precompile/precompile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,14 @@ import (
"github.com/onsi/gomega"

// Import the solidity package, so that ginkgo maps out the tests declared within the package
_ "github.com/ava-labs/subnet-evm/tests/precompile/solidity"
"github.com/ava-labs/subnet-evm/tests/utils"
"github.com/ava-labs/subnet-evm/tests/precompile/solidity"
)

func init() {
utils.RegisterNodeRun()
}

func TestE2E(t *testing.T) {
if basePath := os.Getenv("TEST_SOURCE_ROOT"); basePath != "" {
os.Chdir(basePath)
}
gomega.RegisterFailHandler(ginkgo.Fail)
solidity.RegisterAsyncTests()
ginkgo.RunSpecs(t, "subnet-evm precompile ginkgo test suite")
}
121 changes: 77 additions & 44 deletions tests/precompile/solidity/suites.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,98 @@ package solidity

import (
"context"
"fmt"
"time"

"github.com/ava-labs/subnet-evm/tests/utils"
ginkgo "github.com/onsi/ginkgo/v2"
)

var _ = ginkgo.Describe("[Precompiles]", func() {
// Register the ping test first
utils.RegisterPingTest()

// Each ginkgo It node specifies the name of the genesis file (in ./tests/precompile/genesis/)
// to use to launch the subnet and the name of the TS test file to run on the subnet (in ./contracts/tests/)
ginkgo.It("contract native minter", ginkgo.Label("Precompile"), ginkgo.Label("ContractNativeMinter"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["contract_native_minter"], "contract_native_minter")
})

ginkgo.It("tx allow list", ginkgo.Label("Precompile"), ginkgo.Label("TxAllowList"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// Registers the Asynchronized Precompile Tests
// Before running the tests, this function creates all subnets given in the genesis files
// and then runs the hardhat tests for each one asynchronously if called with `ginkgo run -procs=`.
func RegisterAsyncTests() {
// Tests here assumes that the genesis files are in ./tests/precompile/genesis/
// with the name {precompile_name}.json
genesisFiles, err := utils.GetFilesAndAliases("./tests/precompile/genesis/*.json")
if err != nil {
ginkgo.AbortSuite("Failed to get genesis files: " + err.Error())
}
if len(genesisFiles) == 0 {
ginkgo.AbortSuite("No genesis files found")
}
subnetsSuite := utils.CreateSubnetsSuite(genesisFiles)

var _ = ginkgo.Describe("[Asynchronized Precompile Tests]", func() {
// Register the ping test first
utils.RegisterPingTest()

// Each ginkgo It node specifies the name of the genesis file (in ./tests/precompile/genesis/)
// to use to launch the subnet and the name of the TS test file to run on the subnet (in ./contracts/tests/)
ginkgo.It("contract native minter", ginkgo.Label("Precompile"), ginkgo.Label("ContractNativeMinter"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["tx_allow_list"], "tx_allow_list")
})
blockchainID := subnetsSuite.GetBlockchainID("contract_native_minter")
runDefaultHardhatTests(ctx, blockchainID, "contract_native_minter")
})

ginkgo.It("contract deployer allow list", ginkgo.Label("Precompile"), ginkgo.Label("ContractDeployerAllowList"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ginkgo.It("tx allow list", ginkgo.Label("Precompile"), ginkgo.Label("TxAllowList"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["contract_deployer_allow_list"], "contract_deployer_allow_list")
})
blockchainID := subnetsSuite.GetBlockchainID("tx_allow_list")
runDefaultHardhatTests(ctx, blockchainID, "tx_allow_list")
})

ginkgo.It("fee manager", ginkgo.Label("Precompile"), ginkgo.Label("FeeManager"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ginkgo.It("contract deployer allow list", ginkgo.Label("Precompile"), ginkgo.Label("ContractDeployerAllowList"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["fee_manager"], "fee_manager")
})
blockchainID := subnetsSuite.GetBlockchainID("contract_deployer_allow_list")
runDefaultHardhatTests(ctx, blockchainID, "contract_deployer_allow_list")
})

ginkgo.It("reward manager", ginkgo.Label("Precompile"), ginkgo.Label("RewardManager"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ginkgo.It("fee manager", ginkgo.Label("Precompile"), ginkgo.Label("FeeManager"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["reward_manager"], "reward_manager")
})
blockchainID := subnetsSuite.GetBlockchainID("fee_manager")
runDefaultHardhatTests(ctx, blockchainID, "fee_manager")
})

// and then runs the hardhat tests for each one without forcing precompile developers to modify this file.
// ADD YOUR PRECOMPILE HERE
/*
ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() {
ginkgo.It("reward manager", ginkgo.Label("Precompile"), ginkgo.Label("RewardManager"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

// Specify the name shared by the genesis file in ./tests/precompile/genesis/{your_precompile}.json
// and the test file in ./contracts/tests/{your_precompile}.ts
// If you want to use a different test command and genesis path than the defaults, you can
// use the utils.RunTestCMD. See utils.RunDefaultHardhatTests for an example.
utils.RunDefaultHardhatTests(ctx, "your_precompile")
blockchainID := subnetsSuite.GetBlockchainID("reward_manager")
runDefaultHardhatTests(ctx, blockchainID, "reward_manager")
})
*/
})

// ADD YOUR PRECOMPILE HERE
/*
ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// Specify the name shared by the genesis file in ./tests/precompile/genesis/{your_precompile}.json
// and the test file in ./contracts/tests/{your_precompile}.ts
// If you want to use a different test command and genesis path than the defaults, you can
// use the utils.RunTestCMD. See utils.RunDefaultHardhatTests for an example.
subnetsSuite.RunHardhatTests(ctx, "your_precompile")
})
*/
})
}

// Default parameters are:
//
// 1. Hardhat contract environment is located at ./contracts
// 2. Hardhat test file is located at ./contracts/test/<test>.ts
// 3. npx is available in the ./contracts directory
func runDefaultHardhatTests(ctx context.Context, blockchainID, testName string) {
cmdPath := "./contracts"
// test path is relative to the cmd path
testPath := fmt.Sprintf("./test/%s.ts", testName)
utils.RunHardhatTests(ctx, blockchainID, cmdPath, testPath)
}
88 changes: 34 additions & 54 deletions tests/utils/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ package utils

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"os/exec"
"strings"
"time"

Expand All @@ -19,22 +18,6 @@ import (
"github.com/onsi/gomega"
)

const (
// Timeout to boot the AvalancheGo node
bootAvalancheNodeTimeout = 5 * time.Minute

// Timeout for the health API to check the AvalancheGo is ready
healthCheckTimeout = 5 * time.Second
)

// At boot time subnets are created, one for each test suite. This global
// variable has all the subnets IDs that can be used.
//
// One process creates the AvalancheGo node and all the subnets, and these
// subnets IDs are passed to all other processes and stored in this global
// variable
var BlockchainIDs map[string]string

// RunCommand starts the command [bin] with the given [args] and returns the command to the caller
// TODO cmd package mentions we can do this more efficiently with cmd.NewCmdOptions rather than looping
// and calling Status().
Expand Down Expand Up @@ -76,22 +59,13 @@ func RegisterPingTest() {
})
}

// RegisterNodeRun registers a before suite that starts an AvalancheGo process to use for the e2e tests
// and an after suite that stops the AvalancheGo process
func RegisterNodeRun() {
// Keep track of the AvalancheGo external bash script, it is null for most
// processes except the first process that starts AvalancheGo
// BeforeSuite starts an AvalancheGo process to use for the e2e tests
var startCmd *cmd.Cmd

// Our test suite runs in separate processes, ginkgo has
// SynchronizedBeforeSuite() which runs once, and its return value is passed
// over to each worker.
//
// Here an AvalancheGo node instance is started, and subnets are created for
// each test case. Each test case has its own subnet, therefore all tests
// can run in parallel without any issue.
//
// This function also compiles all the solidity contracts
var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
ctx, cancel := context.WithTimeout(context.Background(), bootAvalancheNodeTimeout)
_ = ginkgo.BeforeSuite(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

wd, err := os.Getwd()
Expand All @@ -103,34 +77,40 @@ func RegisterNodeRun() {

// Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI]
healthClient := health.NewClient(DefaultLocalNodeURI)
healthy, err := health.AwaitReady(ctx, healthClient, healthCheckTimeout, nil)
healthy, err := health.AwaitReady(ctx, healthClient, HealthCheckTimeout, nil)
gomega.Expect(err).Should(gomega.BeNil())
gomega.Expect(healthy).Should(gomega.BeTrue())
log.Info("AvalancheGo node is healthy")

blockchainIds := make(map[string]string)
files, err := filepath.Glob("./tests/precompile/genesis/*.json")
gomega.Expect(err).NotTo(gomega.HaveOccurred())

for _, file := range files {
basename := filepath.Base(file)
index := basename[:len(basename)-5]
blockchainIds[index] = CreateNewSubnet(ctx, file)
}

blockchainIDsBytes, err := json.Marshal(blockchainIds)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
return blockchainIDsBytes
}, func(data []byte) {
err := json.Unmarshal(data, &BlockchainIDs)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})

// SynchronizedAfterSuite() takes two functions, the first runs after each test suite is done and the second
// function is executed once when all the tests are done. This function is used
// to gracefully shutdown the AvalancheGo node.
var _ = ginkgo.SynchronizedAfterSuite(func() {}, func() {
ginkgo.AfterSuite(func() {
gomega.Expect(startCmd).ShouldNot(gomega.BeNil())
gomega.Expect(startCmd.Stop()).Should(gomega.BeNil())
// TODO add a new node to bootstrap off of the existing node and ensure it can bootstrap all subnets
// created during the test
})
}

// RunDefaultHardhatTests runs the hardhat tests in the given [testPath] on the blockchain with [blockchainID]
// [execPath] is the path where the test command is executed
func RunHardhatTests(ctx context.Context, blockchainID string, execPath string, testPath string) {
chainURI := GetDefaultChainURI(blockchainID)
log.Info(
"Executing HardHat tests on blockchain",
"blockchainID", blockchainID,
"testPath", testPath,
"ChainURI", chainURI,
)

cmd := exec.Command("npx", "hardhat", "test", testPath, "--network", "local")
cmd.Dir = execPath

log.Info("Sleeping to wait for test ping", "rpcURI", chainURI)
err := os.Setenv("RPC_URI", chainURI)
gomega.Expect(err).Should(gomega.BeNil())
log.Info("Running test command", "cmd", cmd.String())

out, err := cmd.CombinedOutput()
fmt.Printf("\nCombined output:\n\n%s\n", string(out))
gomega.Expect(err).Should(gomega.BeNil())
}
15 changes: 13 additions & 2 deletions tests/utils/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@

package utils

var (
import "time"

const (
// Timeout to boot the AvalancheGo node
BootAvalancheNodeTimeout = 5 * time.Minute

// Timeout for the health API to check the AvalancheGo is ready
HealthCheckTimeout = 5 * time.Second

DefaultLocalNodeURI = "http://127.0.0.1:9650"
NodeURIs = []string{DefaultLocalNodeURI, "http://127.0.0.1:9652", "http://127.0.0.1:9654", "http://127.0.0.1:9656", "http://127.0.0.1:9658"}
)

var (
NodeURIs = []string{DefaultLocalNodeURI, "http://127.0.0.1:9652", "http://127.0.0.1:9654", "http://127.0.0.1:9656", "http://127.0.0.1:9658"}
)
Loading

0 comments on commit 402510e

Please sign in to comment.