Skip to content

Commit

Permalink
e2e: Add local network fixture (ava-labs#1700)
Browse files Browse the repository at this point in the history
  • Loading branch information
marun authored Aug 8, 2023
1 parent 429eb44 commit cae18b9
Show file tree
Hide file tree
Showing 18 changed files with 2,021 additions and 4 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/test.e2e.persistent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Test e2e with persistent network

on:
push:
tags:
- "*"
branches:
- master
- dev
pull_request:

permissions:
contents: read

jobs:
test_e2e_persistent:
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '~1.19.12'
check-latest: true
- name: Build the avalanchego binary
shell: bash
run: ./scripts/build.sh -r
- name: Run e2e tests with persistent network
shell: bash
run: ./scripts/tests.e2e.persistent.sh ./build/avalanchego
4 changes: 3 additions & 1 deletion config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (

AvalancheGoDataDirVar = "AVALANCHEGO_DATA_DIR"
defaultUnexpandedDataDir = "$" + AvalancheGoDataDirVar

ProcessContextFilename = "process.json"
)

var (
Expand All @@ -50,7 +52,7 @@ var (
defaultSubnetConfigDir = filepath.Join(defaultConfigDir, "subnets")
defaultPluginDir = filepath.Join(defaultUnexpandedDataDir, "plugins")
defaultChainDataDir = filepath.Join(defaultUnexpandedDataDir, "chainData")
defaultProcessContextPath = filepath.Join(defaultUnexpandedDataDir, "process.json")
defaultProcessContextPath = filepath.Join(defaultUnexpandedDataDir, ProcessContextFilename)
)

func deprecateFlags(fs *pflag.FlagSet) error {
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ require (
github.com/rs/cors v1.7.0
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spaolacci/murmur3 v1.1.0
github.com/spf13/cast v1.5.0
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.8.1
Expand Down Expand Up @@ -103,6 +105,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
Expand All @@ -124,7 +127,6 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sanity-io/litter v1.5.1 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/status-im/keycard-go v0.2.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
Expand Down
64 changes: 64 additions & 0 deletions go.sum

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -476,7 +477,7 @@ func (n *Node) Dispatch() error {

// Remove the process context file to communicate to an orchestrator
// that the node is no longer running.
if err := os.Remove(n.Config.ProcessContextFilePath); err != nil && !os.IsNotExist(err) {
if err := os.Remove(n.Config.ProcessContextFilePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
n.Log.Error("removal of process context file failed",
zap.String("path", n.Config.ProcessContextFilePath),
zap.Error(err),
Expand Down
3 changes: 2 additions & 1 deletion scripts/build_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
# Load the constants
source "$AVALANCHE_PATH"/scripts/constants.sh

go test -shuffle=on -race -timeout="120s" -coverprofile="coverage.out" -covermode="atomic" $(go list ./... | grep -v /mocks | grep -v proto | grep -v tests)
# Ensure execution of fixture unit tests under tests/ but exclude ginkgo tests in tests/e2e and tests/upgrade
go test -shuffle=on -race -timeout="120s" -coverprofile="coverage.out" -covermode="atomic" $(go list ./... | grep -v /mocks | grep -v proto | grep -v tests/e2e | grep -v tests/upgrade)
16 changes: 16 additions & 0 deletions scripts/build_testnetctl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

# Avalanchego root folder
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
# Load the constants
source "$AVALANCHE_PATH"/scripts/constants.sh

echo "Building testnetctl..."
go build -ldflags\
"-X github.com/ava-labs/avalanchego/version.GitCommit=$git_commit $static_ld_flags"\
-o "$AVALANCHE_PATH/build/testnetctl"\
"$AVALANCHE_PATH/tests/fixture/testnet/cmd/"*.go
64 changes: 64 additions & 0 deletions scripts/tests.e2e.persistent.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash

set -e
set -o nounset
set -o pipefail

################################################################
# This script deploys a persistent local network and configures
# tests.e2e.sh to execute the e2e suite against it.
################################################################

# e.g.,
# ./scripts/build.sh
# ./scripts/tests.e2e.persistent_network.sh ./build/avalanchego
if ! [[ "$0" =~ scripts/tests.e2e.persistent.sh ]]; then
echo "must be run from repository root"
exit 255
fi

AVALANCHEGO_PATH="${1-${AVALANCHEGO_PATH:-}}"
if [[ -z "${AVALANCHEGO_PATH}" ]]; then
echo "Missing AVALANCHEGO_PATH argument!"
echo "Usage: ${0} [AVALANCHEGO_PATH]" >>/dev/stderr
exit 255
fi
# Ensure an absolute path to avoid dependency on the working directory
# of script execution.
export AVALANCHEGO_PATH="$(realpath ${AVALANCHEGO_PATH})"

# Create a temporary directory to store persistent network
ROOT_DIR="$(mktemp -d -t e2e-testnet.XXXXX)"

# Provide visual separation between testing and setup/teardown
function print_separator {
printf '%*s\n' "${COLUMNS:-80}" '' | tr ' '
}

# Ensure network cleanup on teardown
function cleanup {
print_separator
echo "cleaning up persistent network"
if [[ -n "${TESTNETCTL_NETWORK_DIR:-}" ]]; then
./build/testnetctl stop-network
fi
rm -r "${ROOT_DIR}"
}
trap cleanup EXIT

# Start a persistent network
./scripts/build_testnetctl.sh
print_separator
./build/testnetctl start-network --root-dir="${ROOT_DIR}"

# Determine the network configuration path from the latest symlink
LATEST_SYMLINK_PATH="${ROOT_DIR}/latest"
if [[ -h "${LATEST_SYMLINK_PATH}" ]]; then
export TESTNETCTL_NETWORK_DIR="$(realpath ${LATEST_SYMLINK_PATH})"
else
echo "failed to find configuration path: ${LATEST_SYMLINK_PATH} symlink not found"
exit 255
fi

print_separator
# TODO(marun) Enable e2e testing
8 changes: 8 additions & 0 deletions tests/fixture/testnet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Test Network Fixture

This package contains configuration and interfaces that are
independent of a given orchestration mechanism
(e.g. [local](local/README.md)). The intent is to enable tests to be
written against the interfaces defined in this package and for
implementation-specific details of test network orchestration to be
limited to test setup and teardown.
124 changes: 124 additions & 0 deletions tests/fixture/testnet/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package main

import (
"context"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"

"github.com/spf13/cobra"

"github.com/ava-labs/avalanchego/tests/fixture/testnet"
"github.com/ava-labs/avalanchego/tests/fixture/testnet/local"
"github.com/ava-labs/avalanchego/version"
)

const cliVersion = "0.0.1"

var (
errAvalancheGoRequired = fmt.Errorf("--avalanchego-path or %s are required", local.AvalancheGoPathEnvName)
errNetworkDirRequired = fmt.Errorf("--network-dir or %s are required", local.NetworkDirEnvName)
)

func main() {
rootCmd := &cobra.Command{
Use: "testnetctl",
Short: "testnetctl commands",
}

versionCmd := &cobra.Command{
Use: "version",
Short: "Print version details",
RunE: func(*cobra.Command, []string) error {
msg := cliVersion
if len(version.GitCommit) > 0 {
msg += ", commit=" + version.GitCommit
}
fmt.Fprintf(os.Stdout, msg+"\n")
return nil
},
}
rootCmd.AddCommand(versionCmd)

var (
rootDir string
execPath string
nodeCount uint8
fundedKeyCount uint8
)
startNetworkCmd := &cobra.Command{
Use: "start-network",
Short: "Start a new local network",
RunE: func(*cobra.Command, []string) error {
if len(execPath) == 0 {
return errAvalancheGoRequired
}

// Root dir will be defaulted on start if not provided

network := &local.LocalNetwork{
LocalConfig: local.LocalConfig{
ExecPath: execPath,
},
}
ctx, cancel := context.WithTimeout(context.Background(), local.DefaultNetworkStartTimeout)
defer cancel()
network, err := local.StartNetwork(ctx, os.Stdout, rootDir, network, int(nodeCount), int(fundedKeyCount))
if err != nil {
return err
}

// Symlink the new network to the 'latest' network to simplify usage
networkRootDir := filepath.Dir(network.Dir)
networkDirName := filepath.Base(network.Dir)
latestSymlinkPath := filepath.Join(networkRootDir, "latest")
if err := os.Remove(latestSymlinkPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
if err := os.Symlink(networkDirName, latestSymlinkPath); err != nil {
return err
}

fmt.Fprintf(os.Stdout, "\nConfigure testnetctl to target this network by default with one of the following statements:")
fmt.Fprintf(os.Stdout, "\n - source %s\n", network.EnvFilePath())
fmt.Fprintf(os.Stdout, " - %s\n", network.EnvFileContents())
fmt.Fprintf(os.Stdout, " - export %s=%s\n", local.NetworkDirEnvName, latestSymlinkPath)

return nil
},
}
startNetworkCmd.PersistentFlags().StringVar(&rootDir, "root-dir", os.Getenv(local.RootDirEnvName), "The path to the root directory for local networks")
startNetworkCmd.PersistentFlags().StringVar(&execPath, "avalanchego-path", os.Getenv(local.AvalancheGoPathEnvName), "The path to an avalanchego binary")
startNetworkCmd.PersistentFlags().Uint8Var(&nodeCount, "node-count", testnet.DefaultNodeCount, "Number of nodes the network should initially consist of")
startNetworkCmd.PersistentFlags().Uint8Var(&fundedKeyCount, "funded-key-count", testnet.DefaultFundedKeyCount, "Number of funded keys the network should start with")
rootCmd.AddCommand(startNetworkCmd)

var networkDir string
stopNetworkCmd := &cobra.Command{
Use: "stop-network",
Short: "Stop a local network",
RunE: func(*cobra.Command, []string) error {
if len(networkDir) == 0 {
return errNetworkDirRequired
}
if err := local.StopNetwork(networkDir); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Stopped network configured at: %s\n", networkDir)
return nil
},
}
stopNetworkCmd.PersistentFlags().StringVar(&networkDir, "network-dir", os.Getenv(local.NetworkDirEnvName), "The path to the configuration directory of a local network")
rootCmd.AddCommand(stopNetworkCmd)

if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "testnetctl failed: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}
Loading

0 comments on commit cae18b9

Please sign in to comment.