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