Files
tendermint/test/e2e/runner/main.go
William Banfield bf9232e99f e2e: cleanup on all errors if preserve not specified (#6950)
If the e2e tests error, they leave all of the e2e state around including containers and networks etc. 
We should clean this up when the tests shuts down, even if it exits in error.
2021-09-17 08:35:49 +00:00

327 lines
7.9 KiB
Go

package main
import (
"context"
"fmt"
"os"
"strconv"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/log"
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
)
var (
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
)
func main() {
NewCLI().Run()
}
// CLI is the Cobra-based command-line interface.
type CLI struct {
root *cobra.Command
testnet *e2e.Testnet
preserve bool
}
// NewCLI sets up the CLI.
func NewCLI() *CLI {
cli := &CLI{}
cli.root = &cobra.Command{
Use: "runner",
Short: "End-to-end test runner",
SilenceUsage: true,
SilenceErrors: true, // we'll output them ourselves in Run()
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
file, err := cmd.Flags().GetString("file")
if err != nil {
return err
}
testnet, err := e2e.LoadTestnet(file)
if err != nil {
return err
}
cli.testnet = testnet
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := Cleanup(cli.testnet); err != nil {
return err
}
defer func() {
if cli.preserve {
logger.Info("Preserving testnet contents because -preserve=true")
} else if err := Cleanup(cli.testnet); err != nil {
logger.Error("Error cleaning up testnet contents", "err", err)
}
}()
if err := Setup(cli.testnet); err != nil {
return err
}
chLoadResult := make(chan error)
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
lctx, loadCancel := context.WithCancel(ctx)
defer loadCancel()
go func() {
chLoadResult <- Load(lctx, cli.testnet)
}()
if err := Start(ctx, cli.testnet); err != nil {
return err
}
if err := Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
return err
}
if cli.testnet.HasPerturbations() {
if err := Perturb(ctx, cli.testnet); err != nil {
return err
}
if err := Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
return err
}
}
if cli.testnet.Evidence > 0 {
if err := InjectEvidence(ctx, cli.testnet, cli.testnet.Evidence); err != nil {
return err
}
if err := Wait(ctx, cli.testnet, 5); err != nil { // ensure chain progress
return err
}
}
loadCancel()
if err := <-chLoadResult; err != nil {
return fmt.Errorf("transaction load failed: %w", err)
}
if err := Wait(ctx, cli.testnet, 5); err != nil { // wait for network to settle before tests
return err
}
if err := Test(cli.testnet); err != nil {
return err
}
return nil
},
}
cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest")
_ = cli.root.MarkPersistentFlagRequired("file")
cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false,
"Preserves the running of the test net after tests are completed")
cli.root.SetHelpCommand(&cobra.Command{
Use: "no-help",
Hidden: true,
})
cli.root.AddCommand(&cobra.Command{
Use: "setup",
Short: "Generates the testnet directory and configuration",
RunE: func(cmd *cobra.Command, args []string) error {
return Setup(cli.testnet)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "start",
Short: "Starts the Docker testnet, waiting for nodes to become available",
RunE: func(cmd *cobra.Command, args []string) error {
_, err := os.Stat(cli.testnet.Dir)
if os.IsNotExist(err) {
err = Setup(cli.testnet)
}
if err != nil {
return err
}
return Start(cmd.Context(), cli.testnet)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "perturb",
Short: "Perturbs the Docker testnet, e.g. by restarting or disconnecting nodes",
RunE: func(cmd *cobra.Command, args []string) error {
return Perturb(cmd.Context(), cli.testnet)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "wait",
Short: "Waits for a few blocks to be produced and all nodes to catch up",
RunE: func(cmd *cobra.Command, args []string) error {
return Wait(cmd.Context(), cli.testnet, 5)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "stop",
Short: "Stops the Docker testnet",
RunE: func(cmd *cobra.Command, args []string) error {
logger.Info("Stopping testnet")
return execCompose(cli.testnet.Dir, "down")
},
})
cli.root.AddCommand(&cobra.Command{
Use: "pause",
Short: "Pauses the Docker testnet",
RunE: func(cmd *cobra.Command, args []string) error {
logger.Info("Pausing testnet")
return execCompose(cli.testnet.Dir, "pause")
},
})
cli.root.AddCommand(&cobra.Command{
Use: "resume",
Short: "Resumes the Docker testnet",
RunE: func(cmd *cobra.Command, args []string) error {
logger.Info("Resuming testnet")
return execCompose(cli.testnet.Dir, "unpause")
},
})
cli.root.AddCommand(&cobra.Command{
Use: "load",
Short: "Generates transaction load until the command is canceled",
RunE: func(cmd *cobra.Command, args []string) (err error) {
return Load(context.Background(), cli.testnet)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "evidence [amount]",
Args: cobra.MaximumNArgs(1),
Short: "Generates and broadcasts evidence to a random node",
RunE: func(cmd *cobra.Command, args []string) (err error) {
amount := 1
if len(args) == 1 {
amount, err = strconv.Atoi(args[0])
if err != nil {
return err
}
}
return InjectEvidence(cmd.Context(), cli.testnet, amount)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "test",
Short: "Runs test cases against a running testnet",
RunE: func(cmd *cobra.Command, args []string) error {
return Test(cli.testnet)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "cleanup",
Short: "Removes the testnet directory",
RunE: func(cmd *cobra.Command, args []string) error {
return Cleanup(cli.testnet)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "logs [node]",
Short: "Shows the testnet or a specefic node's logs",
Example: "runner logs validator03",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return execComposeVerbose(cli.testnet.Dir, append([]string{"logs", "--no-color"}, args...)...)
},
})
cli.root.AddCommand(&cobra.Command{
Use: "tail [node]",
Short: "Tails the testnet or a specific node's logs",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
return execComposeVerbose(cli.testnet.Dir, "logs", "--follow", args[0])
}
return execComposeVerbose(cli.testnet.Dir, "logs", "--follow")
},
})
cli.root.AddCommand(&cobra.Command{
Use: "benchmark",
Short: "Benchmarks testnet",
Long: `Benchmarks the following metrics:
Mean Block Interval
Standard Deviation
Min Block Interval
Max Block Interval
over a 100 block sampling period.
Does not run any perbutations.
`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := Cleanup(cli.testnet); err != nil {
return err
}
defer func() {
if err := Cleanup(cli.testnet); err != nil {
logger.Error("Error cleaning up testnet contents", "err", err)
}
}()
if err := Setup(cli.testnet); err != nil {
return err
}
chLoadResult := make(chan error)
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
lctx, loadCancel := context.WithCancel(ctx)
defer loadCancel()
go func() {
err := Load(lctx, cli.testnet)
chLoadResult <- err
}()
if err := Start(ctx, cli.testnet); err != nil {
return err
}
if err := Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
return err
}
// we benchmark performance over the next 100 blocks
if err := Benchmark(ctx, cli.testnet, 100); err != nil {
return err
}
loadCancel()
if err := <-chLoadResult; err != nil {
return err
}
return nil
},
})
return cli
}
// Run runs the CLI.
func (cli *CLI) Run() {
if err := cli.root.Execute(); err != nil {
logger.Error(err.Error())
os.Exit(1)
}
}