From c9a6fb63e196572730a9c1a9fae62c6b28a65d4c Mon Sep 17 00:00:00 2001 From: Vitaly _Vi Shukela Date: Fri, 3 Jan 2025 06:05:18 +0100 Subject: [PATCH 1/7] Implement --randomize-powerset feature Resolves #265. --- Cargo.toml | 1 + README.md | 3 +++ src/cli.rs | 13 +++++++++++++ src/features.rs | 29 ++++++++++++++++++----------- src/main.rs | 1 + tests/long-help.txt | 3 +++ tests/short-help.txt | 2 ++ 7 files changed, 41 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4805916c..808a43ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ anyhow = "1.0.47" cargo-config2 = "0.1.13" ctrlc = { version = "3.4.4", features = ["termination"] } lexopt = "0.3" +rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } same-file = "1.0.1" serde_json = "1" termcolor = "1" diff --git a/README.md b/README.md index bc628141..1dc39483 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,9 @@ OPTIONS: --include-features and there are multiple features, this also includes runs with just --all-features flag. + --randomize-powerset + Run feature powerset in random order with the specified seed. + --optional-deps [DEPS]... Use optional dependencies as features. diff --git a/src/cli.rs b/src/cli.rs index 6973a3f8..d4b4c73a 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,9 @@ 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 +653,8 @@ impl Args { no_default_features, target: target.into_iter().collect(), + + randomize_powerset, }) } } @@ -695,6 +706,8 @@ const HELP: &[HelpText<'_>] = &[ --include-features and there are multiple features, this also includes runs with just \ --all-features flag." ]), + ("", "--randomize-powerset", "", "Run feature powerset in random order with the specified seed", &[ + ]), ("", "--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..46274481 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,13 @@ pub(crate) fn feature_powerset<'a>( } true }) - .collect() + .collect(); + if let Some(seed) = randomize { + use rand::SeedableRng; + use rand::seq::SliceRandom; + result.shuffle(&mut rand::rngs::SmallRng::seed_from_u64(seed)); + } + result } fn feature_deps(map: &BTreeMap>) -> BTreeMap<&str, BTreeSet<&str>> { @@ -366,22 +373,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 +402,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 +420,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 +434,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 +468,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..0be3e04b 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -47,6 +47,9 @@ OPTIONS: --include-features and there are multiple features, this also includes runs with just --all-features flag. + --randomize-powerset + Run feature powerset in random order with the specified seed. + --optional-deps [DEPS]... Use optional dependencies as features. diff --git a/tests/short-help.txt b/tests/short-help.txt index 5185a78f..869ee2dd 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -16,6 +16,8 @@ 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 Run feature powerset in random order with the specified + seed --optional-deps [DEPS]... Use optional dependencies as features --skip ... Alias for --exclude-features --exclude-features ... Space or comma separated list of features to exclude From a2a4a6e356f759fca606853b26a6405485ca6b2f Mon Sep 17 00:00:00 2001 From: Vitaly _Vi Shukela Date: Sat, 25 Jan 2025 19:01:51 +0100 Subject: [PATCH 2/7] Use `fastrand` instead of `rand`. --- .github/.cspell/rust-dependencies.txt | 1 + Cargo.toml | 2 +- src/cli.rs | 3 ++- src/features.rs | 9 +++++---- 4 files changed, 9 insertions(+), 6 deletions(-) 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 808a43ae..986dab3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,8 @@ 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" -rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } same-file = "1.0.1" serde_json = "1" termcolor = "1" diff --git a/src/cli.rs b/src/cli.rs index d4b4c73a..ea7b19e8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -494,7 +494,8 @@ impl Args { conflicts("--each-feature", "--feature-powerset")?; } - let randomize_powerset = randomize_powerset.as_deref().map(str::parse::).transpose()?; + let randomize_powerset = + randomize_powerset.as_deref().map(str::parse::).transpose()?; if all_features { if each_feature { diff --git a/src/features.rs b/src/features.rs index 46274481..dcf5085f 100644 --- a/src/features.rs +++ b/src/features.rs @@ -215,7 +215,7 @@ pub(crate) fn feature_powerset<'a>( let deps_map = feature_deps(package_features); let at_least_one_of = at_least_one_of_for_package(at_least_one_of, &deps_map); - let mut result : Vec> = 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| { @@ -252,9 +252,10 @@ pub(crate) fn feature_powerset<'a>( }) .collect(); if let Some(seed) = randomize { - use rand::SeedableRng; - use rand::seq::SliceRandom; - result.shuffle(&mut rand::rngs::SmallRng::seed_from_u64(seed)); + if seed != 0 { + fastrand::seed(seed); + } + fastrand::shuffle(&mut result); } result } From c41933dfd07e5d5be204a1572949dbd6d6bf92d9 Mon Sep 17 00:00:00 2001 From: Vitaly _Vi Shukela Date: Sat, 25 Jan 2025 19:04:03 +0100 Subject: [PATCH 3/7] Adjust help message for --randomize-powerset --- README.md | 4 ++++ src/cli.rs | 4 +++- tests/long-help.txt | 4 ++++ tests/short-help.txt | 3 +-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1dc39483..b0d74c0b 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,12 @@ OPTIONS: --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 ea7b19e8..1851f6d2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -707,7 +707,9 @@ const HELP: &[HelpText<'_>] = &[ --include-features and there are multiple features, this also includes runs with just \ --all-features flag." ]), - ("", "--randomize-powerset", "", "Run feature powerset in random order with the specified seed", &[ + ("", "--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.", diff --git a/tests/long-help.txt b/tests/long-help.txt index 0be3e04b..0a355973 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -48,8 +48,12 @@ OPTIONS: --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 869ee2dd..c5d37a99 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -16,8 +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 Run feature powerset in random order with the specified - seed + --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 From dbbeff4fccf717bfddb4193c53b8c7529231d8d8 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 26 Jan 2025 13:06:38 +0900 Subject: [PATCH 4/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0d74c0b..10291054 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ OPTIONS: --all-features flag. --randomize-powerset - Randomize order of powerset elements.. + Randomize order of powerset elements. Run feature powerset in random order with the specified seed. From 08de3824f0cfd097205e7ef708c98ddf6b321656 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 26 Jan 2025 13:06:44 +0900 Subject: [PATCH 5/7] Update src/cli.rs --- src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index 1851f6d2..824b83f2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -707,7 +707,7 @@ 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.", &[ + ("", "--randomize-powerset", "", "Randomize order of powerset elements", &[ "Run feature powerset in random order with the specified seed.", "Zero seed value means unseeded." ]), From 23d6d979d3c473bb7ae8fd8f8ca5da354f9781ca Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 26 Jan 2025 13:06:48 +0900 Subject: [PATCH 6/7] Update tests/long-help.txt --- tests/long-help.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/long-help.txt b/tests/long-help.txt index 0a355973..1cab7f07 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -48,7 +48,7 @@ OPTIONS: --all-features flag. --randomize-powerset - Randomize order of powerset elements.. + Randomize order of powerset elements. Run feature powerset in random order with the specified seed. From 30667c4d225e8aae495e505d5970dca561f3111d Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 26 Jan 2025 13:06:54 +0900 Subject: [PATCH 7/7] Update tests/short-help.txt --- tests/short-help.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/short-help.txt b/tests/short-help.txt index c5d37a99..0d2e45f8 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -16,7 +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. + --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