github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/test/e2e/runner/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 9 "github.com/spf13/cobra" 10 11 "github.com/badrootd/celestia-core/libs/log" 12 "github.com/badrootd/celestia-core/pkg/trace" 13 e2e "github.com/badrootd/celestia-core/test/e2e/pkg" 14 "github.com/badrootd/celestia-core/test/e2e/pkg/infra" 15 "github.com/badrootd/celestia-core/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 iurl, err := cmd.Flags().GetString(trace.FlagInfluxDBURL) 82 if err != nil { 83 return err 84 } 85 itoken, err := cmd.Flags().GetString(trace.FlagInfluxDBToken) 86 if err != nil { 87 return err 88 } 89 if ifd.InfluxDBURL == "" { 90 ifd.InfluxDBURL = iurl 91 ifd.InfluxDBToken = itoken 92 } 93 94 purl, err := cmd.Flags().GetString(trace.FlagPyroscopeURL) 95 if err != nil { 96 return err 97 } 98 pTrace, err := cmd.Flags().GetBool(trace.FlagPyroscopeTrace) 99 if err != nil { 100 return err 101 } 102 if ifd.PyroscopeURL == "" { 103 ifd.PyroscopeURL = purl 104 ifd.PyroscopeTrace = pTrace 105 } 106 107 testnet, err := e2e.LoadTestnet(m, file, ifd) 108 if err != nil { 109 return fmt.Errorf("loading testnet: %s", err) 110 } 111 112 cli.testnet = testnet 113 cli.infp = &infra.NoopProvider{} 114 if inft == "docker" { 115 cli.infp = &docker.Provider{Testnet: testnet} 116 } 117 return nil 118 }, 119 RunE: func(cmd *cobra.Command, args []string) error { 120 if err := Cleanup(cli.testnet); err != nil { 121 return err 122 } 123 if err := Setup(cli.testnet, cli.infp); err != nil { 124 return err 125 } 126 127 chLoadResult := make(chan error) 128 ctx, loadCancel := context.WithCancel(context.Background()) 129 defer loadCancel() 130 go func() { 131 err := Load(ctx, cli.testnet) 132 if err != nil { 133 logger.Error(fmt.Sprintf("Transaction load failed: %v", err.Error())) 134 } 135 chLoadResult <- err 136 }() 137 138 if err := Start(cli.testnet); err != nil { 139 return err 140 } 141 142 if lastMisbehavior := cli.testnet.LastMisbehaviorHeight(); lastMisbehavior > 0 { 143 // wait for misbehaviors before starting perturbations. We do a separate 144 // wait for another 5 blocks, since the last misbehavior height may be 145 // in the past depending on network startup ordering. 146 if err := WaitUntil(cli.testnet, lastMisbehavior); err != nil { 147 return err 148 } 149 } 150 if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through 151 return err 152 } 153 154 if cli.testnet.HasPerturbations() { 155 if err := Perturb(cli.testnet); err != nil { 156 return err 157 } 158 if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through 159 return err 160 } 161 } 162 163 loadCancel() 164 if err := <-chLoadResult; err != nil { 165 return err 166 } 167 if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle before tests 168 return err 169 } 170 if err := Test(cli.testnet); err != nil { 171 return err 172 } 173 if !cli.preserve { 174 if err := Cleanup(cli.testnet); err != nil { 175 return err 176 } 177 } 178 return nil 179 }, 180 } 181 182 cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest") 183 _ = cli.root.MarkPersistentFlagRequired("file") 184 185 cli.root.PersistentFlags().StringP("infrastructure-type", "", "docker", "Backing infrastructure used to run the testnet. Either 'digital-ocean' or 'docker'") 186 187 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'") 188 189 cli.root.PersistentFlags().String(trace.FlagInfluxDBURL, "", trace.FlagInfluxDBURLDescription) 190 191 cli.root.PersistentFlags().String(trace.FlagInfluxDBToken, "", trace.FlagInfluxDBTokenDescription) 192 193 cli.root.PersistentFlags().String(trace.FlagPyroscopeURL, "", trace.FlagPyroscopeURLDescription) 194 195 cli.root.PersistentFlags().Bool(trace.FlagPyroscopeTrace, false, trace.FlagPyroscopeTraceDescription) 196 197 cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false, 198 "Preserves the running of the test net after tests are completed") 199 200 cli.root.AddCommand(&cobra.Command{ 201 Use: "setup", 202 Short: "Generates the testnet directory and configuration", 203 RunE: func(cmd *cobra.Command, args []string) error { 204 return Setup(cli.testnet, cli.infp) 205 }, 206 }) 207 208 cli.root.AddCommand(&cobra.Command{ 209 Use: "start", 210 Short: "Starts the Docker testnet, waiting for nodes to become available", 211 RunE: func(cmd *cobra.Command, args []string) error { 212 _, err := os.Stat(cli.testnet.Dir) 213 if os.IsNotExist(err) { 214 err = Setup(cli.testnet, cli.infp) 215 } 216 if err != nil { 217 return err 218 } 219 return Start(cli.testnet) 220 }, 221 }) 222 223 cli.root.AddCommand(&cobra.Command{ 224 Use: "perturb", 225 Short: "Perturbs the Docker testnet, e.g. by restarting or disconnecting nodes", 226 RunE: func(cmd *cobra.Command, args []string) error { 227 return Perturb(cli.testnet) 228 }, 229 }) 230 231 cli.root.AddCommand(&cobra.Command{ 232 Use: "wait", 233 Short: "Waits for a few blocks to be produced and all nodes to catch up", 234 RunE: func(cmd *cobra.Command, args []string) error { 235 return Wait(cli.testnet, 5) 236 }, 237 }) 238 239 cli.root.AddCommand(&cobra.Command{ 240 Use: "stop", 241 Short: "Stops the Docker testnet", 242 RunE: func(cmd *cobra.Command, args []string) error { 243 logger.Info("Stopping testnet") 244 return execCompose(cli.testnet.Dir, "down") 245 }, 246 }) 247 248 cli.root.AddCommand(&cobra.Command{ 249 Use: "load", 250 Short: "Generates transaction load until the command is canceled", 251 RunE: func(cmd *cobra.Command, args []string) (err error) { 252 return Load(context.Background(), cli.testnet) 253 }, 254 }) 255 256 cli.root.AddCommand(&cobra.Command{ 257 Use: "test", 258 Short: "Runs test cases against a running testnet", 259 RunE: func(cmd *cobra.Command, args []string) error { 260 return Test(cli.testnet) 261 }, 262 }) 263 264 cli.root.AddCommand(&cobra.Command{ 265 Use: "cleanup", 266 Short: "Removes the testnet directory", 267 RunE: func(cmd *cobra.Command, args []string) error { 268 return Cleanup(cli.testnet) 269 }, 270 }) 271 272 cli.root.AddCommand(&cobra.Command{ 273 Use: "logs", 274 Short: "Shows the testnet logs", 275 RunE: func(cmd *cobra.Command, args []string) error { 276 return execComposeVerbose(cli.testnet.Dir, "logs") 277 }, 278 }) 279 280 cli.root.AddCommand(&cobra.Command{ 281 Use: "tail", 282 Short: "Tails the testnet logs", 283 RunE: func(cmd *cobra.Command, args []string) error { 284 return execComposeVerbose(cli.testnet.Dir, "logs", "--follow") 285 }, 286 }) 287 288 cli.root.AddCommand(&cobra.Command{ 289 Use: "benchmark", 290 Short: "Benchmarks testnet", 291 Long: `Benchmarks the following metrics: 292 Mean Block Interval 293 Standard Deviation 294 Min Block Interval 295 Max Block Interval 296 over a 100 block sampling period. 297 298 Does not run any perturbations. 299 `, 300 RunE: func(cmd *cobra.Command, args []string) error { 301 if err := Cleanup(cli.testnet); err != nil { 302 return err 303 } 304 if err := Setup(cli.testnet, cli.infp); err != nil { 305 return err 306 } 307 308 chLoadResult := make(chan error) 309 ctx, loadCancel := context.WithCancel(context.Background()) 310 defer loadCancel() 311 go func() { 312 err := Load(ctx, cli.testnet) 313 if err != nil { 314 logger.Error(fmt.Sprintf("Transaction load errored: %v", err.Error())) 315 } 316 chLoadResult <- err 317 }() 318 319 if err := Start(cli.testnet); err != nil { 320 return err 321 } 322 323 if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through 324 return err 325 } 326 327 // we benchmark performance over the next 100 blocks 328 if err := Benchmark(cli.testnet, 100); err != nil { 329 return err 330 } 331 332 loadCancel() 333 if err := <-chLoadResult; err != nil { 334 return err 335 } 336 337 return Cleanup(cli.testnet) 338 }, 339 }) 340 341 return cli 342 } 343 344 // Run runs the CLI. 345 func (cli *CLI) Run() { 346 if err := cli.root.Execute(); err != nil { 347 logger.Error(err.Error()) 348 os.Exit(1) 349 } 350 }