Skip to content

Commit

Permalink
Exclude feature combinations by detecting dependencies of features
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Nov 28, 2020
1 parent f7e334e commit 00e4337
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 5 deletions.
105 changes: 101 additions & 4 deletions src/features.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};

use crate::{
metadata::{Dependency, Metadata},
PackageId,
Expand Down Expand Up @@ -60,10 +62,46 @@ impl Features {
}
}

pub(crate) fn powerset<T: Clone>(
iter: impl IntoIterator<Item = T>,
pub(crate) fn feature_powerset<'a>(
features: impl IntoIterator<Item = &'a str>,
depth: Option<usize>,
) -> Vec<Vec<T>> {
map: &BTreeMap<String, Vec<String>>,
) -> Vec<Vec<&'a str>> {
let feature_deps = feature_deps(map);
let powerset = powerset(features, depth);
powerset
.into_iter()
.filter(|a| {
!a.iter().filter_map(|b| feature_deps.get(b)).any(|c| a.iter().any(|d| c.contains(d)))
})
.collect()
}

fn feature_deps(map: &BTreeMap<String, Vec<String>>) -> BTreeMap<&str, BTreeSet<&str>> {
let mut feat_deps = BTreeMap::new();
for feat in map.keys() {
let mut set = BTreeSet::new();
fn f<'a>(
map: &'a BTreeMap<String, Vec<String>>,
set: &mut BTreeSet<&'a str>,
curr: &str,
root: &str,
) {
if let Some(v) = map.get(curr) {
for x in v {
if x != root && set.insert(x) {
f(map, set, x, root);
}
}
}
}
f(map, &mut set, feat, feat);
feat_deps.insert(&**feat, set);
}
feat_deps
}

fn powerset<T: Clone>(iter: impl IntoIterator<Item = T>, depth: Option<usize>) -> Vec<Vec<T>> {
iter.into_iter().fold(vec![vec![]], |mut acc, elem| {
let ext = acc.clone().into_iter().map(|mut curr| {
curr.push(elem.clone());
Expand All @@ -80,7 +118,66 @@ pub(crate) fn powerset<T: Clone>(

#[cfg(test)]
mod tests {
use super::powerset;
use super::{feature_deps, feature_powerset, powerset};
use std::{
collections::{BTreeMap, BTreeSet},
iter::FromIterator,
};

macro_rules! svec {
($($expr:expr),* $(,)?) => {
vec![$($expr.into()),*]
};
}

macro_rules! map {
($(($key:expr, $value:expr)),* $(,)?) => {
BTreeMap::from_iter(vec![$(($key.into(), $value)),*])
};
}

macro_rules! set {
($($expr:expr),* $(,)?) => {
BTreeSet::from_iter(vec![$($expr),*])
};
}

#[test]
fn feature_deps1() {
let map =
map![("a", svec![]), ("b", svec!["a"]), ("c", svec!["b"]), ("d", svec!["a", "b"])];
let fd = feature_deps(&map);
assert_eq!(fd, map![
("a", set![]),
("b", set!["a"]),
("c", set!["a", "b"]),
("d", set!["a", "b"])
]);
let list = vec!["a", "b", "c", "d"];
let ps = powerset(list.clone(), None);
assert_eq!(ps, vec![
vec![],
vec!["a"],
vec!["b"],
vec!["a", "b"],
vec!["c"],
vec!["a", "c"],
vec!["b", "c"],
vec!["a", "b", "c"],
vec!["d"],
vec!["a", "d"],
vec!["b", "d"],
vec!["a", "b", "d"],
vec!["c", "d"],
vec!["a", "c", "d"],
vec!["b", "c", "d"],
vec!["a", "b", "c", "d"],
]);
let filtered = feature_powerset(list, None, &map);
assert_eq!(filtered, vec![vec![], vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec![
"c", "d"
]]);
}

#[test]
fn powerset_full() {
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn determine_kind<'a>(cx: &'a Context<'_>, id: &PackageId, progress: &mut Progre
Kind::Each { features }
}
} else if cx.feature_powerset {
let features = features::powerset(features, cx.depth);
let features = features::feature_powerset(features, cx.depth, &package.features);

if (package.features.is_empty() || !cx.include_features.is_empty()) && features.is_empty() {
progress.total += 1;
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/default_feature_behavior/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions tests/fixtures/powerset_deduplication/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target-dir = "../../../target"
21 changes: 21 additions & 0 deletions tests/fixtures/powerset_deduplication/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions tests/fixtures/powerset_deduplication/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "deduplication"
version = "0.1.0"
authors = ["Taiki Endo <te316e89@gmail.com>"]
publish = false

[workspace]
members = [
"member1",
".",
]

[features]
a = []
b = ["a"]
c = ["b"]
d = ["member1"]
e = ["b", "d"]

[dependencies]
member1 = { path = "member1", optional = true }
easytime = { version = "0.1", default-features = false }

[dev-dependencies]
15 changes: 15 additions & 0 deletions tests/fixtures/powerset_deduplication/member1/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "member1"
version = "0.1.0"
authors = ["Taiki Endo <te316e89@gmail.com>"]

[features]
a = []
b = ["a"]
c = ["b"]
d = []
e = ["b", "d"]

[dependencies]

[dev-dependencies]
1 change: 1 addition & 0 deletions tests/fixtures/powerset_deduplication/member1/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
1 change: 1 addition & 0 deletions tests/fixtures/powerset_deduplication/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
120 changes: 120 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,126 @@ fn feature_powerset_failure() {
);
}

#[test]
fn powerset_deduplication() {
cargo_hack(["check", "--feature-powerset"])
.test_dir("tests/fixtures/powerset_deduplication")
.assert_success()
.assert_stderr_contains(
"
running `cargo check --no-default-features` on deduplication (1/11)
running `cargo check --no-default-features --features a` on deduplication (2/11)
running `cargo check --no-default-features --features b` on deduplication (3/11)
running `cargo check --no-default-features --features c` on deduplication (4/11)
running `cargo check --no-default-features --features d` on deduplication (5/11)
running `cargo check --no-default-features --features a,d` on deduplication (6/11)
running `cargo check --no-default-features --features b,d` on deduplication (7/11)
running `cargo check --no-default-features --features c,d` on deduplication (8/11)
running `cargo check --no-default-features --features e` on deduplication (9/11)
running `cargo check --no-default-features --features c,e` on deduplication (10/11)
running `cargo check --no-default-features --all-features` on deduplication (11/11)
",
)
.assert_stderr_not_contains(
"
a,b
b,c
a,c
a,e
b,e
d,e
",
);

cargo_hack(["check", "--feature-powerset", "--optional-deps"])
.test_dir("tests/fixtures/powerset_deduplication")
.assert_success()
.assert_stderr_contains(
"
running `cargo check --no-default-features` on deduplication (1/15)
running `cargo check --no-default-features --features a` on deduplication (2/15)
running `cargo check --no-default-features --features b` on deduplication (3/15)
running `cargo check --no-default-features --features c` on deduplication (4/15)
running `cargo check --no-default-features --features d` on deduplication (5/15)
running `cargo check --no-default-features --features a,d` on deduplication (6/15)
running `cargo check --no-default-features --features b,d` on deduplication (7/15)
running `cargo check --no-default-features --features c,d` on deduplication (8/15)
running `cargo check --no-default-features --features e` on deduplication (9/15)
running `cargo check --no-default-features --features c,e` on deduplication (10/15)
running `cargo check --no-default-features --features member1` on deduplication (11/15)
running `cargo check --no-default-features --features a,member1` on deduplication (12/15)
running `cargo check --no-default-features --features b,member1` on deduplication (13/15)
running `cargo check --no-default-features --features c,member1` on deduplication (14/15)
running `cargo check --no-default-features --all-features` on deduplication (15/15)
",
)
.assert_stderr_not_contains(
"
a,b
b,c
a,c
a,e
b,e
d,e
",
);
}

#[rustversion::attr(not(since(1.41)), ignore)]
#[test]
fn powerset_deduplication_include_deps_features() {
// TODO: Since easytime/default depends on easytime/std, their combination should be excluded,
// but it's not working yet because include-deps-features itself isn't fully implemented.
cargo_hack(["check", "--feature-powerset", "--include-deps-features"])
.test_dir("tests/fixtures/powerset_deduplication")
.assert_success()
.assert_stderr_contains(
"
running `cargo check --no-default-features` on deduplication (1/41)
running `cargo check --no-default-features --features a` on deduplication (2/41)
running `cargo check --no-default-features --features b` on deduplication (3/41)
running `cargo check --no-default-features --features c` on deduplication (4/41)
running `cargo check --no-default-features --features d` on deduplication (5/41)
running `cargo check --no-default-features --features a,d` on deduplication (6/41)
running `cargo check --no-default-features --features b,d` on deduplication (7/41)
running `cargo check --no-default-features --features c,d` on deduplication (8/41)
running `cargo check --no-default-features --features e` on deduplication (9/41)
running `cargo check --no-default-features --features c,e` on deduplication (10/41)
running `cargo check --no-default-features --features easytime/default` on deduplication (11/41)
running `cargo check --no-default-features --features a,easytime/default` on deduplication (12/41)
running `cargo check --no-default-features --features b,easytime/default` on deduplication (13/41)
running `cargo check --no-default-features --features c,easytime/default` on deduplication (14/41)
running `cargo check --no-default-features --features d,easytime/default` on deduplication (15/41)
running `cargo check --no-default-features --features a,d,easytime/default` on deduplication (16/41)
running `cargo check --no-default-features --features b,d,easytime/default` on deduplication (17/41)
running `cargo check --no-default-features --features c,d,easytime/default` on deduplication (18/41)
running `cargo check --no-default-features --features e,easytime/default` on deduplication (19/41)
running `cargo check --no-default-features --features c,e,easytime/default` on deduplication (20/41)
running `cargo check --no-default-features --features easytime/std` on deduplication (21/41)
running `cargo check --no-default-features --features a,easytime/std` on deduplication (22/41)
running `cargo check --no-default-features --features b,easytime/std` on deduplication (23/41)
running `cargo check --no-default-features --features c,easytime/std` on deduplication (24/41)
running `cargo check --no-default-features --features d,easytime/std` on deduplication (25/41)
running `cargo check --no-default-features --features a,d,easytime/std` on deduplication (26/41)
running `cargo check --no-default-features --features b,d,easytime/std` on deduplication (27/41)
running `cargo check --no-default-features --features c,d,easytime/std` on deduplication (28/41)
running `cargo check --no-default-features --features e,easytime/std` on deduplication (29/41)
running `cargo check --no-default-features --features c,e,easytime/std` on deduplication (30/41)
running `cargo check --no-default-features --features easytime/default,easytime/std` on deduplication (31/41)
running `cargo check --no-default-features --features a,easytime/default,easytime/std` on deduplication (32/41)
running `cargo check --no-default-features --features b,easytime/default,easytime/std` on deduplication (33/41)
running `cargo check --no-default-features --features c,easytime/default,easytime/std` on deduplication (34/41)
running `cargo check --no-default-features --features d,easytime/default,easytime/std` on deduplication (35/41)
running `cargo check --no-default-features --features a,d,easytime/default,easytime/std` on deduplication (36/41)
running `cargo check --no-default-features --features b,d,easytime/default,easytime/std` on deduplication (37/41)
running `cargo check --no-default-features --features c,d,easytime/default,easytime/std` on deduplication (38/41)
running `cargo check --no-default-features --features e,easytime/default,easytime/std` on deduplication (39/41)
running `cargo check --no-default-features --features c,e,easytime/default,easytime/std` on deduplication (40/41)
running `cargo check --no-default-features --all-features` on deduplication (41/41)
",
);
}

#[test]
fn feature_powerset_depth() {
cargo_hack(["check", "--feature-powerset", "--depth", "2"])
Expand Down

0 comments on commit 00e4337

Please sign in to comment.