Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
"\\[Node create\\]",
"\\[Node devnet\\]",
"\\[Docker\\]",
"\\[Blockchain Deploy Flags\\]",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we want to start modifying CI at this stage

]
os: [ubuntu-24.04, macos-14]
exclude:
Expand Down
111 changes: 111 additions & 0 deletions tests/e2e/commands/blockchain/blockchain_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package blockchain

import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"

"github.com/ava-labs/avalanche-cli/tests/e2e/utils"
)

// TestCase represents a single test case configuration
type TestCase struct {
Name string `json:"name"`
Flags map[string]string `json:"flags"`
ExpectedError string `json:"expectedError,omitempty"`
}

// TestJSONConfig represents the json configuration that contains cli command flag inputs
type TestJSONConfig struct {
GlobalFlags map[string]interface{} `json:"globalFlags"`
HappyPath []TestCase `json:"happyPath"`
NotHappyPath []TestCase `json:"notHappyPath"`
}

var avalancheBinaryPath = "./bin/avalanche"

// SetAvalancheBinaryPath sets the path to the avalanche binary
func SetAvalancheBinaryPath(path string) {
avalancheBinaryPath = path
}

// TestCommandWithJSONConfig tests a CLI command with flag inputs from a JSON file
func TestCommandWithJSONConfig(command string, configPath string, testCase *TestCase) (string, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love the idea - any plan to move it to some test common directories to enable it reusable by all other commands?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Yes i will move it to external directories

Copy link
Contributor Author

@sukantoraymond sukantoraymond Apr 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generalized the function

blockchainCmd := "blockchain"

// Read and parse the JSON config file
configData, err := os.ReadFile(configPath)
if err != nil {
return "", fmt.Errorf("failed to read config file: %w", err)
}

var config TestJSONConfig
if err := json.Unmarshal(configData, &config); err != nil {
return "", fmt.Errorf("failed to parse config file: %w", err)
}

// Build command arguments
cmdArgs := []string{blockchainCmd, command}

// Add blockchain name from global flags
if blockchainName, ok := config.GlobalFlags["blockchainName"].(string); ok {
cmdArgs = append(cmdArgs, blockchainName)
}

// Create a map to store all flags, starting with global flags
allFlags := make(map[string]interface{})
for flag, value := range config.GlobalFlags {
if flag != "blockchainName" {
allFlags[flag] = value
}
}

// Override with test case specific flags if provided
if testCase != nil {
for flag, value := range testCase.Flags {
allFlags[flag] = value
}
}

// Add all flags to command arguments
for flag, value := range allFlags {
cmdArgs = append(cmdArgs, "--"+flag+"="+fmt.Sprintf("%v", value))
}

// Execute the command
cmd := exec.Command(avalancheBinaryPath, cmdArgs...)
fmt.Println(cmd)
output, err := cmd.CombinedOutput()
if err != nil {
var (
exitErr *exec.ExitError
stderr string
)
if errors.As(err, &exitErr) {
stderr = string(exitErr.Stderr)
}
fmt.Println(string(output))
utils.PrintStdErr(err)
fmt.Println(stderr)
return "", err
}

return string(output), nil
}

// ReadTestConfig reads and parses the test configuration from a JSON file
func ReadTestConfig(configPath string) (*TestJSONConfig, error) {
configData, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}

var config TestJSONConfig
if err := json.Unmarshal(configData, &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}

return &config, nil
}
18 changes: 9 additions & 9 deletions tests/e2e/commands/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (

const (
subnetEVMMainnetChainID = 11
poaValidatorManagerOwner = "0x2e6FcBb9d4E17eC4cF67eddfa7D32eabC4cdCFc6"
PoaValidatorManagerOwner = "0x2e6FcBb9d4E17eC4cF67eddfa7D32eabC4cdCFc6"
bootstrapFilepathFlag = "--bootstrap-filepath"
avalancheGoPath = "--avalanchego-path"
localNodeClusterName = "testLocalNode"
Expand All @@ -35,12 +35,12 @@ func CreateSubnetEvmConfigNonSOV(subnetName string, genesisPath string) (string,
return mapping[utils.LatestEVM2AvagoKey], mapping[utils.LatestAvago2EVMKey]
}

func CreateSubnetEvmConfigSOV(subnetName string, genesisPath string) (string, string) {
func CreateSubnetEvmConfigSOV(subnetName string, genesisPath string, validatorManagerOwner string) (string, string) {
mapper := utils.NewVersionMapper()
mapping, err := utils.GetVersionMapping(mapper)
gomega.Expect(err).Should(gomega.BeNil())
// let's use a SubnetEVM version which has a guaranteed compatible avago
CreateSubnetEvmConfigWithVersionSOV(subnetName, genesisPath, mapping[utils.LatestEVM2AvagoKey])
CreateSubnetEvmConfigWithVersionSOV(subnetName, genesisPath, mapping[utils.LatestEVM2AvagoKey], validatorManagerOwner)
return mapping[utils.LatestEVM2AvagoKey], mapping[utils.LatestAvago2EVMKey]
}

Expand Down Expand Up @@ -85,7 +85,7 @@ func CreateSubnetEvmConfigWithVersionNonSOV(subnetName string, genesisPath strin
gomega.Expect(exists).Should(gomega.BeTrue())
}

func CreateSubnetEvmConfigWithVersionSOV(subnetName string, genesisPath string, version string) {
func CreateSubnetEvmConfigWithVersionSOV(subnetName string, genesisPath string, version string, validatorManagerOwner string) {
// Check config does not already exist
exists, err := utils.SubnetConfigExists(subnetName)
gomega.Expect(err).Should(gomega.BeNil())
Expand All @@ -101,9 +101,9 @@ func CreateSubnetEvmConfigWithVersionSOV(subnetName string, genesisPath string,
subnetName,
"--proof-of-authority",
"--validator-manager-owner",
poaValidatorManagerOwner,
validatorManagerOwner,
"--proxy-contract-owner",
poaValidatorManagerOwner,
validatorManagerOwner,
"--" + constants.SkipUpdateFlag,
"--icm=false",
"--evm-token",
Expand Down Expand Up @@ -217,7 +217,7 @@ func CreateCustomVMConfigNonSOV(subnetName string, genesisPath string, vmPath st
gomega.Expect(exists).Should(gomega.BeTrue())
}

func CreateCustomVMConfigSOV(subnetName string, genesisPath string, vmPath string) {
func CreateCustomVMConfigSOV(subnetName string, genesisPath string, vmPath string, validatorManagerOwner string) {
// Check config does not already exist
exists, err := utils.SubnetConfigExists(subnetName)
gomega.Expect(err).Should(gomega.BeNil())
Expand All @@ -236,9 +236,9 @@ func CreateCustomVMConfigSOV(subnetName string, genesisPath string, vmPath strin
genesisPath,
"--proof-of-authority",
"--validator-manager-owner",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also need to stop using flag constants here, and take those from cmd/flags

poaValidatorManagerOwner,
validatorManagerOwner,
"--proxy-contract-owner",
poaValidatorManagerOwner,
validatorManagerOwner,
"--custom",
subnetName,
"--custom-vm-path",
Expand Down
111 changes: 111 additions & 0 deletions tests/e2e/commands_e2e/blockchain/blockchain_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package blockchain

import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"

"github.com/ava-labs/avalanche-cli/tests/e2e/utils"
)

// TestCase represents a single test case configuration
type TestCase struct {
Name string `json:"name"`
Flags map[string]string `json:"flags"`
ExpectedError string `json:"expectedError,omitempty"`
}

// TestJSONConfig represents the json configuration that contains cli command flag inputs
type TestJSONConfig struct {
GlobalFlags map[string]interface{} `json:"globalFlags"`
HappyPath []TestCase `json:"happyPath"`
NotHappyPath []TestCase `json:"notHappyPath"`
}

var avalancheBinaryPath = "./bin/avalanche"

// SetAvalancheBinaryPath sets the path to the avalanche binary
func SetAvalancheBinaryPath(path string) {
avalancheBinaryPath = path
}

// TestCommandWithJSONConfig tests a CLI command with flag inputs from a JSON file
func TestCommandWithJSONConfig(command string, configPath string, testCase *TestCase) (string, error) {
blockchainCmd := "blockchain"

// Read and parse the JSON config file
configData, err := os.ReadFile(configPath)
if err != nil {
return "", fmt.Errorf("failed to read config file: %w", err)
}

var config TestJSONConfig
if err := json.Unmarshal(configData, &config); err != nil {
return "", fmt.Errorf("failed to parse config file: %w", err)
}

// Build command arguments
cmdArgs := []string{blockchainCmd, command}

// Add blockchain name from global flags
if blockchainName, ok := config.GlobalFlags["blockchainName"].(string); ok {
cmdArgs = append(cmdArgs, blockchainName)
}

// Create a map to store all flags, starting with global flags
allFlags := make(map[string]interface{})
for flag, value := range config.GlobalFlags {
if flag != "blockchainName" {
allFlags[flag] = value
}
}

// Override with test case specific flags if provided
if testCase != nil {
for flag, value := range testCase.Flags {
allFlags[flag] = value
}
}

// Add all flags to command arguments
for flag, value := range allFlags {
cmdArgs = append(cmdArgs, "--"+flag+"="+fmt.Sprintf("%v", value))
}

// Execute the command
cmd := exec.Command(avalancheBinaryPath, cmdArgs...)
fmt.Println(cmd)
output, err := cmd.CombinedOutput()
if err != nil {
var (
exitErr *exec.ExitError
stderr string
)
if errors.As(err, &exitErr) {
stderr = string(exitErr.Stderr)
}
fmt.Println(string(output))
utils.PrintStdErr(err)
fmt.Println(stderr)
return "", err
}

return string(output), nil
}

// ReadTestConfig reads and parses the test configuration from a JSON file
func ReadTestConfig(configPath string) (*TestJSONConfig, error) {
configData, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}

var config TestJSONConfig
if err := json.Unmarshal(configData, &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}

return &config, nil
}
23 changes: 23 additions & 0 deletions tests/e2e/commands_e2e/blockchain/deploy/deploy_tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"globalFlags": {
"local": true,
"skip-icm-deploy": true,
"skip-update-check": true,
"blockchainName": "testSubnet"
},
"happyPath": [
{
"name": "local_deploy",
"flags": {}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can set globalFlags in each testCases as well, which will override the globalFlags value

],
"notHappyPath": [
{
"name": "invalid_version",
"flags": {
"avalanchego-version": "invalid_version"
},
"expectedError": "invalid version string"
}
]
}
69 changes: 69 additions & 0 deletions tests/e2e/commands_e2e/blockchain/deploy/suite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package deploy

import (
"fmt"

"github.com/ava-labs/avalanche-cli/tests/e2e/commands"
"github.com/ava-labs/avalanche-cli/tests/e2e/commands_e2e/blockchain"
"github.com/ava-labs/avalanche-cli/tests/e2e/utils"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)

const (
deployTestJSONPath = "tests/e2e/commands_e2e/blockchain/deploy/deploy_tests.json"
subnetName = "testSubnet"
)

var (
config *blockchain.TestJSONConfig
err error
)

const ewoqEVMAddress = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"

var _ = ginkgo.Describe("[Blockchain Deploy Flags]", ginkgo.Ordered, func() {
_ = ginkgo.BeforeEach(func() {
// Create test subnet config
commands.CreateSubnetEvmConfigSOV(subnetName, utils.SubnetEvmGenesisPath, ewoqEVMAddress)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use ewoq here because we use ewoq as validator manager owner when we deploy in local network


// Read test configuration
config, err = blockchain.ReadTestConfig(deployTestJSONPath)
gomega.Expect(err).Should(gomega.BeNil())
})

ginkgo.AfterEach(func() {
commands.CleanNetwork()
// Cleanup test subnet config
commands.DeleteSubnetConfig(subnetName)
})

ginkgo.It("should successfully deploy a blockchain", func() {
// Run each happy path test case
for _, testCase := range config.HappyPath {
ginkgo.By(fmt.Sprintf("Running test case: %s", testCase.Name))
_, err = blockchain.TestCommandWithJSONConfig(
"deploy",
deployTestJSONPath,
&testCase,
)
gomega.Expect(err).Should(gomega.BeNil())
}
})

ginkgo.It("should handle invalid configurations", func() {
// Run each not happy path test case
for _, testCase := range config.NotHappyPath {
ginkgo.By(fmt.Sprintf("Running test case: %s", testCase.Name))
_, err = blockchain.TestCommandWithJSONConfig(
"deploy",
deployTestJSONPath,
&testCase,
)
gomega.Expect(err).Should(gomega.HaveOccurred())
}
})
})
Loading
Loading