github.com/vipernet-xyz/tm@v0.34.24/test/e2e/runner/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  
    10  	"github.com/spf13/cobra"
    11  
    12  	"github.com/vipernet-xyz/tm/libs/log"
    13  	e2e "github.com/vipernet-xyz/tm/test/e2e/pkg"
    14  	"github.com/vipernet-xyz/tm/test/e2e/pkg/infra"
    15  	"github.com/vipernet-xyz/tm/test/e2e/pkg/infra/docker"
    16  )
    17  
    18  var (
    19  	logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
    20  )
    21  
    22  func main() {
    23  	NewCLI().Run()
    24  }
    25  
    26  // CLI is the Cobra-based command-line interface.
    27  type CLI struct {
    28  	root     *cobra.Command
    29  	testnet  *e2e.Testnet
    30  	preserve bool
    31  	infp     infra.Provider
    32  }
    33  
    34  // NewCLI sets up the CLI.
    35  func NewCLI() *CLI {
    36  	cli := &CLI{}
    37  	cli.root = &cobra.Command{
    38  		Use:           "runner",
    39  		Short:         "End-to-end test runner",
    40  		SilenceUsage:  true,
    41  		SilenceErrors: true, // we'll output them ourselves in Run()
    42  		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
    43  			file, err := cmd.Flags().GetString("file")
    44  			if err != nil {
    45  				return err
    46  			}
    47  			m, err := e2e.LoadManifest(file)
    48  			if err != nil {
    49  				return err
    50  			}
    51  
    52  			inft, err := cmd.Flags().GetString("infrastructure-type")
    53  			if err != nil {
    54  				return err
    55  			}
    56  
    57  			var ifd e2e.InfrastructureData
    58  			switch inft {
    59  			case "docker":
    60  				var err error
    61  				ifd, err = e2e.NewDockerInfrastructureData(m)
    62  				if err != nil {
    63  					return err
    64  				}
    65  			case "digital-ocean":
    66  				p, err := cmd.Flags().GetString("infrastructure-data")
    67  				if err != nil {
    68  					return err
    69  				}
    70  				if p == "" {
    71  					return errors.New("'--infrastructure-data' must be set when using the 'digital-ocean' infrastructure-type")
    72  				}
    73  				ifd, err = e2e.InfrastructureDataFromFile(p)
    74  				if err != nil {
    75  					return fmt.Errorf("parsing infrastructure data: %s", err)
    76  				}
    77  			default:
    78  				return fmt.Errorf("unknown infrastructure type '%s'", inft)
    79  			}
    80  
    81  			testnet, err := e2e.LoadTestnet(m, file, ifd)
    82  			if err != nil {
    83  				return fmt.Errorf("loading testnet: %s", err)
    84  			}
    85  
    86  			cli.testnet = testnet
    87  			cli.infp = &infra.NoopProvider{}
    88  			if inft == "docker" {
    89  				cli.infp = &docker.Provider{Testnet: testnet}
    90  			}
    91  			return nil
    92  		},
    93  		RunE: func(cmd *cobra.Command, args []string) error {
    94  			if err := Cleanup(cli.testnet); err != nil {
    95  				return err
    96  			}
    97  			if err := Setup(cli.testnet, cli.infp); err != nil {
    98  				return err
    99  			}
   100  
   101  			chLoadResult := make(chan error)
   102  			ctx, loadCancel := context.WithCancel(context.Background())
   103  			defer loadCancel()
   104  			go func() {
   105  				err := Load(ctx, cli.testnet, 1)
   106  				if err != nil {
   107  					logger.Error(fmt.Sprintf("Transaction load failed: %v", err.Error()))
   108  				}
   109  				chLoadResult <- err
   110  			}()
   111  
   112  			if err := Start(cli.testnet); err != nil {
   113  				return err
   114  			}
   115  
   116  			if lastMisbehavior := cli.testnet.LastMisbehaviorHeight(); lastMisbehavior > 0 {
   117  				// wait for misbehaviors before starting perturbations. We do a separate
   118  				// wait for another 5 blocks, since the last misbehavior height may be
   119  				// in the past depending on network startup ordering.
   120  				if err := WaitUntil(cli.testnet, lastMisbehavior); err != nil {
   121  					return err
   122  				}
   123  			}
   124  			if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through
   125  				return err
   126  			}
   127  
   128  			if cli.testnet.HasPerturbations() {
   129  				if err := Perturb(cli.testnet); err != nil {
   130  					return err
   131  				}
   132  				if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through
   133  					return err
   134  				}
   135  			}
   136  
   137  			loadCancel()
   138  			if err := <-chLoadResult; err != nil {
   139  				return err
   140  			}
   141  			if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle before tests
   142  				return err
   143  			}
   144  			if err := Test(cli.testnet); err != nil {
   145  				return err
   146  			}
   147  			if !cli.preserve {
   148  				if err := Cleanup(cli.testnet); err != nil {
   149  					return err
   150  				}
   151  			}
   152  			return nil
   153  		},
   154  	}
   155  
   156  	cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest")
   157  	_ = cli.root.MarkPersistentFlagRequired("file")
   158  
   159  	cli.root.PersistentFlags().StringP("infrastructure-type", "", "docker", "Backing infrastructure used to run the testnet. Either 'digital-ocean' or 'docker'")
   160  
   161  	cli.root.PersistentFlags().StringP("infrastructure-data", "", "", "path to the json file containing the infrastructure data. Only used if the 'infrastructure-type' is set to a value other than 'docker'")
   162  
   163  	cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false,
   164  		"Preserves the running of the test net after tests are completed")
   165  
   166  	cli.root.AddCommand(&cobra.Command{
   167  		Use:   "setup",
   168  		Short: "Generates the testnet directory and configuration",
   169  		RunE: func(cmd *cobra.Command, args []string) error {
   170  			return Setup(cli.testnet, cli.infp)
   171  		},
   172  	})
   173  
   174  	cli.root.AddCommand(&cobra.Command{
   175  		Use:   "start",
   176  		Short: "Starts the Docker testnet, waiting for nodes to become available",
   177  		RunE: func(cmd *cobra.Command, args []string) error {
   178  			_, err := os.Stat(cli.testnet.Dir)
   179  			if os.IsNotExist(err) {
   180  				err = Setup(cli.testnet, cli.infp)
   181  			}
   182  			if err != nil {
   183  				return err
   184  			}
   185  			return Start(cli.testnet)
   186  		},
   187  	})
   188  
   189  	cli.root.AddCommand(&cobra.Command{
   190  		Use:   "perturb",
   191  		Short: "Perturbs the Docker testnet, e.g. by restarting or disconnecting nodes",
   192  		RunE: func(cmd *cobra.Command, args []string) error {
   193  			return Perturb(cli.testnet)
   194  		},
   195  	})
   196  
   197  	cli.root.AddCommand(&cobra.Command{
   198  		Use:   "wait",
   199  		Short: "Waits for a few blocks to be produced and all nodes to catch up",
   200  		RunE: func(cmd *cobra.Command, args []string) error {
   201  			return Wait(cli.testnet, 5)
   202  		},
   203  	})
   204  
   205  	cli.root.AddCommand(&cobra.Command{
   206  		Use:   "stop",
   207  		Short: "Stops the Docker testnet",
   208  		RunE: func(cmd *cobra.Command, args []string) error {
   209  			logger.Info("Stopping testnet")
   210  			return execCompose(cli.testnet.Dir, "down")
   211  		},
   212  	})
   213  
   214  	cli.root.AddCommand(&cobra.Command{
   215  		Use:   "load [multiplier]",
   216  		Args:  cobra.MaximumNArgs(1),
   217  		Short: "Generates transaction load until the command is canceled",
   218  		RunE: func(cmd *cobra.Command, args []string) (err error) {
   219  			m := 1
   220  
   221  			if len(args) == 1 {
   222  				m, err = strconv.Atoi(args[0])
   223  				if err != nil {
   224  					return err
   225  				}
   226  			}
   227  
   228  			return Load(context.Background(), cli.testnet, m)
   229  		},
   230  	})
   231  
   232  	cli.root.AddCommand(&cobra.Command{
   233  		Use:   "test",
   234  		Short: "Runs test cases against a running testnet",
   235  		RunE: func(cmd *cobra.Command, args []string) error {
   236  			return Test(cli.testnet)
   237  		},
   238  	})
   239  
   240  	cli.root.AddCommand(&cobra.Command{
   241  		Use:   "cleanup",
   242  		Short: "Removes the testnet directory",
   243  		RunE: func(cmd *cobra.Command, args []string) error {
   244  			return Cleanup(cli.testnet)
   245  		},
   246  	})
   247  
   248  	cli.root.AddCommand(&cobra.Command{
   249  		Use:   "logs",
   250  		Short: "Shows the testnet logs",
   251  		RunE: func(cmd *cobra.Command, args []string) error {
   252  			return execComposeVerbose(cli.testnet.Dir, "logs")
   253  		},
   254  	})
   255  
   256  	cli.root.AddCommand(&cobra.Command{
   257  		Use:   "tail",
   258  		Short: "Tails the testnet logs",
   259  		RunE: func(cmd *cobra.Command, args []string) error {
   260  			return execComposeVerbose(cli.testnet.Dir, "logs", "--follow")
   261  		},
   262  	})
   263  
   264  	cli.root.AddCommand(&cobra.Command{
   265  		Use:   "benchmark",
   266  		Short: "Benchmarks testnet",
   267  		Long: `Benchmarks the following metrics:
   268  	Mean Block Interval
   269  	Standard Deviation
   270  	Min Block Interval
   271  	Max Block Interval
   272  over a 100 block sampling period.
   273  		
   274  Does not run any perbutations.
   275  		`,
   276  		RunE: func(cmd *cobra.Command, args []string) error {
   277  			if err := Cleanup(cli.testnet); err != nil {
   278  				return err
   279  			}
   280  			if err := Setup(cli.testnet, cli.infp); err != nil {
   281  				return err
   282  			}
   283  
   284  			chLoadResult := make(chan error)
   285  			ctx, loadCancel := context.WithCancel(context.Background())
   286  			defer loadCancel()
   287  			go func() {
   288  				err := Load(ctx, cli.testnet, 1)
   289  				if err != nil {
   290  					logger.Error(fmt.Sprintf("Transaction load failed: %v", err.Error()))
   291  				}
   292  				chLoadResult <- err
   293  			}()
   294  
   295  			if err := Start(cli.testnet); err != nil {
   296  				return err
   297  			}
   298  
   299  			if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through
   300  				return err
   301  			}
   302  
   303  			// we benchmark performance over the next 100 blocks
   304  			if err := Benchmark(cli.testnet, 100); err != nil {
   305  				return err
   306  			}
   307  
   308  			loadCancel()
   309  			if err := <-chLoadResult; err != nil {
   310  				return err
   311  			}
   312  
   313  			if err := Cleanup(cli.testnet); err != nil {
   314  				return err
   315  			}
   316  
   317  			return nil
   318  		},
   319  	})
   320  
   321  	return cli
   322  }
   323  
   324  // Run runs the CLI.
   325  func (cli *CLI) Run() {
   326  	if err := cli.root.Execute(); err != nil {
   327  		logger.Error(err.Error())
   328  		os.Exit(1)
   329  	}
   330  }