cargo/tests/testsuite/weak_dep_features.rs

639 lines
15 KiB
Rust
Raw Normal View History

2020-10-25 23:34:03 +00:00
//! Tests for weak-dep-features.
use super::features2::switch_to_resolver_2;
use cargo_test_support::paths::CargoPathExt;
use cargo_test_support::registry::{self, Dependency, Package};
2021-02-10 19:15:19 +00:00
use cargo_test_support::{project, publish};
2020-10-25 23:34:03 +00:00
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\");",
2020-10-25 23:34:03 +00:00
feature=feature).unwrap();
}
for feature in disabled_features {
writeln!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");",
2020-10-25 23:34:03 +00:00
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")
2020-10-25 23:34:03 +00:00
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("check --features f1,bar")
2020-10-25 23:34:03 +00:00
.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")
2020-10-25 23:34:03 +00:00
.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")
2020-10-25 23:34:03 +00:00
.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")
2020-10-25 23:34:03 +00:00
.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")
2020-10-25 23:34:03 +00:00
.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")
2020-10-25 23:34:03 +00:00
.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
2020-10-25 23:34:03 +00:00
//
// 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"
2020-10-25 23:34:03 +00:00
[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")
2020-10-25 23:34:03 +00:00
.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]
2021-06-11 14:27:33 +00:00
fn weak_namespaced() {
// Behavior with a dep: dependency.
2020-10-25 23:34:03 +00:00
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]
2021-06-11 14:27:33 +00:00
bar = { version = "1.0", optional = true }
[features]
f1 = ["bar?/feat"]
f2 = ["dep:bar"]
2020-10-25 23:34:03 +00:00
"#,
)
2021-06-11 14:27:33 +00:00
.file("src/lib.rs", &require(&["f1"], &["f2", "bar"]))
2020-10-25 23:34:03 +00:00
.build();
p.cargo("check --features f1")
2021-06-11 14:27:33 +00:00
.with_stderr(
2020-10-25 23:34:03 +00:00
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
2021-06-11 14:27:33 +00:00
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("tree -f")
2021-06-11 14:27:33 +00:00
.arg("{p} feats:{f}")
.with_stdout("foo v0.1.0 ([ROOT]/foo) feats:")
.run();
p.cargo("tree --features f1 -f")
2021-06-11 14:27:33 +00:00
.arg("{p} feats:{f}")
.with_stdout("foo v0.1.0 ([ROOT]/foo) feats:f1")
.run();
p.cargo("tree --features f1,f2 -f")
2021-06-11 14:27:33 +00:00
.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")
2021-06-11 14:27:33 +00:00
.with_stderr(
"\
2020-10-25 23:34:03 +00:00
[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")
2020-10-25 23:34:03 +00:00
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree --features f1,bar")
2020-10-25 23:34:03 +00:00
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar v1.0.0
",
)
.run();
p.cargo("tree --features f1,bar -e features")
2020-10-25 23:34:03 +00:00
.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")
2020-10-25 23:34:03 +00:00
.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)
2020-10-25 23:34:03 +00:00
",
)
.run();
}
2021-02-10 19:15:19 +00:00
#[cargo_test]
fn publish() {
// HACK below allows us to use a local registry
let registry = registry::init();
2021-02-10 19:15:19 +00:00
// 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();
// HACK: Inject `foo` directly into the index so `publish` won't block for it to be in
// the index.
//
// This is to ensure we can verify the Summary we post to the registry as doing so precludes
// the registry from processing the publish.
Package::new("foo", "0.1.0")
.file("src/lib.rs", "")
.publish();
p.cargo("publish")
.replace_crates_io(registry.index_url())
2021-02-10 19:15:19 +00:00
.with_stderr(
"\
[UPDATING] [..]
[PACKAGING] foo v0.1.0 [..]
[VERIFYING] foo v0.1.0 [..]
[COMPILING] foo v0.1.0 [..]
[FINISHED] [..]
[PACKAGED] [..]
2021-02-10 19:15:19 +00:00
[UPLOADING] foo v0.1.0 [..]
fix(publish): Block until it is in index Originally, crates.io would block on publish requests until the publish was complete, giving `cargo publish` this behavior by extension. When crates.io switched to asynchronous publishing, this intermittently broke people's workflows when publishing multiple crates. I say interittent because it usually works until it doesn't and it is unclear why to the end user because it will be published by the time they check. In the end, callers tend to either put in timeouts (and pray), poll the server's API, or use `crates-index` crate to poll the index. This isn't sufficient because - For any new interested party, this is a pit of failure they'll fall into - crates-index has re-implemented index support incorrectly in the past, currently doesn't handle auth, doesn't support `git-cli`, etc. - None of these previous options work if we were to implement workspace-publish support (#1169) - The new sparse registry might increase the publish times, making the delay easier to hit manually - The new sparse registry goes through CDNs so checking the server's API might not be sufficient - Once the sparse registry is available, crates-index users will find out when the package is ready in git but it might not be ready through the sparse registry because of CDNs So now `cargo` will block until it sees the package in the index. - This is checking via the index instead of server APIs in case there are propagation delays. This has the side effect of being noisy because of all of the "Updating index" messages. - This is done unconditionally because cargo used to block and that didn't seem to be a problem, blocking by default is the less error prone case, and there doesn't seem to be enough justification for a "don't block" flag. The timeout was 5min but I dropped it to 1m. Unfortunately, I don't have data from `cargo-release` to know what a reasonable timeout is, so going ahead and dropping to 60s and assuming anything more is an outage. Fixes #9507
2022-07-21 16:48:29 +00:00
[UPDATING] [..]
2021-02-10 19:15:19 +00:00
",
)
.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#"{}
2021-02-10 19:15:19 +00:00
[package]
name = "foo"
version = "0.1.0"
description = "foo"
homepage = "https://example.com/"
license = "MIT"
2021-02-10 19:15:19 +00:00
[dependencies.bar]
version = "1.0"
optional = true
[features]
feat1 = []
feat2 = ["bar?/feat"]
"#,
cargo::core::package::MANIFEST_PREAMBLE
),
2021-02-10 19:15:19 +00:00
)],
);
}