github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/test/e2e/runner/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	stdlog "log"
     7  	"math/rand"
     8  	"os"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/spf13/cobra"
    13  
    14  	"github.com/ari-anchor/sei-tendermint/libs/log"
    15  	e2e "github.com/ari-anchor/sei-tendermint/test/e2e/pkg"
    16  )
    17  
    18  const randomSeed = 2308084734268
    19  
    20  func main() {
    21  	ctx, cancel := context.WithCancel(context.Background())
    22  	defer cancel()
    23  
    24  	logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
    25  	if err != nil {
    26  		stdlog.Fatal(err)
    27  	}
    28  
    29  	NewCLI(logger).Run(ctx, logger)
    30  }
    31  
    32  // CLI is the Cobra-based command-line interface.
    33  type CLI struct {
    34  	root     *cobra.Command
    35  	testnet  *e2e.Testnet
    36  	preserve bool
    37  }
    38  
    39  // NewCLI sets up the CLI.
    40  func NewCLI(logger log.Logger) *CLI {
    41  	cli := &CLI{}
    42  	cli.root = &cobra.Command{
    43  		Use:           "runner",
    44  		Short:         "End-to-end test runner",
    45  		SilenceUsage:  true,
    46  		SilenceErrors: true, // we'll output them ourselves in Run()
    47  		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
    48  			file, err := cmd.Flags().GetString("file")
    49  			if err != nil {
    50  				return err
    51  			}
    52  			testnet, err := e2e.LoadTestnet(file)
    53  			if err != nil {
    54  				return err
    55  			}
    56  
    57  			cli.testnet = testnet
    58  			return nil
    59  		},
    60  		RunE: func(cmd *cobra.Command, args []string) (err error) {
    61  			if err = Cleanup(logger, cli.testnet); err != nil {
    62  				return err
    63  			}
    64  			defer func() {
    65  				if cli.preserve {
    66  					logger.Info("Preserving testnet contents because -preserve=true")
    67  				} else if err != nil {
    68  					logger.Info("Preserving testnet that encountered error",
    69  						"err", err)
    70  				} else if err := Cleanup(logger, cli.testnet); err != nil {
    71  					logger.Error("error cleaning up testnet contents", "err", err)
    72  				}
    73  			}()
    74  			if err = Setup(logger, cli.testnet); err != nil {
    75  				return err
    76  			}
    77  
    78  			r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec
    79  
    80  			chLoadResult := make(chan error)
    81  			ctx, cancel := context.WithCancel(cmd.Context())
    82  			defer cancel()
    83  
    84  			lctx, loadCancel := context.WithCancel(ctx)
    85  			defer loadCancel()
    86  			go func() {
    87  				chLoadResult <- Load(lctx, logger, r, cli.testnet)
    88  			}()
    89  			startAt := time.Now()
    90  			if err = Start(ctx, logger, cli.testnet); err != nil {
    91  				return err
    92  			}
    93  
    94  			if err = Wait(ctx, logger, cli.testnet, 5); err != nil { // allow some txs to go through
    95  				return err
    96  			}
    97  
    98  			if cli.testnet.HasPerturbations() {
    99  				if err = Perturb(ctx, logger, cli.testnet); err != nil {
   100  					return err
   101  				}
   102  				if err = Wait(ctx, logger, cli.testnet, 5); err != nil { // allow some txs to go through
   103  					return err
   104  				}
   105  			}
   106  
   107  			if cli.testnet.Evidence > 0 {
   108  				if err = InjectEvidence(ctx, logger, r, cli.testnet, cli.testnet.Evidence); err != nil {
   109  					return err
   110  				}
   111  				if err = Wait(ctx, logger, cli.testnet, 5); err != nil { // ensure chain progress
   112  					return err
   113  				}
   114  			}
   115  
   116  			// to help make sure that we don't run into
   117  			// situations where 0 transactions have
   118  			// happened on quick cases, we make sure that
   119  			// it's been at least 10s before canceling the
   120  			// load generator.
   121  			//
   122  			// TODO allow the load generator to report
   123  			// successful transactions to avoid needing
   124  			// this sleep.
   125  			if rest := time.Since(startAt); rest < 15*time.Second {
   126  				time.Sleep(15*time.Second - rest)
   127  			}
   128  
   129  			loadCancel()
   130  
   131  			if err = <-chLoadResult; err != nil {
   132  				return fmt.Errorf("transaction load failed: %w", err)
   133  			}
   134  			if err = Wait(ctx, logger, cli.testnet, 5); err != nil { // wait for network to settle before tests
   135  				return err
   136  			}
   137  			if err := Test(cli.testnet); err != nil {
   138  				return err
   139  			}
   140  			return nil
   141  		},
   142  	}
   143  
   144  	cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest")
   145  	_ = cli.root.MarkPersistentFlagRequired("file")
   146  
   147  	cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false,
   148  		"Preserves the running of the test net after tests are completed")
   149  
   150  	cli.root.SetHelpCommand(&cobra.Command{
   151  		Use:    "no-help",
   152  		Hidden: true,
   153  	})
   154  
   155  	cli.root.AddCommand(&cobra.Command{
   156  		Use:   "setup",
   157  		Short: "Generates the testnet directory and configuration",
   158  		RunE: func(cmd *cobra.Command, args []string) error {
   159  			return Setup(logger, cli.testnet)
   160  		},
   161  	})
   162  
   163  	cli.root.AddCommand(&cobra.Command{
   164  		Use:   "start",
   165  		Short: "Starts the Docker testnet, waiting for nodes to become available",
   166  		RunE: func(cmd *cobra.Command, args []string) error {
   167  			_, err := os.Stat(cli.testnet.Dir)
   168  			if os.IsNotExist(err) {
   169  				err = Setup(logger, cli.testnet)
   170  			}
   171  			if err != nil {
   172  				return err
   173  			}
   174  			return Start(cmd.Context(), logger, cli.testnet)
   175  		},
   176  	})
   177  
   178  	cli.root.AddCommand(&cobra.Command{
   179  		Use:   "perturb",
   180  		Short: "Perturbs the Docker testnet, e.g. by restarting or disconnecting nodes",
   181  		RunE: func(cmd *cobra.Command, args []string) error {
   182  			return Perturb(cmd.Context(), logger, cli.testnet)
   183  		},
   184  	})
   185  
   186  	cli.root.AddCommand(&cobra.Command{
   187  		Use:   "wait",
   188  		Short: "Waits for a few blocks to be produced and all nodes to catch up",
   189  		RunE: func(cmd *cobra.Command, args []string) error {
   190  			return Wait(cmd.Context(), logger, cli.testnet, 5)
   191  		},
   192  	})
   193  
   194  	cli.root.AddCommand(&cobra.Command{
   195  		Use:   "stop",
   196  		Short: "Stops the Docker testnet",
   197  		RunE: func(cmd *cobra.Command, args []string) error {
   198  			logger.Info("Stopping testnet")
   199  			return execCompose(cli.testnet.Dir, "down")
   200  		},
   201  	})
   202  
   203  	cli.root.AddCommand(&cobra.Command{
   204  		Use:   "pause",
   205  		Short: "Pauses the Docker testnet",
   206  		RunE: func(cmd *cobra.Command, args []string) error {
   207  			logger.Info("Pausing testnet")
   208  			return execCompose(cli.testnet.Dir, "pause")
   209  		},
   210  	})
   211  
   212  	cli.root.AddCommand(&cobra.Command{
   213  		Use:   "resume",
   214  		Short: "Resumes the Docker testnet",
   215  		RunE: func(cmd *cobra.Command, args []string) error {
   216  			logger.Info("Resuming testnet")
   217  			return execCompose(cli.testnet.Dir, "unpause")
   218  		},
   219  	})
   220  
   221  	cli.root.AddCommand(&cobra.Command{
   222  		Use:   "load",
   223  		Short: "Generates transaction load until the command is canceled",
   224  		RunE: func(cmd *cobra.Command, args []string) (err error) {
   225  			return Load(
   226  				cmd.Context(),
   227  				logger,
   228  				rand.New(rand.NewSource(randomSeed)), // nolint: gosec
   229  				cli.testnet,
   230  			)
   231  		},
   232  	})
   233  
   234  	cli.root.AddCommand(&cobra.Command{
   235  		Use:   "evidence [amount]",
   236  		Args:  cobra.MaximumNArgs(1),
   237  		Short: "Generates and broadcasts evidence to a random node",
   238  		RunE: func(cmd *cobra.Command, args []string) (err error) {
   239  			amount := 1
   240  
   241  			if len(args) == 1 {
   242  				amount, err = strconv.Atoi(args[0])
   243  				if err != nil {
   244  					return err
   245  				}
   246  			}
   247  
   248  			return InjectEvidence(
   249  				cmd.Context(),
   250  				logger,
   251  				rand.New(rand.NewSource(randomSeed)), // nolint: gosec
   252  				cli.testnet,
   253  				amount,
   254  			)
   255  		},
   256  	})
   257  
   258  	cli.root.AddCommand(&cobra.Command{
   259  		Use:   "test",
   260  		Short: "Runs test cases against a running testnet",
   261  		RunE: func(cmd *cobra.Command, args []string) error {
   262  			return Test(cli.testnet)
   263  		},
   264  	})
   265  
   266  	cli.root.AddCommand(&cobra.Command{
   267  		Use:   "cleanup",
   268  		Short: "Removes the testnet directory",
   269  		RunE: func(cmd *cobra.Command, args []string) error {
   270  			return Cleanup(logger, cli.testnet)
   271  		},
   272  	})
   273  
   274  	cli.root.AddCommand(&cobra.Command{
   275  		Use:     "logs [node]",
   276  		Short:   "Shows the testnet or a specefic node's logs",
   277  		Example: "runner logs validator03",
   278  		Args:    cobra.MaximumNArgs(1),
   279  		RunE: func(cmd *cobra.Command, args []string) error {
   280  			return execComposeVerbose(cli.testnet.Dir, append([]string{"logs", "--no-color"}, args...)...)
   281  		},
   282  	})
   283  
   284  	cli.root.AddCommand(&cobra.Command{
   285  		Use:   "tail [node]",
   286  		Short: "Tails the testnet or a specific node's logs",
   287  		Args:  cobra.MaximumNArgs(1),
   288  		RunE: func(cmd *cobra.Command, args []string) error {
   289  			if len(args) == 1 {
   290  				return execComposeVerbose(cli.testnet.Dir, "logs", "--follow", args[0])
   291  			}
   292  			return execComposeVerbose(cli.testnet.Dir, "logs", "--follow")
   293  		},
   294  	})
   295  
   296  	cli.root.AddCommand(&cobra.Command{
   297  		Use:   "benchmark",
   298  		Short: "Benchmarks testnet",
   299  		Long: `Benchmarks the following metrics:
   300  	Mean Block Interval
   301  	Standard Deviation
   302  	Min Block Interval
   303  	Max Block Interval
   304  over a 100 block sampling period.
   305  		
   306  Does not run any perbutations.
   307  		`,
   308  		RunE: func(cmd *cobra.Command, args []string) error {
   309  			if err := Cleanup(logger, cli.testnet); err != nil {
   310  				return err
   311  			}
   312  			defer func() {
   313  				if err := Cleanup(logger, cli.testnet); err != nil {
   314  					logger.Error("error cleaning up testnet contents", "err", err)
   315  				}
   316  			}()
   317  
   318  			if err := Setup(logger, cli.testnet); err != nil {
   319  				return err
   320  			}
   321  
   322  			chLoadResult := make(chan error)
   323  			ctx, cancel := context.WithCancel(cmd.Context())
   324  			defer cancel()
   325  
   326  			r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec
   327  
   328  			lctx, loadCancel := context.WithCancel(ctx)
   329  			defer loadCancel()
   330  			go func() {
   331  				chLoadResult <- Load(lctx, logger, r, cli.testnet)
   332  			}()
   333  
   334  			if err := Start(ctx, logger, cli.testnet); err != nil {
   335  				return err
   336  			}
   337  
   338  			if err := Wait(ctx, logger, cli.testnet, 5); err != nil { // allow some txs to go through
   339  				return err
   340  			}
   341  
   342  			// we benchmark performance over the next 100 blocks
   343  			if err := Benchmark(ctx, logger, cli.testnet, 100); err != nil {
   344  				return err
   345  			}
   346  
   347  			loadCancel()
   348  			if err := <-chLoadResult; err != nil {
   349  				return err
   350  			}
   351  
   352  			return nil
   353  		},
   354  	})
   355  
   356  	return cli
   357  }
   358  
   359  // Run runs the CLI.
   360  func (cli *CLI) Run(ctx context.Context, logger log.Logger) {
   361  	if err := cli.root.ExecuteContext(ctx); err != nil {
   362  		logger.Error(err.Error())
   363  		os.Exit(1)
   364  	}
   365  }