diff --git a/src/cli/args.rs b/src/cli/args.rs index 266432fab..6d040f9cc 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -8,7 +8,7 @@ use clap::{Parser, ValueHint}; /// Supported formats: tar, zip, gz, xz/lzma, bz/bz2, lz4, sz, zst. /// /// Repository: https://github.com/ouch-org/ouch -#[derive(Parser, Debug)] +#[derive(Parser, Debug, PartialEq)] #[command(about, version)] // Disable rustdoc::bare_urls because rustdoc parses URLs differently than Clap #[allow(rustdoc::bare_urls)] @@ -41,7 +41,7 @@ pub struct CliArgs { #[arg(short, long, global = true)] pub format: Option, - /// Ouch and claps subcommands + // Ouch and claps subcommands #[command(subcommand)] pub cmd: Subcommand, } @@ -97,3 +97,155 @@ pub enum Subcommand { tree: bool, }, } + +#[cfg(test)] +mod tests { + use super::*; + + fn args_splitter(input: &str) -> impl Iterator { + input.split_whitespace() + } + + fn to_paths(iter: impl IntoIterator) -> Vec { + iter.into_iter().map(PathBuf::from).collect() + } + + macro_rules! test { + ($args:expr, $expected:expr) => { + let result = match CliArgs::try_parse_from(args_splitter($args)) { + Ok(result) => result, + Err(err) => panic!( + "CLI result is Err, expected Ok, input: '{}'.\nResult: '{err}'", + $args + ), + }; + assert_eq!(result, $expected, "CLI result mismatched, input: '{}'.", $args); + }; + } + + fn mock_cli_args() -> CliArgs { + CliArgs { + yes: false, + no: false, + accessible: false, + hidden: false, + quiet: false, + gitignore: false, + format: None, + // This is usually replaced in assertion tests + cmd: Subcommand::Decompress { + // Put a crazy value here so no test can assert it unintentionally + files: vec!["\x00\x11\x22".into()], + output_dir: None, + }, + } + } + + #[test] + fn test_clap_cli_ok() { + test!( + "ouch decompress file.tar.gz", + CliArgs { + cmd: Subcommand::Decompress { + files: to_paths(["file.tar.gz"]), + output_dir: None, + }, + ..mock_cli_args() + } + ); + test!( + "ouch d file.tar.gz", + CliArgs { + cmd: Subcommand::Decompress { + files: to_paths(["file.tar.gz"]), + output_dir: None, + }, + ..mock_cli_args() + } + ); + test!( + "ouch d a b c", + CliArgs { + cmd: Subcommand::Decompress { + files: to_paths(["a", "b", "c"]), + output_dir: None, + }, + ..mock_cli_args() + } + ); + + test!( + "ouch compress file file.tar.gz", + CliArgs { + cmd: Subcommand::Compress { + files: to_paths(["file"]), + output: PathBuf::from("file.tar.gz"), + level: None, + fast: false, + slow: false, + }, + ..mock_cli_args() + } + ); + test!( + "ouch compress a b c archive.tar.gz", + CliArgs { + cmd: Subcommand::Compress { + files: to_paths(["a", "b", "c"]), + output: PathBuf::from("archive.tar.gz"), + level: None, + fast: false, + slow: false, + }, + ..mock_cli_args() + } + ); + test!( + "ouch compress a b c archive.tar.gz", + CliArgs { + cmd: Subcommand::Compress { + files: to_paths(["a", "b", "c"]), + output: PathBuf::from("archive.tar.gz"), + level: None, + fast: false, + slow: false, + }, + ..mock_cli_args() + } + ); + + let inputs = [ + "ouch compress a b c output --format tar.gz", + // https://github.com/clap-rs/clap/issues/5115 + // "ouch compress a b c --format tar.gz output", + // "ouch compress a b --format tar.gz c output", + // "ouch compress a --format tar.gz b c output", + "ouch compress --format tar.gz a b c output", + "ouch --format tar.gz compress a b c output", + ]; + for input in inputs { + test!( + input, + CliArgs { + cmd: Subcommand::Compress { + files: to_paths(["a", "b", "c"]), + output: PathBuf::from("output"), + level: None, + fast: false, + slow: false, + }, + format: Some("tar.gz".into()), + ..mock_cli_args() + } + ); + } + } + + #[test] + fn test_clap_cli_err() { + assert!(CliArgs::try_parse_from(args_splitter("ouch c")).is_err()); + assert!(CliArgs::try_parse_from(args_splitter("ouch c input")).is_err()); + assert!(CliArgs::try_parse_from(args_splitter("ouch d")).is_err()); + assert!(CliArgs::try_parse_from(args_splitter("ouch l")).is_err()); + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 1fd7f3890..cfcd67d0e 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -20,7 +20,7 @@ impl CliArgs { /// And: /// 1. Make paths absolute. /// 2. Checks the QuestionPolicy. - pub fn parse_args() -> crate::Result<(Self, QuestionPolicy, FileVisibilityPolicy)> { + pub fn parse_and_validate_args() -> crate::Result<(Self, QuestionPolicy, FileVisibilityPolicy)> { let mut args = Self::parse(); set_accessible(args.accessible); diff --git a/src/main.rs b/src/main.rs index 46619bf10..809c4e6d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,6 @@ fn main() { } fn run() -> Result<()> { - let (args, skip_questions_positively, file_visibility_policy) = CliArgs::parse_args()?; + let (args, skip_questions_positively, file_visibility_policy) = CliArgs::parse_and_validate_args()?; commands::run(args, skip_questions_positively, file_visibility_policy) }