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 }