From 60a90c279ec83e08af1a7d030ebad8b47d25bc02 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Sun, 4 Feb 2024 16:43:07 -0800 Subject: [PATCH] Add --features etc Fixes `--features`, `--no-default-features` options #218 --- Cargo.lock | 8 ++--- NEWS.md | 2 ++ book/src/cargo-args.md | 10 +++++++ src/cargo.rs | 55 +++++++++++++++++++++++++++++++++++ src/main.rs | 25 +++++++++++++++- src/options.rs | 66 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 470a504e..32f83eb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.17" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -269,9 +269,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.17" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", diff --git a/NEWS.md b/NEWS.md index c6108539..97951849 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ ## Unreleased +- New: `--features`, `--no-default-features` and `--all-features` options are passed through to Cargo. + ## 24.2.0 - New: Colored output can be enabled in CI or other noninteractive situations by passing `--colors=always`, or setting `CARGO_TERM_COLOR=always`, or `CLICOLOR_FORCE=1`. Colors can similarly be forced off with `--colors=never`, `CARGO_TERM_COLOR=never`, or `NO_COLOR=1`. diff --git a/book/src/cargo-args.md b/book/src/cargo-args.md index cfa04746..57089750 100644 --- a/book/src/cargo-args.md +++ b/book/src/cargo-args.md @@ -7,6 +7,16 @@ test`. There is not yet a way to pass options only to `cargo build` but not to `cargo test`. +## Feature flags + +The `--features`, `--all-features`, and `--no-default-features` flags can be given to cargo-mutants and they will be passed down to `cargo build` and `cargo test`. + +For example, this can be useful if you have tests that are only enabled with a feature flag: + +```shell +cargo mutants -- --features=fail/failpoints +``` + ## Arguments to all `cargo` commands To pass more arguments to every Cargo invocation, use `--cargo-arg`, or the `additional_cargo_args` configuration key. diff --git a/src/cargo.rs b/src/cargo.rs index fae22e06..299320e6 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -90,6 +90,19 @@ fn cargo_argv( } else { cargo_args.push("--workspace".to_string()); } + let features = &options.features; + if features.no_default_features { + cargo_args.push("--no-default-features".to_owned()); + } + if features.all_features { + cargo_args.push("--all-features".to_owned()); + } + cargo_args.extend( + features + .features + .iter() + .map(|f| format!("--features={}", f)), + ); cargo_args.extend(options.additional_cargo_args.iter().cloned()); if phase == Phase::Test { cargo_args.extend(options.additional_cargo_test_args.iter().cloned()); @@ -232,4 +245,46 @@ mod test { ] ); } + + #[test] + fn no_default_features_args_passed_to_cargo() { + let args = Args::try_parse_from(["mutants", "--no-default-features"].as_slice()).unwrap(); + let options = Options::from_args(&args).unwrap(); + let build_dir = Utf8Path::new("/tmp/buildXYZ"); + assert_eq!( + cargo_argv(build_dir, None, Phase::Check, &options)[1..], + ["check", "--tests", "--workspace", "--no-default-features"] + ); + } + + #[test] + fn all_features_args_passed_to_cargo() { + let args = Args::try_parse_from(["mutants", "--all-features"].as_slice()).unwrap(); + let options = Options::from_args(&args).unwrap(); + let build_dir = Utf8Path::new("/tmp/buildXYZ"); + assert_eq!( + cargo_argv(build_dir, None, Phase::Check, &options)[1..], + ["check", "--tests", "--workspace", "--all-features"] + ); + } + + #[test] + fn feature_args_passed_to_cargo() { + let args = Args::try_parse_from( + ["mutants", "--features", "foo", "--features", "bar,baz"].as_slice(), + ) + .unwrap(); + let options = Options::from_args(&args).unwrap(); + let build_dir = Utf8Path::new("/tmp/buildXYZ"); + assert_eq!( + cargo_argv(build_dir, None, Phase::Check, &options)[1..], + [ + "check", + "--tests", + "--workspace", + "--features=foo", + "--features=bar,baz" + ] + ); + } } diff --git a/src/main.rs b/src/main.rs index ac6ee505..68ffdc6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,7 +103,7 @@ pub enum BaselineStrategy { about, after_help = SPONSOR_MESSAGE, )] -struct Args { +pub struct Args { /// show cargo output for all invocations (very verbose). #[arg(long, help_heading = "Output")] all_logs: bool, @@ -311,6 +311,28 @@ struct Args { /// pass remaining arguments to cargo test after all options and after `--`. #[arg(last = true, help_heading = "Execution")] cargo_test_args: Vec, + + #[command(flatten)] + features: Features, +} + +#[derive(clap::Args, PartialEq, Eq, Debug, Default, Clone)] +pub struct Features { + //--- features + /// Space or comma separated list of features to activate. + // (The features are not split or parsed, just passed through to Cargo.) + #[arg(long, help_heading = "Feature Selection")] + pub features: Vec, + + /// Do not activate the `default` feature. + #[arg(long, help_heading = "Feature Selection")] + pub no_default_features: bool, + + /// Activate all features. + // (This does not conflict because this only turns on features in the top level package, + // and you might use --features to turn on features in dependencies.) + #[arg(long, help_heading = "Feature Selection")] + pub all_features: bool, } fn main() -> Result<()> { @@ -358,6 +380,7 @@ fn main() -> Result<()> { config::Config::read_tree_config(&workspace.dir)? }; debug!(?config); + debug!(?args.features); let options = Options::new(&args, &config)?; debug!(?options); let package_filter = if !args.mutate_packages.is_empty() { diff --git a/src/options.rs b/src/options.rs index d24a8939..e8ec5d05 100644 --- a/src/options.rs +++ b/src/options.rs @@ -70,6 +70,9 @@ pub struct Options { /// Additional arguments to `cargo test`. pub additional_cargo_test_args: Vec, + /// Selection of features for cargo. + pub features: super::Features, + /// Files to examine. pub examine_globset: Option, @@ -194,6 +197,7 @@ impl Options { .context("Failed to compile exclude_re regex")?, examine_globset: build_glob_set(or_slices(&args.file, &config.examine_globs))?, exclude_globset: build_glob_set(or_slices(&args.exclude, &config.exclude_globs))?, + features: args.features.clone(), gitignore: args.gitignore, in_place: args.in_place, jobs: args.jobs, @@ -219,6 +223,11 @@ impl Options { }); Ok(options) } + + #[cfg(test)] + pub fn from_args(args: &Args) -> Result { + Options::new(args, &Config::default()) + } } /// If the first slices is non-empty, return that, otherwise the second. @@ -301,4 +310,61 @@ mod test { let options = Options::new(&args, &config).unwrap(); assert_eq!(options.test_tool, TestTool::Nextest); } + + #[test] + fn features_arg() { + let args = Args::try_parse_from(["mutants", "--features", "nice,shiny features"]).unwrap(); + assert_eq!( + args.features.features.iter().as_ref(), + ["nice,shiny features"] + ); + assert!(!args.features.no_default_features); + assert!(!args.features.all_features); + + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!( + options.features.features.iter().as_ref(), + ["nice,shiny features"] + ); + assert!(!options.features.no_default_features); + assert!(!options.features.all_features); + } + + #[test] + fn no_default_features_arg() { + let args = Args::try_parse_from([ + "mutants", + "--no-default-features", + "--features", + "nice,shiny features", + ]) + .unwrap(); + + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!( + options.features.features.iter().as_ref(), + ["nice,shiny features"] + ); + assert!(options.features.no_default_features); + assert!(!options.features.all_features); + } + + #[test] + fn all_features_arg() { + let args = Args::try_parse_from([ + "mutants", + "--all-features", + "--features", + "nice,shiny features", + ]) + .unwrap(); + + let options = Options::new(&args, &Config::default()).unwrap(); + assert_eq!( + options.features.features.iter().as_ref(), + ["nice,shiny features"] + ); + assert!(!options.features.no_default_features); + assert!(options.features.all_features); + } }