From 3ff56c22cda4504832d4fe57fd6357e8743965ed Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 30 Aug 2022 05:01:33 -0400 Subject: [PATCH] adds start as default subcommand for zebrad (#4957) * adds start as default subcommand for zebrad * moves EntryPoint to submodule and adds a test * moves all start tests to config_test to avoid listener conflicts * Update zebrad/src/application/entry_point.rs docs * Revert "moves all start tests to config_test to avoid listener conflicts" This reverts commit 61ce46f5a13907facc3a11326e7a328d81b2be3d. * Update based on test API changes from another PR Co-authored-by: teor Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zebrad/Cargo.toml | 2 +- zebrad/src/application.rs | 7 +- zebrad/src/application/entry_point.rs | 104 ++++++++++++++++++++++++++ zebrad/src/commands.rs | 4 +- zebrad/src/commands/start.rs | 2 +- zebrad/tests/acceptance.rs | 46 ++++++++---- 6 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 zebrad/src/application/entry_point.rs diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 1e3f72cf5e0..5fc2ccb8fba 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -76,7 +76,7 @@ zebra-rpc = { path = "../zebra-rpc" } zebra-state = { path = "../zebra-state" } abscissa_core = "0.5" -gumdrop = "0.7" +gumdrop = { version = "0.7", features = ["default_expr"]} chrono = { version = "0.4.20", default-features = false, features = ["clock", "std"] } humantime = "2.1.0" humantime-serde = "1.1.1" diff --git a/zebrad/src/application.rs b/zebrad/src/application.rs index a3e788449ef..6ebd51c987b 100644 --- a/zebrad/src/application.rs +++ b/zebrad/src/application.rs @@ -1,5 +1,8 @@ //! Zebrad Abscissa Application +mod entry_point; +use self::entry_point::EntryPoint; + use std::{fmt::Write as _, io::Write as _, process}; use abscissa_core::{ @@ -7,7 +10,7 @@ use abscissa_core::{ config::{self, Configurable}, status_err, terminal::{component::Terminal, stderr, stdout, ColorChoice}, - Application, Component, EntryPoint, FrameworkError, Shutdown, StandardPaths, Version, + Application, Component, FrameworkError, Shutdown, StandardPaths, Version, }; use zebra_network::constants::PORT_IN_USE_ERROR; @@ -138,7 +141,7 @@ impl Default for ZebradApp { impl Application for ZebradApp { /// Entrypoint command for this application. - type Cmd = EntryPoint; + type Cmd = EntryPoint; /// Application configuration. type Cfg = ZebradConfig; diff --git a/zebrad/src/application/entry_point.rs b/zebrad/src/application/entry_point.rs new file mode 100644 index 00000000000..bbcff6cb8e7 --- /dev/null +++ b/zebrad/src/application/entry_point.rs @@ -0,0 +1,104 @@ +//! Zebrad EntryPoint + +use crate::{ + commands::{StartCmd, ZebradCmd}, + config::ZebradConfig, +}; + +use std::path::PathBuf; + +use abscissa_core::{ + command::{Command, Usage}, + config::Configurable, + FrameworkError, Options, Runnable, +}; + +// (See https://docs.rs/abscissa_core/0.5.2/src/abscissa_core/command/entrypoint.rs.html) +/// Toplevel entrypoint command. +/// +/// Handles obtaining toplevel help as well as verbosity settings. +#[derive(Debug, Options)] +pub struct EntryPoint { + /// Path to the configuration file + #[options(short = "c", help = "path to configuration file")] + pub config: Option, + + /// Obtain help about the current command + #[options(short = "h", help = "print help message")] + pub help: bool, + + /// Increase verbosity setting + #[options(short = "v", help = "be verbose")] + pub verbose: bool, + + /// Subcommand to execute. + /// + /// The `command` option will delegate option parsing to the command type, + /// starting at the first free argument. Defaults to start. + #[options(command, default_expr = "Some(ZebradCmd::Start(StartCmd::default()))")] + pub command: Option, +} + +impl EntryPoint { + /// Borrow the underlying command type + fn command(&self) -> &ZebradCmd { + self.command + .as_ref() + .expect("Some(ZebradCmd::Start(StartCmd::default()) as default value") + } +} + +impl Runnable for EntryPoint { + fn run(&self) { + self.command().run() + } +} + +impl Command for EntryPoint { + /// Name of this program as a string + fn name() -> &'static str { + ZebradCmd::name() + } + + /// Description of this program + fn description() -> &'static str { + ZebradCmd::description() + } + + /// Version of this program + fn version() -> &'static str { + ZebradCmd::version() + } + + /// Authors of this program + fn authors() -> &'static str { + ZebradCmd::authors() + } + + /// Get usage information for a particular subcommand (if available) + fn subcommand_usage(command: &str) -> Option { + ZebradCmd::subcommand_usage(command) + } +} + +impl Configurable for EntryPoint { + /// Path to the command's configuration file + fn config_path(&self) -> Option { + match &self.config { + // Use explicit `-c`/`--config` argument if passed + Some(cfg) => Some(cfg.clone()), + + // Otherwise defer to the toplevel command's config path logic + None => self.command.as_ref().and_then(|cmd| cmd.config_path()), + } + } + + /// Process the configuration after it has been loaded, potentially + /// modifying it or returning an error if options are incompatible + fn process_config(&self, config: ZebradConfig) -> Result { + match &self.command { + Some(cmd) => cmd.process_config(config), + None => Ok(config), + } + } +} diff --git a/zebrad/src/commands.rs b/zebrad/src/commands.rs index 9dcc2745b5b..c53d177d2b9 100644 --- a/zebrad/src/commands.rs +++ b/zebrad/src/commands.rs @@ -9,10 +9,12 @@ mod version; use self::ZebradCmd::*; use self::{ - copy_state::CopyStateCmd, download::DownloadCmd, generate::GenerateCmd, start::StartCmd, + copy_state::CopyStateCmd, download::DownloadCmd, generate::GenerateCmd, tip_height::TipHeightCmd, version::VersionCmd, }; +pub use self::start::StartCmd; + use crate::config::ZebradConfig; use abscissa_core::{ diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 9c792e340d2..b22a82df7ce 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -90,7 +90,7 @@ use crate::{ }; /// `start` subcommand -#[derive(Command, Debug, Options)] +#[derive(Command, Debug, Options, Default)] pub struct StartCmd { /// Filter strings which override the config file and defaults #[options(free, help = "tracing filters which override the zebrad.toml config")] diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 13718fd00d7..79249ea1db9 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -446,21 +446,6 @@ fn ephemeral(cache_dir_config: EphemeralConfig, cache_dir_check: EphemeralCheck) Ok(()) } -#[test] -fn app_no_args() -> Result<()> { - let _init_guard = zebra_test::init(); - - let testdir = testdir()?.with_config(&mut default_test_config()?)?; - - let child = testdir.spawn_child(args![])?; - let output = child.wait_with_output()?; - let output = output.assert_success()?; - - output.stdout_line_contains("USAGE:")?; - - Ok(()) -} - #[test] fn version_no_args() -> Result<()> { let _init_guard = zebra_test::init(); @@ -517,6 +502,37 @@ fn config_test() -> Result<()> { // Check that an older stored configuration we have for Zebra works stored_config_works()?; + // Runs `zebrad` serially to avoid potential port conflicts + app_no_args()?; + + Ok(()) +} + +/// Test that `zebrad` runs the start command with no args +fn app_no_args() -> Result<()> { + let _init_guard = zebra_test::init(); + + // start caches state, so run one of the start tests with persistent state + let testdir = testdir()?.with_config(&mut persistent_test_config()?)?; + + let mut child = testdir.spawn_child(args![])?; + + // Run the program and kill it after a few seconds + std::thread::sleep(LAUNCH_DELAY); + child.kill(true)?; + + let output = child.wait_with_output()?; + let output = output.assert_failure()?; + + output.stdout_line_contains("Starting zebrad")?; + + // Make sure the command passed the legacy chain check + output.stdout_line_contains("starting legacy chain check")?; + output.stdout_line_contains("no legacy chain found")?; + + // Make sure the command was killed + output.assert_was_killed()?; + Ok(()) }