From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- src/tools/cargo/tests/testsuite/features.rs | 2084 +++++++++++++++++++++++++++ 1 file changed, 2084 insertions(+) create mode 100644 src/tools/cargo/tests/testsuite/features.rs (limited to 'src/tools/cargo/tests/testsuite/features.rs') diff --git a/src/tools/cargo/tests/testsuite/features.rs b/src/tools/cargo/tests/testsuite/features.rs new file mode 100644 index 000000000..848e05677 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/features.rs @@ -0,0 +1,2084 @@ +//! Tests for `[features]` table. + +use cargo_test_support::paths::CargoPathExt; +use cargo_test_support::registry::{Dependency, Package}; +use cargo_test_support::{basic_manifest, project}; + +#[cargo_test] +fn invalid1() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + bar = ["baz"] + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + feature `bar` includes `baz` which is neither a dependency nor another feature +", + ) + .run(); +} + +#[cargo_test] +fn same_name() { + // Feature with the same name as a dependency. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + bar = ["baz"] + baz = [] + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/main.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("tree -f") + .arg("{p} [{f}]") + .with_stderr("") + .with_stdout( + "\ +foo v0.0.1 ([..]) [] +└── bar v1.0.0 ([..]) [] +", + ) + .run(); + + p.cargo("tree --features bar -f") + .arg("{p} [{f}]") + .with_stderr("") + .with_stdout( + "\ +foo v0.0.1 ([..]) [bar,baz] +└── bar v1.0.0 ([..]) [] +", + ) + .run(); +} + +#[cargo_test] +fn invalid3() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + bar = ["baz"] + + [dependencies.baz] + path = "foo" + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + feature `bar` includes `baz`, but `baz` is not an optional dependency + A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition. +", + ) + .run(); +} + +#[cargo_test] +fn invalid4() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + features = ["bar"] + "#, + ) + .file("src/main.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +error: failed to select a version for `bar`. + ... required by package `foo v0.0.1 ([..])` +versions that meet the requirements `*` are: 0.0.1 + +the package `foo` depends on `bar`, with features: `bar` but `bar` does not have these features. + + +failed to select a version for `bar` which could resolve this conflict", + ) + .run(); + + p.change_file("Cargo.toml", &basic_manifest("foo", "0.0.1")); + + p.cargo("check --features test") + .with_status(101) + .with_stderr("error: Package `foo v0.0.1 ([..])` does not have the feature `test`") + .run(); +} + +#[cargo_test] +fn invalid5() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dev-dependencies.bar] + path = "bar" + optional = true + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + dev-dependencies are not allowed to be optional: `bar` +", + ) + .run(); +} + +#[cargo_test] +fn invalid6() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = ["bar/baz"] + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check --features foo") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + feature `foo` includes `bar/baz`, but `bar` is not a dependency +", + ) + .run(); +} + +#[cargo_test] +fn invalid7() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = ["bar/baz"] + bar = [] + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check --features foo") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + feature `foo` includes `bar/baz`, but `bar` is not a dependency +", + ) + .run(); +} + +#[cargo_test] +fn invalid8() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + features = ["foo/bar"] + "#, + ) + .file("src/main.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("check --features foo") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[CWD]/Cargo.toml` + +Caused by: + feature `foo/bar` in dependency `bar` is not allowed to contain slashes + If you want to enable features [..] +", + ) + .run(); +} + +#[cargo_test] +fn invalid9() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("check --features bar") + .with_stderr( + "\ +error: Package `foo v0.0.1 ([..])` does not have feature `bar`. It has a required dependency with that name, but only optional dependencies can be used as features. +", + ).with_status(101).run(); +} + +#[cargo_test] +fn invalid10() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + features = ["baz"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [dependencies.baz] + path = "baz" + "#, + ) + .file("bar/src/lib.rs", "") + .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("bar/baz/src/lib.rs", "") + .build(); + + p.cargo("check").with_stderr("\ +error: failed to select a version for `bar`. + ... required by package `foo v0.0.1 ([..])` +versions that meet the requirements `*` are: 0.0.1 + +the package `foo` depends on `bar`, with features: `baz` but `bar` does not have these features. + It has a required dependency with that name, but only optional dependencies can be used as features. + + +failed to select a version for `bar` which could resolve this conflict +").with_status(101) + .run(); +} + +#[cargo_test] +fn no_transitive_dep_feature_requirement() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.derived] + path = "derived" + + [features] + default = ["derived/bar/qux"] + "#, + ) + .file( + "src/main.rs", + r#" + extern crate derived; + fn main() { derived::test(); } + "#, + ) + .file( + "derived/Cargo.toml", + r#" + [package] + name = "derived" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "../bar" + "#, + ) + .file("derived/src/lib.rs", "extern crate bar; pub use bar::test;") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [features] + qux = [] + "#, + ) + .file( + "bar/src/lib.rs", + r#" + #[cfg(feature = "qux")] + pub fn test() { print!("test"); } + "#, + ) + .build(); + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[CWD]/Cargo.toml` + +Caused by: + multiple slashes in feature `derived/bar/qux` (included by feature `default`) are not allowed +", + ) + .run(); +} + +#[cargo_test] +fn no_feature_doesnt_build() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(feature = "bar")] + extern crate bar; + #[cfg(feature = "bar")] + fn main() { bar::bar(); println!("bar") } + #[cfg(not(feature = "bar"))] + fn main() {} + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("build") + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.process(&p.bin("foo")).with_stdout("").run(); + + p.cargo("build --features bar -v") + .with_stderr( + "\ +[COMPILING] bar v0.0.1 ([CWD]/bar) +[RUNNING] `rustc --crate-name bar [..] +[DIRTY-MSVC] foo v0.0.1 ([CWD]): the list of features changed +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name foo [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.process(&p.bin("foo")).with_stdout("bar\n").run(); +} + +#[cargo_test] +fn default_feature_pulled_in() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + default = ["bar"] + + [dependencies.bar] + path = "bar" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(feature = "bar")] + extern crate bar; + #[cfg(feature = "bar")] + fn main() { bar::bar(); println!("bar") } + #[cfg(not(feature = "bar"))] + fn main() {} + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("build") + .with_stderr( + "\ +[COMPILING] bar v0.0.1 ([CWD]/bar) +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.process(&p.bin("foo")).with_stdout("bar\n").run(); + + p.cargo("build --no-default-features -v") + .with_stderr( + "\ +[DIRTY-MSVC] foo v0.0.1 ([CWD]): the list of features changed +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name foo [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.process(&p.bin("foo")).with_stdout("").run(); +} + +#[cargo_test] +fn cyclic_feature() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + default = ["default"] + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr("[ERROR] cyclic feature dependency: feature `default` depends on itself") + .run(); +} + +#[cargo_test] +fn cyclic_feature2() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = ["bar"] + bar = ["foo"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check").with_stdout("").run(); +} + +#[cargo_test] +fn groups_on_groups_on_groups() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + default = ["f1"] + f1 = ["f2", "bar"] + f2 = ["f3", "f4"] + f3 = ["f5", "f6", "baz"] + f4 = ["f5", "f7"] + f5 = ["f6"] + f6 = ["f7"] + f7 = ["bar"] + + [dependencies.bar] + path = "bar" + optional = true + + [dependencies.baz] + path = "baz" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[allow(unused_extern_crates)] + extern crate bar; + #[allow(unused_extern_crates)] + extern crate baz; + fn main() {} + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn many_cli_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + optional = true + + [dependencies.baz] + path = "baz" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[allow(unused_extern_crates)] + extern crate bar; + #[allow(unused_extern_crates)] + extern crate baz; + fn main() {} + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + p.cargo("check --features") + .arg("bar baz") + .with_stderr( + "\ +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn union_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.d1] + path = "d1" + features = ["f1"] + [dependencies.d2] + path = "d2" + features = ["f2"] + "#, + ) + .file( + "src/main.rs", + r#" + #[allow(unused_extern_crates)] + extern crate d1; + extern crate d2; + fn main() { + d2::f1(); + d2::f2(); + } + "#, + ) + .file( + "d1/Cargo.toml", + r#" + [package] + name = "d1" + version = "0.0.1" + authors = [] + + [features] + f1 = ["d2"] + + [dependencies.d2] + path = "../d2" + features = ["f1"] + optional = true + "#, + ) + .file("d1/src/lib.rs", "") + .file( + "d2/Cargo.toml", + r#" + [package] + name = "d2" + version = "0.0.1" + authors = [] + + [features] + f1 = [] + f2 = [] + "#, + ) + .file( + "d2/src/lib.rs", + r#" + #[cfg(feature = "f1")] pub fn f1() {} + #[cfg(feature = "f2")] pub fn f2() {} + "#, + ) + .build(); + + p.cargo("check") + .with_stderr( + "\ +[CHECKING] d2 v0.0.1 ([CWD]/d2) +[CHECKING] d1 v0.0.1 ([CWD]/d1) +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn many_features_no_rebuilds() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + authors = [] + + [dependencies.a] + path = "a" + features = ["fall"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + authors = [] + + [features] + ftest = [] + ftest2 = [] + fall = ["ftest", "ftest2"] + "#, + ) + .file("a/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[CHECKING] a v0.1.0 ([CWD]/a) +[CHECKING] b v0.1.0 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.root().move_into_the_past(); + + p.cargo("check -v") + .with_stderr( + "\ +[FRESH] a v0.1.0 ([..]/a) +[FRESH] b v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +// Tests that all cmd lines work with `--features ""` +#[cargo_test] +fn empty_features() { + let p = project().file("src/main.rs", "fn main() {}").build(); + + p.cargo("check --features").arg("").run(); +} + +// Tests that all cmd lines work with `--features ""` +#[cargo_test] +fn transitive_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = ["bar/baz"] + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/main.rs", "extern crate bar; fn main() { bar::baz(); }") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [features] + baz = [] + "#, + ) + .file( + "bar/src/lib.rs", + r#"#[cfg(feature = "baz")] pub fn baz() {}"#, + ) + .build(); + + p.cargo("check --features foo").run(); +} + +#[cargo_test] +fn everything_in_the_lockfile() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + f1 = ["d1/f1"] + f2 = ["d2"] + + [dependencies.d1] + path = "d1" + [dependencies.d2] + path = "d2" + optional = true + [dependencies.d3] + path = "d3" + optional = true + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "d1/Cargo.toml", + r#" + [package] + name = "d1" + version = "0.0.1" + authors = [] + + [features] + f1 = [] + "#, + ) + .file("d1/src/lib.rs", "") + .file("d2/Cargo.toml", &basic_manifest("d2", "0.0.2")) + .file("d2/src/lib.rs", "") + .file( + "d3/Cargo.toml", + r#" + [package] + name = "d3" + version = "0.0.3" + authors = [] + + [features] + f3 = [] + "#, + ) + .file("d3/src/lib.rs", "") + .build(); + + p.cargo("fetch").run(); + let lockfile = p.read_lockfile(); + assert!( + lockfile.contains(r#"name = "d1""#), + "d1 not found\n{}", + lockfile + ); + assert!( + lockfile.contains(r#"name = "d2""#), + "d2 not found\n{}", + lockfile + ); + assert!( + lockfile.contains(r#"name = "d3""#), + "d3 not found\n{}", + lockfile + ); +} + +#[cargo_test] +fn no_rebuild_when_frobbing_default_feature() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + a = { path = "a" } + b = { path = "b" } + "#, + ) + .file("src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + authors = [] + + [dependencies] + a = { path = "../a", features = ["f1"], default-features = false } + "#, + ) + .file("b/src/lib.rs", "") + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + authors = [] + + [features] + default = ["f1"] + f1 = [] + "#, + ) + .file("a/src/lib.rs", "") + .build(); + + p.cargo("check").run(); + p.cargo("check").with_stdout("").run(); + p.cargo("check").with_stdout("").run(); +} + +#[cargo_test] +fn unions_work_with_no_default_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + a = { path = "a" } + b = { path = "b" } + "#, + ) + .file("src/lib.rs", "extern crate a; pub fn foo() { a::a(); }") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + authors = [] + + [dependencies] + a = { path = "../a", features = [], default-features = false } + "#, + ) + .file("b/src/lib.rs", "") + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + authors = [] + + [features] + default = ["f1"] + f1 = [] + "#, + ) + .file("a/src/lib.rs", r#"#[cfg(feature = "f1")] pub fn a() {}"#) + .build(); + + p.cargo("check").run(); + p.cargo("check").with_stdout("").run(); + p.cargo("check").with_stdout("").run(); +} + +#[cargo_test] +fn optional_and_dev_dep() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "test" + version = "0.1.0" + authors = [] + + [dependencies] + foo = { path = "foo", optional = true } + [dev-dependencies] + foo = { path = "foo" } + "#, + ) + .file("src/lib.rs", "") + .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("foo/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[CHECKING] test v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn activating_feature_activates_dep() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "test" + version = "0.1.0" + authors = [] + + [dependencies] + foo = { path = "foo", optional = true } + + [features] + a = ["foo/a"] + "#, + ) + .file( + "src/lib.rs", + "extern crate foo; pub fn bar() { foo::bar(); }", + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [features] + a = [] + "#, + ) + .file("foo/src/lib.rs", r#"#[cfg(feature = "a")] pub fn bar() {}"#) + .build(); + + p.cargo("check --features a -v").run(); +} + +#[cargo_test] +fn dep_feature_in_cmd_line() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.derived] + path = "derived" + "#, + ) + .file( + "src/main.rs", + r#" + extern crate derived; + fn main() { derived::test(); } + "#, + ) + .file( + "derived/Cargo.toml", + r#" + [package] + name = "derived" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "../bar" + + [features] + default = [] + derived-feat = ["bar/some-feat"] + "#, + ) + .file("derived/src/lib.rs", "extern crate bar; pub use bar::test;") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [features] + some-feat = [] + "#, + ) + .file( + "bar/src/lib.rs", + r#" + #[cfg(feature = "some-feat")] + pub fn test() { print!("test"); } + "#, + ) + .build(); + + // The foo project requires that feature "some-feat" in "bar" is enabled. + // Building without any features enabled should fail: + p.cargo("check") + .with_status(101) + .with_stderr_contains("[..]unresolved import `bar::test`") + .run(); + + // We should be able to enable the feature "derived-feat", which enables "some-feat", + // on the command line. The feature is enabled, thus building should be successful: + p.cargo("check --features derived/derived-feat").run(); + + // Trying to enable features of transitive dependencies is an error + p.cargo("check --features bar/some-feat") + .with_status(101) + .with_stderr("error: package `foo v0.0.1 ([..])` does not have a dependency named `bar`") + .run(); + + // Hierarchical feature specification should still be disallowed + p.cargo("check --features derived/bar/some-feat") + .with_status(101) + .with_stderr("[ERROR] multiple slashes in feature `derived/bar/some-feat` is not allowed") + .run(); +} + +#[cargo_test] +fn all_features_flag_enables_all_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = [] + bar = [] + + [dependencies.baz] + path = "baz" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(feature = "foo")] + pub fn foo() {} + + #[cfg(feature = "bar")] + pub fn bar() { + extern crate baz; + baz::baz(); + } + + fn main() { + foo(); + bar(); + } + "#, + ) + .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + p.cargo("check --all-features").run(); +} + +#[cargo_test] +fn many_cli_features_comma_delimited() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + optional = true + + [dependencies.baz] + path = "baz" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[allow(unused_extern_crates)] + extern crate bar; + #[allow(unused_extern_crates)] + extern crate baz; + fn main() {} + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + p.cargo("check --features bar,baz") + .with_stderr( + "\ +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn many_cli_features_comma_and_space_delimited() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + optional = true + + [dependencies.baz] + path = "baz" + optional = true + + [dependencies.bam] + path = "bam" + optional = true + + [dependencies.bap] + path = "bap" + optional = true + "#, + ) + .file( + "src/main.rs", + r#" + #[allow(unused_extern_crates)] + extern crate bar; + #[allow(unused_extern_crates)] + extern crate baz; + #[allow(unused_extern_crates)] + extern crate bam; + #[allow(unused_extern_crates)] + extern crate bap; + fn main() {} + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .file("bam/Cargo.toml", &basic_manifest("bam", "0.0.1")) + .file("bam/src/lib.rs", "pub fn bam() {}") + .file("bap/Cargo.toml", &basic_manifest("bap", "0.0.1")) + .file("bap/src/lib.rs", "pub fn bap() {}") + .build(); + + p.cargo("check --features") + .arg("bar,baz bam bap") + .with_stderr( + "\ +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..]) +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn only_dep_is_optional() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = ['bar'] + + [dependencies] + bar = { version = "0.1", optional = true } + + [dev-dependencies] + bar = "0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check").run(); +} + +#[cargo_test] +fn all_features_all_crates() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [workspace] + members = ['bar'] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [features] + foo = [] + "#, + ) + .file("bar/src/main.rs", "#[cfg(feature = \"foo\")] fn main() {}") + .build(); + + p.cargo("check --all-features --workspace").run(); +} + +#[cargo_test] +fn feature_off_dylib() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar"] + + [package] + name = "foo" + version = "0.0.1" + + [lib] + crate-type = ["dylib"] + + [features] + f1 = [] + "#, + ) + .file( + "src/lib.rs", + r#" + pub fn hello() -> &'static str { + if cfg!(feature = "f1") { + "f1" + } else { + "no f1" + } + } + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + + [dependencies] + foo = { path = ".." } + "#, + ) + .file( + "bar/src/main.rs", + r#" + extern crate foo; + + fn main() { + assert_eq!(foo::hello(), "no f1"); + } + "#, + ) + .build(); + + // Build the dylib with `f1` feature. + p.cargo("check --features f1").run(); + // Check that building without `f1` uses a dylib without `f1`. + p.cargo("run -p bar").run(); +} + +#[cargo_test] +fn warn_if_default_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + optional = true + + [features] + default-features = ["bar"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("check") + .with_stderr( + r#" +[WARNING] `default-features = [".."]` was found in [features]. Did you mean to use `default = [".."]`? +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] + "#.trim(), + ).run(); +} + +#[cargo_test] +fn no_feature_for_non_optional_dep() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = { path = "bar" } + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(not(feature = "bar"))] + fn main() { + } + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [features] + a = [] + "#, + ) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("check --features bar/a").run(); +} + +#[cargo_test] +fn features_option_given_twice() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + a = [] + b = [] + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(all(feature = "a", feature = "b"))] + fn main() {} + "#, + ) + .build(); + + p.cargo("check --features a --features b").run(); +} + +#[cargo_test] +fn multi_multi_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + a = [] + b = [] + c = [] + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(all(feature = "a", feature = "b", feature = "c"))] + fn main() {} + "#, + ) + .build(); + + p.cargo("check --features a --features").arg("b c").run(); +} + +#[cargo_test] +fn cli_parse_ok() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + a = [] + "#, + ) + .file( + "src/main.rs", + r#" + #[cfg(feature = "a")] + fn main() { + assert_eq!(std::env::args().nth(1).unwrap(), "b"); + } + "#, + ) + .build(); + + p.cargo("run --features a b").run(); +} + +#[cargo_test] +fn all_features_virtual_ws() { + // What happens with `--all-features` in the root of a virtual workspace. + // Some of this behavior is a little strange (member dependencies also + // have all features enabled, one might expect `f4` to be disabled). + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b"] + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + edition = "2018" + + [dependencies] + b = {path="../b", optional=true} + + [features] + default = ["f1"] + f1 = [] + f2 = [] + "#, + ) + .file( + "a/src/main.rs", + r#" + fn main() { + if cfg!(feature="f1") { + println!("f1"); + } + if cfg!(feature="f2") { + println!("f2"); + } + #[cfg(feature="b")] + b::f(); + } + "#, + ) + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + + [features] + default = ["f3"] + f3 = [] + f4 = [] + "#, + ) + .file( + "b/src/lib.rs", + r#" + pub fn f() { + if cfg!(feature="f3") { + println!("f3"); + } + if cfg!(feature="f4") { + println!("f4"); + } + } + "#, + ) + .build(); + + p.cargo("run").with_stdout("f1\n").run(); + p.cargo("run --all-features") + .with_stdout("f1\nf2\nf3\nf4\n") + .run(); + // In `a`, it behaves differently. :( + p.cargo("run --all-features") + .cwd("a") + .with_stdout("f1\nf2\nf3\n") + .run(); +} + +#[cargo_test] +fn slash_optional_enables() { + // --features dep/feat will enable `dep` and set its feature. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + dep = {path="dep", optional=true} + "#, + ) + .file( + "src/lib.rs", + r#" + #[cfg(not(feature="dep"))] + compile_error!("dep not set"); + "#, + ) + .file( + "dep/Cargo.toml", + r#" + [package] + name = "dep" + version = "0.1.0" + + [features] + feat = [] + "#, + ) + .file( + "dep/src/lib.rs", + r#" + #[cfg(not(feature="feat"))] + compile_error!("feat not set"); + "#, + ) + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr_contains("[..]dep not set[..]") + .run(); + + p.cargo("check --features dep/feat").run(); +} + +#[cargo_test] +fn registry_summary_order_doesnt_matter() { + // Checks for an issue where the resolver depended on the order of entries + // in the registry summary. If there was a non-optional dev-dependency + // that appeared before an optional normal dependency, then the resolver + // would not activate the optional dependency with a pkg/featname feature + // syntax. + Package::new("dep", "0.1.0") + .feature("feat1", &[]) + .file( + "src/lib.rs", + r#" + #[cfg(feature="feat1")] + pub fn work() { + println!("it works"); + } + "#, + ) + .publish(); + Package::new("bar", "0.1.0") + .feature("bar_feat", &["dep/feat1"]) + .add_dep(Dependency::new("dep", "0.1.0").dev()) + .add_dep(Dependency::new("dep", "0.1.0").optional(true)) + .file( + "src/lib.rs", + r#" + // This will fail to compile without `dep` optional dep activated. + extern crate dep; + + pub fn doit() { + dep::work(); + } + "#, + ) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + + [dependencies] + bar = { version="0.1", features = ["bar_feat"] } + "#, + ) + .file( + "src/main.rs", + r#" + fn main() { + bar::doit(); + } + "#, + ) + .build(); + + p.cargo("run") + .with_stderr( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] [..] +[DOWNLOADED] [..] +[COMPILING] dep v0.1.0 +[COMPILING] bar v0.1.0 +[COMPILING] foo v0.1.0 [..] +[FINISHED] [..] +[RUNNING] `target/debug/foo[EXE]` +", + ) + .with_stdout("it works") + .run(); +} + +#[cargo_test] +fn nonexistent_required_features() { + Package::new("required_dependency", "0.1.0") + .feature("simple", &[]) + .publish(); + Package::new("optional_dependency", "0.2.0") + .feature("optional", &[]) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [features] + existing = [] + fancy = ["optional_dependency"] + [dependencies] + required_dependency = { version = "0.1", optional = false} + optional_dependency = { version = "0.2", optional = true} + [[example]] + name = "ololo" + required-features = ["not_present", + "existing", + "fancy", + "required_dependency/not_existing", + "required_dependency/simple", + "optional_dependency/optional", + "not_specified_dependency/some_feature"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("examples/ololo.rs", "fn main() {}") + .build(); + + p.cargo("check --examples") + .with_stderr_contains( + "\ +[WARNING] invalid feature `not_present` in required-features of target `ololo`: \ + `not_present` is not present in [features] section +[WARNING] invalid feature `required_dependency/not_existing` in required-features \ + of target `ololo`: feature `not_existing` does not exist in package \ + `required_dependency v0.1.0` +[WARNING] invalid feature `not_specified_dependency/some_feature` in required-features \ + of target `ololo`: dependency `not_specified_dependency` does not exist +", + ) + .run(); +} + +#[cargo_test] +fn invalid_feature_names_warning() { + // Warnings for more restricted feature syntax. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [features] + # Some valid, but unusual names, shouldn't warn. + "c++17" = [] + "128bit" = [] + "_foo" = [] + "feat-name" = [] + "feat_name" = [] + "foo.bar" = [] + + # Invalid names. + "+foo" = [] + "-foo" = [] + ".foo" = [] + "foo:bar" = [] + "foo?" = [] + "?foo" = [] + "ⒶⒷⒸ" = [] + "a¼" = [] + "#, + ) + .file("src/lib.rs", "") + .build(); + + // Unfortunately the warnings are duplicated due to the Summary being + // loaded twice (once in the Workspace, and once in PackageRegistry) and + // Cargo does not have a de-duplication system. This should probably be + // OK, since I'm not expecting this to affect anyone. + p.cargo("check") + .with_stderr("\ +[WARNING] invalid character `+` in feature `+foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `-` in feature `-foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `.` in feature `.foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `?` in feature `?foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `¼` in feature `a¼` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `:` in feature `foo:bar` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `?` in feature `foo?` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `Ⓐ` in feature `ⒶⒷⒸ` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `Ⓑ` in feature `ⒶⒷⒸ` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[WARNING] invalid character `Ⓒ` in feature `ⒶⒷⒸ` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters) +This was previously accepted but is being phased out; it will become a hard error in a future release. +For more information, see issue #8813 , and please leave a comment if this will be a problem for your project. +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +") + .run(); +} + +#[cargo_test] +fn invalid_feature_names_error() { + // Errors for more restricted feature syntax. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [features] + "foo/bar" = [] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[CWD]/Cargo.toml` + +Caused by: + feature named `foo/bar` is not allowed to contain slashes +", + ) + .run(); +} + +#[cargo_test] +fn default_features_conflicting_warning() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + a = { path = "a", features = ["f1"], default-features = false, default_features = false } + "#, + ) + .file("src/lib.rs", "") + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + authors = [] + + [features] + default = ["f1"] + f1 = [] + "#, + ) + .file("a/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_stderr_contains( +"[WARNING] conflicting between `default-features` and `default_features` in the `a` dependency.\n + `default_features` is ignored and not recommended for use in the future" + ) + .run(); +} -- cgit v1.2.3