From 48d4df129c7209f0b46679a5e1eaf3b4906c67a7 Mon Sep 17 00:00:00 2001 From: Vitaly _Vi Shukela Date: Sat, 25 Jan 2025 19:50:04 +0100 Subject: [PATCH] Implement --powerset-skip-first and --powerset-num-tests --- README.md | 11 +++++ src/cli.rs | 23 ++++++++++ src/features.rs | 107 +++++++++++++++++++++++++++++++++++++++---- src/main.rs | 2 + tests/long-help.txt | 11 +++++ tests/short-help.txt | 2 + 6 files changed, 147 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b0d74c0..b3b3e0e 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,17 @@ OPTIONS: Zero seed value means unseeded. + --powerset-skip-first + Skip specified number of initial powerset elements. + + This allows to resume interrupted large powerset runs of parallelize them using + CARGO_TARGET_DIR. + + --powerset-num-tests + Limit --feature-powerset run to specified number of trials. + + You can resume the run using --powerset-skip-first option. + --optional-deps [DEPS]... Use optional dependencies as features. diff --git a/src/cli.rs b/src/cli.rs index 1851f6d..7d1da63 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -104,6 +104,12 @@ pub(crate) struct Args { /// --randomize-powerset pub(crate) randomize_powerset: Option, + + /// --powerset-skip-first + pub(crate) powerset_skip_first: Option, + + /// --powerset-num-tests + pub(crate) powerset_num_tests: Option, } impl Args { @@ -182,6 +188,9 @@ impl Args { let mut randomize_powerset = None; + let mut powerset_skip_first = None; + let mut powerset_num_tests = None; + let mut group_features: Vec = vec![]; let mut mutually_exclusive_features: Vec = vec![]; let mut depth = None; @@ -314,6 +323,8 @@ impl Args { Long("ignore-private") => parse_flag!(ignore_private), Long("exclude-no-default-features") => parse_flag!(exclude_no_default_features), Long("exclude-all-features") => parse_flag!(exclude_all_features), + Long("powerset-skip-first") => parse_opt!(powerset_skip_first, false), + Long("powerset-num-tests") => parse_opt!(powerset_num_tests, false), Long("include-deps-features") => parse_flag!(include_deps_features), Long("clean-per-run") => parse_flag!(clean_per_run), Long("clean-per-version") => parse_flag!(clean_per_version), @@ -604,6 +615,9 @@ impl Args { || !mutually_exclusive_features.is_empty(); exclude_features.extend_from_slice(&features); + let powerset_skip_first = powerset_skip_first.as_deref().map(str::parse).transpose()?; + let powerset_num_tests = powerset_num_tests.as_deref().map(str::parse).transpose()?; + term::verbose::set(verbose != 0); // If `-vv` is passed, propagate `-v` to cargo. if verbose > 1 { @@ -650,6 +664,9 @@ impl Args { exclude_no_default_features, exclude_all_features, + powerset_skip_first, + powerset_num_tests, + features, no_default_features, @@ -711,6 +728,12 @@ const HELP: &[HelpText<'_>] = &[ "Run feature powerset in random order with the specified seed.", "Zero seed value means unseeded." ]), + ("", "--powerset-skip-first", "", "Skip specified number of initial powerset elements", &[ + "This allows to resume interrupted large powerset runs of parallelize them using CARGO_TARGET_DIR.", + ]), + ("", "--powerset-num-tests", "", "Limit --feature-powerset run to specified number of trials", &[ + "You can resume the run using --powerset-skip-first option.", + ]), ("", "--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 dcf5085..70e83d0 100644 --- a/src/features.rs +++ b/src/features.rs @@ -211,6 +211,8 @@ pub(crate) fn feature_powerset<'a>( mutually_exclusive_features: &[Feature], package_features: &BTreeMap>, randomize: Option, + skip_first: Option, + num_tests: 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); @@ -257,6 +259,19 @@ pub(crate) fn feature_powerset<'a>( } fastrand::shuffle(&mut result); } + if let Some(mut skip_first) = skip_first { + result.retain(|_| { + if skip_first == 0 { + true + } else { + skip_first -= 1; + false + } + }); + } + if let Some(num_tests) = num_tests { + result.resize_with(num_tests.min(result.len()), || unreachable!()); + } result } @@ -374,22 +389,32 @@ 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, None); + let filtered = feature_powerset(&list, None, &[], &[], &map, None, None, 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, None); + let filtered = feature_powerset(&list, None, &["a".into()], &[], &map, None, None, 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, None); + let filtered = feature_powerset(&list, None, &["c".into()], &[], &map, None, None, None); assert_eq!(filtered, vec![vec!["c"], vec!["c", "d"]]); - let filtered = feature_powerset(&list, None, &["a".into(), "c".into()], &[], &map, None); + let filtered = + feature_powerset(&list, None, &["a".into(), "c".into()], &[], &map, None, None, 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, None); + let filtered = feature_powerset( + &list, + None, + &[], + &mutually_exclusive_features, + &map, + None, + None, + None, + ); assert_eq!(filtered, vec![ vec!["a"], vec!["b"], @@ -403,7 +428,16 @@ 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, None); + let filtered = feature_powerset( + &list, + None, + &[], + &mutually_exclusive_features, + &map, + None, + None, + None, + ); assert_eq!(filtered, vec![ vec!["a"], vec!["b"], @@ -421,7 +455,16 @@ 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, None); + let filtered = feature_powerset( + &list, + None, + &[], + &mutually_exclusive_features, + &map, + None, + None, + None, + ); assert_eq!(filtered, vec![ vec!["a"], vec!["b"], @@ -435,7 +478,16 @@ 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, None); + let filtered = feature_powerset( + &list, + None, + &[], + &mutually_exclusive_features, + &map, + None, + None, + None, + ); assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"]]); } @@ -469,9 +521,46 @@ mod tests { vec!["b", "c", "d"], vec!["a", "b", "c", "d"], ]); - let filtered = feature_powerset(&list, None, &[], &[], &map, None); + let filtered = feature_powerset(&list, None, &[], &[], &map, None, None, None); assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]); } + #[test] + fn powerset_paging() { + let map = map![("a", v![]), ("b", v!["a"]), ("c", v!["b"]), ("d", v!["a", "b"])]; + let list: Vec = v!["a", "b", "c", "d", "e"]; + let filtered = feature_powerset(&list, None, &[], &[], &map, None, None, None); + assert_eq!(filtered, vec![ + vec!["a"], + vec!["b"], + vec!["c"], + vec!["d"], + vec!["c", "d"], + vec!["e"], + vec!["a", "e"], + vec!["b", "e"], + vec!["c", "e"], + vec!["d", "e"], + vec!["c", "d", "e"] + ]); + + let filtered = feature_powerset(&list, None, &[], &[], &map, None, None, Some(0)); + assert!(filtered.is_empty()); + + let filtered = feature_powerset(&list, None, &[], &[], &map, None, None, Some(4)); + assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"],]); + + let filtered = feature_powerset(&list, None, &[], &[], &map, None, Some(0), Some(4)); + assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"],]); + + let filtered = feature_powerset(&list, None, &[], &[], &map, None, Some(4), Some(4)); + assert_eq!(filtered, vec![vec!["c", "d"], vec!["e"], vec!["a", "e"], vec!["b", "e"],]); + + let filtered = feature_powerset(&list, None, &[], &[], &map, None, Some(8), Some(4)); + assert_eq!(filtered, vec![vec!["c", "e"], vec!["d", "e"], vec!["c", "d", "e"]]); + + let filtered = feature_powerset(&list, None, &[], &[], &map, None, Some(8), None); + assert_eq!(filtered, vec![vec!["c", "e"], vec!["d", "e"], vec!["c", "d", "e"]]); + } #[test] fn powerset_full() { diff --git a/src/main.rs b/src/main.rs index e9277f6..a8be8da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -290,6 +290,8 @@ fn determine_kind<'a>( &cx.mutually_exclusive_features, &package.features, cx.randomize_powerset, + cx.powerset_skip_first, + cx.powerset_num_tests, ); 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 0a35597..d2c99b2 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -54,6 +54,17 @@ OPTIONS: Zero seed value means unseeded. + --powerset-skip-first + Skip specified number of initial powerset elements. + + This allows to resume interrupted large powerset runs of parallelize them using + CARGO_TARGET_DIR. + + --powerset-num-tests + Limit --feature-powerset run to specified number of trials. + + You can resume the run using --powerset-skip-first option. + --optional-deps [DEPS]... Use optional dependencies as features. diff --git a/tests/short-help.txt b/tests/short-help.txt index c5d37a9..9a49b03 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -17,6 +17,8 @@ OPTIONS: --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. + --powerset-skip-first Skip specified number of initial powerset elements + --powerset-num-tests Limit --feature-powerset run to specified number of trials --optional-deps [DEPS]... Use optional dependencies as features --skip ... Alias for --exclude-features --exclude-features ... Space or comma separated list of features to exclude