diff options
Diffstat (limited to 'tests/testsuite/weak_dep_features.rs')
-rw-r--r-- | tests/testsuite/weak_dep_features.rs | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/tests/testsuite/weak_dep_features.rs b/tests/testsuite/weak_dep_features.rs new file mode 100644 index 0000000..dfc1e6b --- /dev/null +++ b/tests/testsuite/weak_dep_features.rs @@ -0,0 +1,629 @@ +//! Tests for weak-dep-features. + +use super::features2::switch_to_resolver_2; +use cargo_test_support::paths::CargoPathExt; +use cargo_test_support::registry::{Dependency, Package, RegistryBuilder}; +use cargo_test_support::{project, publish}; +use std::fmt::Write; + +// Helper to create lib.rs files that check features. +fn require(enabled_features: &[&str], disabled_features: &[&str]) -> String { + let mut s = String::new(); + for feature in enabled_features { + writeln!(s, "#[cfg(not(feature=\"{feature}\"))] compile_error!(\"expected feature {feature} to be enabled\");", + feature=feature).unwrap(); + } + for feature in disabled_features { + writeln!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");", + feature=feature).unwrap(); + } + s +} + +#[cargo_test] +fn simple() { + Package::new("bar", "1.0.0") + .feature("feat", &[]) + .file("src/lib.rs", &require(&["feat"], &[])) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { version = "1.0", optional = true } + + [features] + f1 = ["bar?/feat"] + "#, + ) + .file("src/lib.rs", &require(&["f1"], &[])) + .build(); + + // It's a bit unfortunate that this has to download `bar`, but avoiding + // that is extremely difficult. + p.cargo("check --features f1") + .with_stderr( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 [..] +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + p.cargo("check --features f1,bar") + .with_stderr( + "\ +[CHECKING] bar v1.0.0 +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn deferred() { + // A complex chain that requires deferring enabling the feature due to + // another dependency getting enabled. + Package::new("bar", "1.0.0") + .feature("feat", &[]) + .file("src/lib.rs", &require(&["feat"], &[])) + .publish(); + Package::new("dep", "1.0.0") + .add_dep(Dependency::new("bar", "1.0").optional(true)) + .feature("feat", &["bar?/feat"]) + .publish(); + Package::new("bar_activator", "1.0.0") + .feature_dep("dep", "1.0", &["bar"]) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + dep = { version = "1.0", features = ["feat"] } + bar_activator = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] dep v1.0.0 [..] +[DOWNLOADED] bar_activator v1.0.0 [..] +[DOWNLOADED] bar v1.0.0 [..] +[CHECKING] bar v1.0.0 +[CHECKING] dep v1.0.0 +[CHECKING] bar_activator v1.0.0 +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn not_optional_dep() { + // Attempt to use dep_name?/feat where dep_name is not optional. + Package::new("dep", "1.0.0").feature("feat", &[]).publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + dep = "1.0" + + [features] + feat = ["dep?/feat"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr("\ +error: failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + feature `feat` includes `dep?/feat` with a `?`, but `dep` is not an optional dependency + A non-optional dependency of the same name is defined; consider removing the `?` or changing the dependency to be optional +") + .run(); +} + +#[cargo_test] +fn optional_cli_syntax() { + // --features bar?/feat + Package::new("bar", "1.0.0") + .feature("feat", &[]) + .file("src/lib.rs", &require(&["feat"], &[])) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { version = "1.0", optional = true } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // Does not build bar. + p.cargo("check --features bar?/feat") + .with_stderr( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 [..] +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + // Builds bar. + p.cargo("check --features bar?/feat,bar") + .with_stderr( + "\ +[CHECKING] bar v1.0.0 +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + eprintln!("check V2 resolver"); + switch_to_resolver_2(&p); + p.build_dir().rm_rf(); + // Does not build bar. + p.cargo("check --features bar?/feat") + .with_stderr( + "\ +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + // Builds bar. + p.cargo("check --features bar?/feat,bar") + .with_stderr( + "\ +[CHECKING] bar v1.0.0 +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn required_features() { + // required-features doesn't allow ? + Package::new("bar", "1.0.0").feature("feat", &[]).publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { version = "1.0", optional = true } + + [[bin]] + name = "foo" + required-features = ["bar?/feat"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[ERROR] invalid feature `bar?/feat` in required-features of target `foo`: \ +optional dependency with `?` is not allowed in required-features +", + ) + .run(); +} + +#[cargo_test] +fn weak_with_host_decouple() { + // weak-dep-features with new resolver + // + // foo v0.1.0 + // └── common v1.0.0 + // └── bar v1.0.0 <-- does not have `feat` enabled + // [build-dependencies] + // └── bar_activator v1.0.0 + // └── common v1.0.0 + // └── bar v1.0.0 <-- does have `feat` enabled + Package::new("bar", "1.0.0") + .feature("feat", &[]) + .file( + "src/lib.rs", + r#" + pub fn feat() -> bool { + cfg!(feature = "feat") + } + "#, + ) + .publish(); + + Package::new("common", "1.0.0") + .add_dep(Dependency::new("bar", "1.0").optional(true)) + .feature("feat", &["bar?/feat"]) + .file( + "src/lib.rs", + r#" + #[cfg(feature = "bar")] + pub fn feat() -> bool { bar::feat() } + #[cfg(not(feature = "bar"))] + pub fn feat() -> bool { false } + "#, + ) + .publish(); + + Package::new("bar_activator", "1.0.0") + .feature_dep("common", "1.0", &["bar", "feat"]) + .file( + "src/lib.rs", + r#" + pub fn feat() -> bool { + common::feat() + } + "#, + ) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + resolver = "2" + + [dependencies] + common = { version = "1.0", features = ["feat"] } + + [build-dependencies] + bar_activator = "1.0" + "#, + ) + .file( + "src/main.rs", + r#" + fn main() { + assert!(!common::feat()); + } + "#, + ) + .file( + "build.rs", + r#" + fn main() { + assert!(bar_activator::feat()); + } + "#, + ) + .build(); + + p.cargo("run") + .with_stderr( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] [..] +[DOWNLOADED] [..] +[DOWNLOADED] [..] +[COMPILING] bar v1.0.0 +[COMPILING] common v1.0.0 +[COMPILING] bar_activator v1.0.0 +[COMPILING] foo v0.1.0 [..] +[FINISHED] [..] +[RUNNING] `target/debug/foo[EXE]` +", + ) + .run(); +} + +#[cargo_test] +fn weak_namespaced() { + // Behavior with a dep: dependency. + Package::new("bar", "1.0.0") + .feature("feat", &[]) + .file("src/lib.rs", &require(&["feat"], &[])) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { version = "1.0", optional = true } + + [features] + f1 = ["bar?/feat"] + f2 = ["dep:bar"] + "#, + ) + .file("src/lib.rs", &require(&["f1"], &["f2", "bar"])) + .build(); + + p.cargo("check --features f1") + .with_stderr( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 [..] +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + p.cargo("tree -f") + .arg("{p} feats:{f}") + .with_stdout("foo v0.1.0 ([ROOT]/foo) feats:") + .run(); + + p.cargo("tree --features f1 -f") + .arg("{p} feats:{f}") + .with_stdout("foo v0.1.0 ([ROOT]/foo) feats:f1") + .run(); + + p.cargo("tree --features f1,f2 -f") + .arg("{p} feats:{f}") + .with_stdout( + "\ +foo v0.1.0 ([ROOT]/foo) feats:f1,f2 +└── bar v1.0.0 feats:feat +", + ) + .run(); + + // "bar" remains not-a-feature + p.change_file("src/lib.rs", &require(&["f1", "f2"], &["bar"])); + + p.cargo("check --features f1,f2") + .with_stderr( + "\ +[CHECKING] bar v1.0.0 +[CHECKING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn tree() { + Package::new("bar", "1.0.0") + .feature("feat", &[]) + .file("src/lib.rs", &require(&["feat"], &[])) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { version = "1.0", optional = true } + + [features] + f1 = ["bar?/feat"] + "#, + ) + .file("src/lib.rs", &require(&["f1"], &[])) + .build(); + + p.cargo("tree --features f1") + .with_stdout("foo v0.1.0 ([ROOT]/foo)") + .run(); + + p.cargo("tree --features f1,bar") + .with_stdout( + "\ +foo v0.1.0 ([ROOT]/foo) +└── bar v1.0.0 +", + ) + .run(); + + p.cargo("tree --features f1,bar -e features") + .with_stdout( + "\ +foo v0.1.0 ([ROOT]/foo) +└── bar feature \"default\" + └── bar v1.0.0 +", + ) + .run(); + + p.cargo("tree --features f1,bar -e features -i bar") + .with_stdout( + "\ +bar v1.0.0 +├── bar feature \"default\" +│ └── foo v0.1.0 ([ROOT]/foo) +│ ├── foo feature \"bar\" (command-line) +│ ├── foo feature \"default\" (command-line) +│ └── foo feature \"f1\" (command-line) +└── bar feature \"feat\" + └── foo feature \"f1\" (command-line) +", + ) + .run(); + + p.cargo("tree -e features --features bar?/feat") + .with_stdout("foo v0.1.0 ([ROOT]/foo)") + .run(); + + // This is a little strange in that it produces no output. + // Maybe `cargo tree` should print a note about why? + p.cargo("tree -e features -i bar --features bar?/feat") + .with_stdout("") + .run(); + + p.cargo("tree -e features -i bar --features bar?/feat,bar") + .with_stdout( + "\ +bar v1.0.0 +├── bar feature \"default\" +│ └── foo v0.1.0 ([ROOT]/foo) +│ ├── foo feature \"bar\" (command-line) +│ └── foo feature \"default\" (command-line) +└── bar feature \"feat\" (command-line) +", + ) + .run(); +} + +#[cargo_test] +fn publish() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + // Publish behavior with /? syntax. + Package::new("bar", "1.0.0").feature("feat", &[]).publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + description = "foo" + license = "MIT" + homepage = "https://example.com/" + + [dependencies] + bar = { version = "1.0", optional = true } + + [features] + feat1 = [] + feat2 = ["bar?/feat"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.1.0 [..] +[VERIFYING] foo v0.1.0 [..] +[UPDATING] [..] +[COMPILING] foo v0.1.0 [..] +[FINISHED] [..] +[PACKAGED] [..] +[UPLOADING] foo v0.1.0 [..] +[UPDATING] [..] +", + ) + .run(); + + publish::validate_upload_with_contents( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [ + { + "default_features": true, + "features": [], + "kind": "normal", + "name": "bar", + "optional": true, + "target": null, + "version_req": "^1.0" + } + ], + "description": "foo", + "documentation": null, + "features": { + "feat1": [], + "feat2": ["bar?/feat"] + }, + "homepage": "https://example.com/", + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": null, + "vers": "0.1.0" + } + "#, + "foo-0.1.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + &[( + "Cargo.toml", + &format!( + r#"{} +[package] +name = "foo" +version = "0.1.0" +description = "foo" +homepage = "https://example.com/" +license = "MIT" + +[dependencies.bar] +version = "1.0" +optional = true + +[features] +feat1 = [] +feat2 = ["bar?/feat"] +"#, + cargo::core::package::MANIFEST_PREAMBLE + ), + )], + ); +} |