Skip to content

Commit e23b01c

Browse files
authored
Run ginkgo precompile tests in parallel (#712)
Use SynchronizedBeforeSuite to create the node and all the subnet that are needed to run tests. Pass the blockchainID to each work processor and run the tests in parallel to reduce runtime
1 parent 9c03a87 commit e23b01c

File tree

7 files changed

+76
-32
lines changed

7 files changed

+76
-32
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
- name: Use Node.js
6969
uses: actions/setup-node@v3
7070
with:
71-
node-version: "18.x"
71+
node-version: "18.15"
7272
- name: NPM Clean Install
7373
run: npm ci
7474
working-directory: ./contracts

contracts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
},
2929
"license": "BSD-3-Clause",
3030
"scripts": {
31-
"build": "rm -rf dist/ && tsc -b",
31+
"build": "rm -rf dist/ && tsc -b && npx hardhat compile --network local",
3232
"compile": "npx hardhat compile",
3333
"console": "npx hardhat console",
3434
"test": "npx hardhat test",
@@ -41,6 +41,6 @@
4141
},
4242
"engines": {
4343
"npm": ">6.0.0",
44-
"node": ">=18.16.0"
44+
"node": ">=18.15.0"
4545
}
4646
}

scripts/run_ginkgo.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ echo "building precompile.test"
2020
# to install the ginkgo binary (required for test build and run)
2121
go install -v github.com/onsi/ginkgo/v2/ginkgo@${GINKGO_VERSION}
2222

23-
ACK_GINKGO_RC=true ginkgo build ./tests/precompile ./tests/load
23+
TEST_SOURCE_ROOT=$(pwd)
24+
25+
ACK_GINKGO_RC=true ginkgo build ./tests/load
2426

2527
# By default, it runs all e2e test cases!
2628
# Use "--ginkgo.skip" to skip tests.
2729
# Use "--ginkgo.focus" to select tests.
28-
./tests/precompile/precompile.test \
30+
TEST_SOURCE_ROOT="$TEST_SOURCE_ROOT" ginkgo run -procs=5 tests/precompile \
2931
--ginkgo.vv \
3032
--ginkgo.label-filter=${GINKGO_LABEL_FILTER:-""}
3133

tests/precompile/precompile_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package precompile
55

66
import (
7+
"os"
78
"testing"
89

910
ginkgo "github.com/onsi/ginkgo/v2"
@@ -19,6 +20,9 @@ func init() {
1920
}
2021

2122
func TestE2E(t *testing.T) {
23+
if basePath := os.Getenv("TEST_SOURCE_ROOT"); basePath != "" {
24+
os.Chdir(basePath)
25+
}
2226
gomega.RegisterFailHandler(ginkgo.Fail)
2327
ginkgo.RunSpecs(t, "subnet-evm precompile ginkgo test suite")
2428
}

tests/precompile/solidity/suites.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
ginkgo "github.com/onsi/ginkgo/v2"
1313
)
1414

15-
var _ = ginkgo.Describe("[Precompiles]", ginkgo.Ordered, func() {
15+
var _ = ginkgo.Describe("[Precompiles]", func() {
1616
// Register the ping test first
1717
utils.RegisterPingTest()
1818

@@ -22,35 +22,35 @@ var _ = ginkgo.Describe("[Precompiles]", ginkgo.Ordered, func() {
2222
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
2323
defer cancel()
2424

25-
utils.RunDefaultHardhatTests(ctx, "contract_native_minter")
25+
utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["contract_native_minter"], "contract_native_minter")
2626
})
2727

2828
ginkgo.It("tx allow list", ginkgo.Label("Precompile"), ginkgo.Label("TxAllowList"), func() {
2929
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
3030
defer cancel()
3131

32-
utils.RunDefaultHardhatTests(ctx, "tx_allow_list")
32+
utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["tx_allow_list"], "tx_allow_list")
3333
})
3434

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

39-
utils.RunDefaultHardhatTests(ctx, "contract_deployer_allow_list")
39+
utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["contract_deployer_allow_list"], "contract_deployer_allow_list")
4040
})
4141

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

46-
utils.RunDefaultHardhatTests(ctx, "fee_manager")
46+
utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["fee_manager"], "fee_manager")
4747
})
4848

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

53-
utils.RunDefaultHardhatTests(ctx, "reward_manager")
53+
utils.RunDefaultHardhatTests(ctx, utils.BlockchainIDs["reward_manager"], "reward_manager")
5454
})
5555

5656
// and then runs the hardhat tests for each one without forcing precompile developers to modify this file.

tests/utils/command.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ package utils
55

66
import (
77
"context"
8+
"encoding/json"
89
"fmt"
910
"os"
11+
"path/filepath"
1012
"strings"
1113
"time"
1214

@@ -58,13 +60,36 @@ func RegisterPingTest() {
5860
})
5961
}
6062

61-
// RegisterNodeRun registers a before suite that starts an AvalancheGo process to use for the e2e tests
62-
// and an after suite that stops the AvalancheGo process
63+
// At boot time subnets are created, one for each test suite. This global
64+
// variable has all the subnets IDs that can be used.
65+
//
66+
// One process creates the AvalancheGo node and all the subnets, and these
67+
// subnets IDs are passed to all other processes and stored in this global
68+
// variable
69+
var BlockchainIDs map[string]string
70+
71+
// Timeout to boot the AvalancheGo node
72+
var bootAvalancheNodeTimeout = 5 * time.Minute
73+
74+
// Timeout for the health API to check the AvalancheGo is ready
75+
var healthCheckTimeout = 5 * time.Second
76+
6377
func RegisterNodeRun() {
64-
// BeforeSuite starts an AvalancheGo process to use for the e2e tests
78+
// Keep track of the AvalancheGo external bash script, it is null for most
79+
// processes except the first process that starts AvalancheGo
6580
var startCmd *cmd.Cmd
66-
_ = ginkgo.BeforeSuite(func() {
67-
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
81+
82+
// Our test suite runs in a separated processes, ginkgo hasI
83+
// SynchronizedBeforeSuite() which is promised to run once, and its output is
84+
// passed over to each worker.
85+
//
86+
// In here an AvalancheGo node instance is started, and subnets are created for
87+
// each test case. Each test case has its own subnet, therefore all tests can
88+
// run in parallel without any issue.
89+
//
90+
// This function also compiles all the solidity contracts
91+
var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
92+
ctx, cancel := context.WithTimeout(context.Background(), bootAvalancheNodeTimeout)
6893
defer cancel()
6994

7095
wd, err := os.Getwd()
@@ -76,16 +101,34 @@ func RegisterNodeRun() {
76101

77102
// Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI]
78103
healthClient := health.NewClient(DefaultLocalNodeURI)
79-
healthy, err := health.AwaitReady(ctx, healthClient, 5*time.Second, nil)
104+
healthy, err := health.AwaitReady(ctx, healthClient, healthCheckTimeout, nil)
80105
gomega.Expect(err).Should(gomega.BeNil())
81106
gomega.Expect(healthy).Should(gomega.BeTrue())
82107
log.Info("AvalancheGo node is healthy")
108+
109+
blockchainIds := make(map[string]string)
110+
files, err := filepath.Glob("./tests/precompile/genesis/*.json")
111+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
112+
113+
for _, file := range files {
114+
basename := filepath.Base(file)
115+
index := basename[:len(basename)-5]
116+
blockchainIds[index] = CreateNewSubnet(ctx, file)
117+
}
118+
119+
blockchainIDsBytes, err := json.Marshal(blockchainIds)
120+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
121+
return blockchainIDsBytes
122+
}, func(data []byte) {
123+
err := json.Unmarshal(data, &BlockchainIDs)
124+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
83125
})
84126

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

tests/utils/subnet.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,15 @@ func GetDefaultChainURI(blockchainID string) string {
104104
return fmt.Sprintf("%s/ext/bc/%s/rpc", DefaultLocalNodeURI, blockchainID)
105105
}
106106

107-
// RunDefaultHardhatTests runs the hardhat tests on a new blockchain
107+
// RunDefaultHardhatTests runs the hardhat tests on a given blockchain ID
108108
// with default parameters. Default parameters are:
109-
// 1. Genesis file is located at ./tests/precompile/genesis/<test>.json
110-
// 2. Hardhat contract environment is located at ./contracts
111-
// 3. Hardhat test file is located at ./contracts/test/<test>.ts
112-
// 4. npx is available in the ./contracts directory
113-
func RunDefaultHardhatTests(ctx context.Context, test string) {
114-
log.Info("Executing HardHat tests on a new blockchain", "test", test)
115-
116-
genesisFilePath := fmt.Sprintf("./tests/precompile/genesis/%s.json", test)
117-
118-
blockchainID := CreateNewSubnet(ctx, genesisFilePath)
109+
// 1. Hardhat contract environment is located at ./contracts
110+
// 2. Hardhat test file is located at ./contracts/test/<test>.ts
111+
// 3. npx is available in the ./contracts directory
112+
func RunDefaultHardhatTests(ctx context.Context, blockchainID string, test string) {
119113
chainURI := GetDefaultChainURI(blockchainID)
120-
log.Info("Created subnet successfully", "ChainURI", chainURI)
114+
log.Info("Executing HardHat tests on a new blockchain", "blockchainID", blockchainID, "test", test)
115+
log.Info("Using subnet", "ChainURI", chainURI)
121116

122117
cmdPath := "./contracts"
123118
// test path is relative to the cmd path

0 commit comments

Comments
 (0)