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