diff --git a/.github/.cspell/rust-dependencies.txt b/.github/.cspell/rust-dependencies.txt index 2edd1b7b..cf8ed84d 100644 --- a/.github/.cspell/rust-dependencies.txt +++ b/.github/.cspell/rust-dependencies.txt @@ -3,5 +3,6 @@ argfile easytime +fastrand lexopt termcolor diff --git a/Cargo.toml b/Cargo.toml index 4805916c..986dab3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ pkg-fmt = "tgz" anyhow = "1.0.47" cargo-config2 = "0.1.13" ctrlc = { version = "3.4.4", features = ["termination"] } +fastrand = "2.3.0" lexopt = "0.3" same-file = "1.0.1" serde_json = "1" diff --git a/README.md b/README.md index bc628141..10291054 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,13 @@ OPTIONS: --include-features and there are multiple features, this also includes runs with just --all-features flag. + --randomize-powerset + Randomize order of powerset elements. + + Run feature powerset in random order with the specified seed. + + Zero seed value means unseeded. + --optional-deps [DEPS]... Use optional dependencies as features. diff --git a/src/cli.rs b/src/cli.rs index 6973a3f8..824b83f2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -101,6 +101,9 @@ pub(crate) struct Args { // propagated to cargo (as a part of leading_args) /// --no-default-features pub(crate) no_default_features: bool, + + /// --randomize-powerset + pub(crate) randomize_powerset: Option, } impl Args { @@ -177,6 +180,8 @@ impl Args { let mut exclude_no_default_features = false; let mut exclude_all_features = false; + let mut randomize_powerset = None; + let mut group_features: Vec = vec![]; let mut mutually_exclusive_features: Vec = vec![]; let mut depth = None; @@ -303,6 +308,7 @@ impl Args { Long("remove-dev-deps") => parse_flag!(remove_dev_deps), Long("each-feature") => parse_flag!(each_feature), Long("feature-powerset") => parse_flag!(feature_powerset), + Long("randomize-powerset") => parse_opt!(randomize_powerset, false), Long("at-least-one-of") => at_least_one_of.push(parser.value()?.parse()?), Long("no-private") => parse_flag!(no_private), Long("ignore-private") => parse_flag!(ignore_private), @@ -487,6 +493,10 @@ impl Args { if each_feature && feature_powerset { conflicts("--each-feature", "--feature-powerset")?; } + + let randomize_powerset = + randomize_powerset.as_deref().map(str::parse::).transpose()?; + if all_features { if each_feature { conflicts("--all-features", "--each-feature")?; @@ -644,6 +654,8 @@ impl Args { no_default_features, target: target.into_iter().collect(), + + randomize_powerset, }) } } @@ -695,6 +707,10 @@ const HELP: &[HelpText<'_>] = &[ --include-features and there are multiple features, this also includes runs with just \ --all-features flag." ]), + ("", "--randomize-powerset", "", "Randomize order of powerset elements", &[ + "Run feature powerset in random order with the specified seed.", + "Zero seed value means unseeded." + ]), ("", "--optional-deps", "[DEPS]...", "Use optional dependencies as features", &[ "If DEPS are not specified, all optional dependencies are considered as features.", "This flag can only be used together with either --each-feature flag or --feature-powerset \ diff --git a/src/features.rs b/src/features.rs index c5e3d1c3..dcf5085f 100644 --- a/src/features.rs +++ b/src/features.rs @@ -210,11 +210,12 @@ pub(crate) fn feature_powerset<'a>( at_least_one_of: &[Feature], mutually_exclusive_features: &[Feature], package_features: &BTreeMap>, + randomize: Option, ) -> Vec> { let deps_map = feature_deps(package_features); let at_least_one_of = at_least_one_of_for_package(at_least_one_of, &deps_map); - powerset(features, depth) + let mut result: Vec> = powerset(features, depth) .into_iter() .skip(1) // The first element of a powerset is `[]` so it should be skipped. .filter(|fs| { @@ -249,7 +250,14 @@ pub(crate) fn feature_powerset<'a>( } true }) - .collect() + .collect(); + if let Some(seed) = randomize { + if seed != 0 { + fastrand::seed(seed); + } + fastrand::shuffle(&mut result); + } + result } fn feature_deps(map: &BTreeMap>) -> BTreeMap<&str, BTreeSet<&str>> { @@ -366,22 +374,22 @@ mod tests { let map = map![("a", v![]), ("b", v!["a"]), ("c", v!["b"]), ("d", v!["a", "b"])]; let list = v!["a", "b", "c", "d"]; - let filtered = feature_powerset(&list, None, &[], &[], &map); + let filtered = feature_powerset(&list, None, &[], &[], &map, None); assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]); - let filtered = feature_powerset(&list, None, &["a".into()], &[], &map); + let filtered = feature_powerset(&list, None, &["a".into()], &[], &map, None); assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]); - let filtered = feature_powerset(&list, None, &["c".into()], &[], &map); + let filtered = feature_powerset(&list, None, &["c".into()], &[], &map, None); assert_eq!(filtered, vec![vec!["c"], vec!["c", "d"]]); - let filtered = feature_powerset(&list, None, &["a".into(), "c".into()], &[], &map); + let filtered = feature_powerset(&list, None, &["a".into(), "c".into()], &[], &map, None); assert_eq!(filtered, vec![vec!["c"], vec!["c", "d"]]); let map = map![("tokio", v![]), ("async-std", v![]), ("a", v![]), ("b", v!["a"])]; let list = v!["a", "b", "tokio", "async-std"]; let mutually_exclusive_features = [Feature::group(["tokio", "async-std"])]; - let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map); + let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None); assert_eq!(filtered, vec![ vec!["a"], vec!["b"], @@ -395,7 +403,7 @@ mod tests { let mutually_exclusive_features = [Feature::group(["tokio", "a"]), Feature::group(["tokio", "async-std"])]; - let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map); + let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None); assert_eq!(filtered, vec![ vec!["a"], vec!["b"], @@ -413,7 +421,7 @@ mod tests { ]; let list = v!["a", "b", "tokio", "async-std"]; let mutually_exclusive_features = [Feature::group(["tokio", "async-std"])]; - let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map); + let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None); assert_eq!(filtered, vec![ vec!["a"], vec!["b"], @@ -427,7 +435,7 @@ mod tests { let map = map![("a", v![]), ("b", v!["a"]), ("c", v![]), ("d", v!["b"])]; let list = v!["a", "b", "c", "d"]; let mutually_exclusive_features = [Feature::group(["a", "c"])]; - let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map); + let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None); assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"]]); } @@ -461,7 +469,7 @@ mod tests { vec!["b", "c", "d"], vec!["a", "b", "c", "d"], ]); - let filtered = feature_powerset(&list, None, &[], &[], &map); + let filtered = feature_powerset(&list, None, &[], &[], &map, None); assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]); } diff --git a/src/main.rs b/src/main.rs index 592c16c0..e9277f6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -289,6 +289,7 @@ fn determine_kind<'a>( &cx.at_least_one_of, &cx.mutually_exclusive_features, &package.features, + cx.randomize_powerset, ); if (pkg_features.normal().is_empty() && pkg_features.optional_deps().is_empty() diff --git a/tests/long-help.txt b/tests/long-help.txt index cdcbacea..1cab7f07 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -47,6 +47,13 @@ OPTIONS: --include-features and there are multiple features, this also includes runs with just --all-features flag. + --randomize-powerset + Randomize order of powerset elements. + + Run feature powerset in random order with the specified seed. + + Zero seed value means unseeded. + --optional-deps [DEPS]... Use optional dependencies as features. diff --git a/tests/short-help.txt b/tests/short-help.txt index 5185a78f..0d2e45f8 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -16,6 +16,7 @@ OPTIONS: -F, --features ... Space or comma separated list of features to activate --each-feature Perform for each feature of the package --feature-powerset Perform for the feature powerset of the package + --randomize-powerset Randomize order of powerset elements --optional-deps [DEPS]... Use optional dependencies as features --skip ... Alias for --exclude-features --exclude-features ... Space or comma separated list of features to exclude