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 }