Skip to content
Closed
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
}
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"
}
]
}
66 changes: 66 additions & 0 deletions tests/e2e/commands_e2e/blockchain/deploy/suite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package deploy

import (
"fmt"

Check failure on line 7 in tests/e2e/commands_e2e/blockchain/deploy/suite.go

View workflow job for this annotation

GitHub Actions / Lint

File is not properly formatted (gofumpt)
"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"

Check failure on line 16 in tests/e2e/commands_e2e/blockchain/deploy/suite.go

View workflow job for this annotation

GitHub Actions / Lint

ST1003: const deployTestJsonPath should be deployTestJSONPath (stylecheck)
subnetName = "testSubnet"
)

var (
config *blockchain.TestJSONConfig
err error
)

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

// 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())
}
})
})
1 change: 1 addition & 0 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

"github.com/ava-labs/avalanche-cli/pkg/utils"
_ "github.com/ava-labs/avalanche-cli/tests/e2e/commands_e2e/blockchain/deploy"
_ "github.com/ava-labs/avalanche-cli/tests/e2e/testcases/apm"
_ "github.com/ava-labs/avalanche-cli/tests/e2e/testcases/errhandling"
_ "github.com/ava-labs/avalanche-cli/tests/e2e/testcases/key"
Expand Down
Loading