Alex Crichton a92fd48fb4 Improve error message for cyclic dependencies
First reported in rust-lang/rust#65014 it looks like our error message
on cyclic dependencies may be confusing at times. It looks like this is
an issue because there are multiple paths through a graph for a
dependency, so using the generic `path_to_top` function isn't producing
the most useful path for this purpose.

We're already walking the graph though, so this commit adds an extra
parameter which collects the list of packages we've visited so far to
produce a hopefully always-accurate error message showing the chain of
dependencies end-to-end for what depends on what.
2019-10-07 16:50:42 -07:00

1472 lines
49 KiB
Rust

use cargo::core::dependency::Kind;
use cargo::core::{enable_nightly_features, Dependency};
use cargo::util::{is_ci, Config};
use resolver_tests::{
assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, dep_req_kind, loc_names, names,
pkg, pkg_id, pkg_loc, registry, registry_strategy, remove_dep, resolve, resolve_and_validated,
resolve_with_config, PrettyPrintRegistry, SatResolve, ToDep, ToPkgId,
};
use proptest::prelude::*;
// NOTE: proptest is a form of fuzz testing. It generates random input and makes sure that
// certain universal truths are upheld. Therefore, it can pass when there is a problem,
// but if it fails then there really is something wrong. When testing something as
// complicated as the resolver, the problems can be very subtle and hard to generate.
// We have had a history of these tests only failing on PRs long after a bug is introduced.
// If you have one of these test fail please report it on #6258,
// and if you did not change the resolver then feel free to retry without concern.
proptest! {
#![proptest_config(ProptestConfig {
max_shrink_iters:
if is_ci() || !atty::is(atty::Stream::Stderr) {
// This attempts to make sure that CI will fail fast,
0
} else {
// but that local builds will give a small clear test case.
std::u32::MAX
},
result_cache: prop::test_runner::basic_result_cache,
.. ProptestConfig::default()
})]
/// NOTE: if you think this test has failed spuriously see the note at the top of this macro.
#[test]
fn prop_passes_validation(
PrettyPrintRegistry(input) in registry_strategy(50, 20, 60)
) {
let reg = registry(input.clone());
let sat_resolve = SatResolve::new(&reg);
// there is only a small chance that any one
// crate will be interesting.
// So we try some of the most complicated.
for this in input.iter().rev().take(20) {
let _ = resolve_and_validated(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&reg,
Some(sat_resolve.clone()),
);
}
}
/// NOTE: if you think this test has failed spuriously see the note at the top of this macro.
#[test]
fn prop_minimum_version_errors_the_same(
PrettyPrintRegistry(input) in registry_strategy(50, 20, 60)
) {
enable_nightly_features();
let mut config = Config::default().unwrap();
config
.configure(
1,
None,
&None,
false,
false,
false,
&None,
&["minimal-versions".to_string()],
)
.unwrap();
let reg = registry(input.clone());
// there is only a small chance that any one
// crate will be interesting.
// So we try some of the most complicated.
for this in input.iter().rev().take(10) {
// minimal-versions change what order the candidates
// are tried but not the existence of a solution
let res = resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&reg,
);
let mres = resolve_with_config(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&reg,
Some(&config),
);
prop_assert_eq!(
res.is_ok(),
mres.is_ok(),
"minimal-versions and regular resolver disagree about weather `{} = \"={}\"` can resolve",
this.name(),
this.version()
)
}
}
/// NOTE: if you think this test has failed spuriously see the note at the top of this macro.
#[test]
fn prop_removing_a_dep_cant_break(
PrettyPrintRegistry(input) in registry_strategy(50, 20, 60),
indexes_to_remove in prop::collection::vec((any::<prop::sample::Index>(), any::<prop::sample::Index>()), ..10)
) {
let reg = registry(input.clone());
let mut removed_input = input.clone();
for (summary_idx, dep_idx) in indexes_to_remove {
if !removed_input.is_empty() {
let summary_idx = summary_idx.index(removed_input.len());
let deps = removed_input[summary_idx].dependencies();
if !deps.is_empty() {
let new = remove_dep(&removed_input[summary_idx], dep_idx.index(deps.len()));
removed_input[summary_idx] = new;
}
}
}
let removed_reg = registry(removed_input);
// there is only a small chance that any one
// crate will be interesting.
// So we try some of the most complicated.
for this in input.iter().rev().take(10) {
if resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&reg,
).is_ok() {
prop_assert!(
resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&removed_reg,
).is_ok(),
"full index worked for `{} = \"={}\"` but removing some deps broke it!",
this.name(),
this.version(),
)
}
}
}
/// NOTE: if you think this test has failed spuriously see the note at the top of this macro.
#[test]
fn prop_limited_independence_of_irrelevant_alternatives(
PrettyPrintRegistry(input) in registry_strategy(50, 20, 60),
indexes_to_unpublish in prop::collection::vec(any::<prop::sample::Index>(), ..10)
) {
let reg = registry(input.clone());
// there is only a small chance that any one
// crate will be interesting.
// So we try some of the most complicated.
for this in input.iter().rev().take(10) {
let res = resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&reg,
);
match res {
Ok(r) => {
// If resolution was successful, then unpublishing a version of a crate
// that was not selected should not change that.
let not_selected: Vec<_> = input
.iter()
.cloned()
.filter(|x| !r.contains(&x.package_id()))
.collect();
if !not_selected.is_empty() {
let indexes_to_unpublish: Vec<_> = indexes_to_unpublish.iter().map(|x| x.get(&not_selected)).collect();
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| !indexes_to_unpublish.contains(&x))
.collect(),
);
let res = resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&new_reg,
);
// Note: that we can not assert that the two `res` are identical
// as the resolver does depend on irrelevant alternatives.
// It uses how constrained a dependency requirement is
// to determine what order to evaluate requirements.
prop_assert!(
res.is_ok(),
"unpublishing {:?} stopped `{} = \"={}\"` from working",
indexes_to_unpublish.iter().map(|x| x.package_id()).collect::<Vec<_>>(),
this.name(),
this.version()
)
}
}
Err(_) => {
// If resolution was unsuccessful, then it should stay unsuccessful
// even if any version of a crate is unpublished.
let indexes_to_unpublish: Vec<_> = indexes_to_unpublish.iter().map(|x| x.get(&input)).collect();
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| !indexes_to_unpublish.contains(&x))
.collect(),
);
let res = resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&new_reg,
);
prop_assert!(
res.is_err(),
"full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!",
this.name(),
this.version(),
indexes_to_unpublish.iter().map(|x| x.package_id()).collect::<Vec<_>>(),
)
}
}
}
}
}
#[test]
fn pub_fail() {
let input = vec![
pkg!(("a", "0.0.4")),
pkg!(("a", "0.0.5")),
pkg!(("e", "0.0.6") => [dep_req_kind("a", "<= 0.0.4", Kind::Normal, true),]),
pkg!(("kB", "0.0.3") => [dep_req("a", ">= 0.0.5"),dep("e"),]),
];
let reg = registry(input.clone());
assert!(resolve_and_validated(vec![dep("kB")], &reg, None).is_err());
}
#[test]
fn basic_public_dependency() {
let reg = registry(vec![
pkg!(("A", "0.1.0")),
pkg!(("A", "0.2.0")),
pkg!("B" => [dep_req_kind("A", "0.1", Kind::Normal, true)]),
pkg!("C" => [dep("A"), dep("B")]),
]);
let res = resolve_and_validated(vec![dep("C")], &reg, None).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("C", "1.0.0"),
("B", "1.0.0"),
("A", "0.1.0"),
]),
);
}
#[test]
fn public_dependency_filling_in() {
// The resolver has an optimization where if a candidate to resolve a dependency
// has already bean activated then we skip looking at the candidates dependencies.
// However, we have to be careful as the new path may make pub dependencies invalid.
// Triggering this case requires dependencies to be resolved in a specific order.
// Fuzzing found this unintuitive case, that triggers this unfortunate order of operations:
// 1. `d`'s dep on `c` is resolved
// 2. `d`'s dep on `a` is resolved with `0.1.1`
// 3. `c`'s dep on `b` is resolved with `0.0.2`
// 4. `b`'s dep on `a` is resolved with `0.0.6` no pub dev conflict as `b` is private to `c`
// 5. `d`'s dep on `b` is resolved with `0.0.2` triggering the optimization.
// Do we notice that `d` has a pub dep conflict on `a`? Lets try it and see.
let reg = registry(vec![
pkg!(("a", "0.0.6")),
pkg!(("a", "0.1.1")),
pkg!(("b", "0.0.0") => [dep("bad")]),
pkg!(("b", "0.0.1") => [dep("bad")]),
pkg!(("b", "0.0.2") => [dep_req_kind("a", "=0.0.6", Kind::Normal, true)]),
pkg!("c" => [dep_req("b", ">=0.0.1")]),
pkg!("d" => [dep("c"), dep("a"), dep("b")]),
]);
let res = resolve_and_validated(vec![dep("d")], &reg, None).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("d", "1.0.0"),
("c", "1.0.0"),
("b", "0.0.2"),
("a", "0.0.6"),
]),
);
}
#[test]
fn public_dependency_filling_in_and_update() {
// The resolver has an optimization where if a candidate to resolve a dependency
// has already bean activated then we skip looking at the candidates dependencies.
// However, we have to be careful as the new path may make pub dependencies invalid.
// Triggering this case requires dependencies to be resolved in a specific order.
// Fuzzing found this unintuitive case, that triggers this unfortunate order of operations:
// 1. `D`'s dep on `B` is resolved
// 2. `D`'s dep on `C` is resolved
// 3. `B`'s dep on `A` is resolved with `0.0.0`
// 4. `C`'s dep on `B` triggering the optimization.
// So did we add `A 0.0.0` to the deps `C` can see?
// Or are we going to resolve `C`'s dep on `A` with `0.0.2`?
// Lets try it and see.
let reg = registry(vec![
pkg!(("A", "0.0.0")),
pkg!(("A", "0.0.2")),
pkg!("B" => [dep_req_kind("A", "=0.0.0", Kind::Normal, true),]),
pkg!("C" => [dep("A"),dep("B")]),
pkg!("D" => [dep("B"),dep("C")]),
]);
let res = resolve_and_validated(vec![dep("D")], &reg, None).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("D", "1.0.0"),
("C", "1.0.0"),
("B", "1.0.0"),
("A", "0.0.0"),
]),
);
}
#[test]
fn public_dependency_skipping() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
// the effects of pub dep must be accounted for.
let input = vec![
pkg!(("a", "0.2.0")),
pkg!(("a", "2.0.0")),
pkg!(("b", "0.0.0") => [dep("bad")]),
pkg!(("b", "0.2.1") => [dep_req_kind("a", "0.2.0", Kind::Normal, true)]),
pkg!("c" => [dep("a"),dep("b")]),
];
let reg = registry(input);
resolve_and_validated(vec![dep("c")], &reg, None).unwrap();
}
#[test]
fn public_dependency_skipping_in_backtracking() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
// the effects of pub dep must be accounted for.
let input = vec![
pkg!(("A", "0.0.0") => [dep("bad")]),
pkg!(("A", "0.0.1") => [dep("bad")]),
pkg!(("A", "0.0.2") => [dep("bad")]),
pkg!(("A", "0.0.3") => [dep("bad")]),
pkg!(("A", "0.0.4")),
pkg!(("A", "0.0.5")),
pkg!("B" => [dep_req_kind("A", ">= 0.0.3", Kind::Normal, true)]),
pkg!("C" => [dep_req("A", "<= 0.0.4"), dep("B")]),
];
let reg = registry(input);
resolve_and_validated(vec![dep("C")], &reg, None).unwrap();
}
#[test]
fn public_sat_topological_order() {
let input = vec![
pkg!(("a", "0.0.1")),
pkg!(("a", "0.0.0")),
pkg!(("b", "0.0.1") => [dep_req_kind("a", "= 0.0.1", Kind::Normal, true),]),
pkg!(("b", "0.0.0") => [dep("bad"),]),
pkg!("A" => [dep_req("a", "= 0.0.0"),dep_req_kind("b", "*", Kind::Normal, true)]),
];
let reg = registry(input);
assert!(resolve_and_validated(vec![dep("A")], &reg, None).is_err());
}
#[test]
fn public_sat_unused_makes_things_pub() {
let input = vec![
pkg!(("a", "0.0.1")),
pkg!(("a", "0.0.0")),
pkg!(("b", "8.0.1") => [dep_req_kind("a", "= 0.0.1", Kind::Normal, true),]),
pkg!(("b", "8.0.0") => [dep_req("a", "= 0.0.1"),]),
pkg!("c" => [dep_req("b", "= 8.0.0"),dep_req("a", "= 0.0.0"),]),
];
let reg = registry(input);
resolve_and_validated(vec![dep("c")], &reg, None).unwrap();
}
#[test]
fn public_sat_unused_makes_things_pub_2() {
let input = vec![
pkg!(("c", "0.0.2")),
pkg!(("c", "0.0.1")),
pkg!(("a-sys", "0.0.2")),
pkg!(("a-sys", "0.0.1") => [dep_req_kind("c", "= 0.0.1", Kind::Normal, true),]),
pkg!("P" => [dep_req_kind("a-sys", "*", Kind::Normal, true),dep_req("c", "= 0.0.1"),]),
pkg!("A" => [dep("P"),dep_req("c", "= 0.0.2"),]),
];
let reg = registry(input);
resolve_and_validated(vec![dep("A")], &reg, None).unwrap();
}
#[test]
#[should_panic(expected = "assertion failed: !name.is_empty()")]
fn test_dependency_with_empty_name() {
// Bug 5229, dependency-names must not be empty
"".to_dep();
}
#[test]
fn test_resolving_empty_dependency_list() {
let res = resolve(Vec::new(), &registry(vec![])).unwrap();
assert_eq!(res, names(&["root"]));
}
#[test]
fn test_resolving_only_package() {
let reg = registry(vec![pkg!("foo")]);
let res = resolve(vec![dep("foo")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo"]));
}
#[test]
fn test_resolving_one_dep() {
let reg = registry(vec![pkg!("foo"), pkg!("bar")]);
let res = resolve(vec![dep("foo")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo"]));
}
#[test]
fn test_resolving_multiple_deps() {
let reg = registry(vec![pkg!("foo"), pkg!("bar"), pkg!("baz")]);
let res = resolve(vec![dep("foo"), dep("baz")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo", "baz"]));
}
#[test]
fn test_resolving_transitive_deps() {
let reg = registry(vec![pkg!("foo"), pkg!("bar" => ["foo"])]);
let res = resolve(vec![dep("bar")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo", "bar"]));
}
#[test]
fn test_resolving_common_transitive_deps() {
let reg = registry(vec![pkg!("foo" => ["bar"]), pkg!("bar")]);
let res = resolve(vec![dep("foo"), dep("bar")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo", "bar"]));
}
#[test]
fn test_resolving_with_same_name() {
let list = vec![
pkg_loc("foo", "https://first.example.com"),
pkg_loc("bar", "https://second.example.com"),
];
let reg = registry(list);
let res = resolve(
vec![
dep_loc("foo", "https://first.example.com"),
dep_loc("bar", "https://second.example.com"),
],
&reg,
)
.unwrap();
let mut names = loc_names(&[
("foo", "https://first.example.com"),
("bar", "https://second.example.com"),
]);
names.push(pkg_id("root"));
assert_same(&res, &names);
}
#[test]
fn test_resolving_with_dev_deps() {
let reg = registry(vec![
pkg!("foo" => ["bar", dep_kind("baz", Kind::Development)]),
pkg!("baz" => ["bat", dep_kind("bam", Kind::Development)]),
pkg!("bar"),
pkg!("bat"),
]);
let res = resolve(vec![dep("foo"), dep_kind("baz", Kind::Development)], &reg).unwrap();
assert_same(&res, &names(&["root", "foo", "bar", "baz", "bat"]));
}
#[test]
fn resolving_with_many_versions() {
let reg = registry(vec![pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2"))]);
let res = resolve(vec![dep("foo")], &reg).unwrap();
assert_same(&res, &names(&[("root", "1.0.0"), ("foo", "1.0.2")]));
}
#[test]
fn resolving_with_specific_version() {
let reg = registry(vec![pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2"))]);
let res = resolve(vec![dep_req("foo", "=1.0.1")], &reg).unwrap();
assert_same(&res, &names(&[("root", "1.0.0"), ("foo", "1.0.1")]));
}
#[test]
fn test_resolving_maximum_version_with_transitive_deps() {
let reg = registry(vec![
pkg!(("util", "1.2.2")),
pkg!(("util", "1.0.0")),
pkg!(("util", "1.1.1")),
pkg!("foo" => [dep_req("util", "1.0.0")]),
pkg!("bar" => [dep_req("util", ">=1.0.1")]),
]);
let res = resolve(vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], &reg).unwrap();
assert_contains(
&res,
&names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("bar", "1.0.0"),
("util", "1.2.2"),
]),
);
assert!(!res.contains(&("util", "1.0.1").to_pkgid()));
assert!(!res.contains(&("util", "1.1.1").to_pkgid()));
}
#[test]
fn test_resolving_minimum_version_with_transitive_deps() {
enable_nightly_features(); // -Z minimal-versions
// When the minimal-versions config option is specified then the lowest
// possible version of a package should be selected. "util 1.0.0" can't be
// selected because of the requirements of "bar", so the minimum version
// must be 1.1.1.
let reg = registry(vec![
pkg!(("util", "1.2.2")),
pkg!(("util", "1.0.0")),
pkg!(("util", "1.1.1")),
pkg!("foo" => [dep_req("util", "1.0.0")]),
pkg!("bar" => [dep_req("util", ">=1.0.1")]),
]);
let mut config = Config::default().unwrap();
config
.configure(
1,
None,
&None,
false,
false,
false,
&None,
&["minimal-versions".to_string()],
)
.unwrap();
let res = resolve_with_config(
vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")],
&reg,
Some(&config),
)
.unwrap();
assert_contains(
&res,
&names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("bar", "1.0.0"),
("util", "1.1.1"),
]),
);
assert!(!res.contains(&("util", "1.2.2").to_pkgid()));
assert!(!res.contains(&("util", "1.0.0").to_pkgid()));
}
#[test]
fn resolving_incompat_versions() {
let reg = registry(vec![
pkg!(("foo", "1.0.1")),
pkg!(("foo", "1.0.2")),
pkg!("bar" => [dep_req("foo", "=1.0.2")]),
]);
assert!(resolve(vec![dep_req("foo", "=1.0.1"), dep("bar")], &reg).is_err());
}
#[test]
fn resolving_wrong_case_from_registry() {
// In the future we may #5678 allow this to happen.
// For back compatibility reasons, we probably won't.
// But we may want to future prove ourselves by understanding it.
// This test documents the current behavior.
let reg = registry(vec![pkg!(("foo", "1.0.0")), pkg!("bar" => ["Foo"])]);
assert!(resolve(vec![dep("bar")], &reg).is_err());
}
#[test]
fn resolving_mis_hyphenated_from_registry() {
// In the future we may #2775 allow this to happen.
// For back compatibility reasons, we probably won't.
// But we may want to future prove ourselves by understanding it.
// This test documents the current behavior.
let reg = registry(vec![pkg!(("fo-o", "1.0.0")), pkg!("bar" => ["fo_o"])]);
assert!(resolve(vec![dep("bar")], &reg).is_err());
}
#[test]
fn resolving_backtrack() {
let reg = registry(vec![
pkg!(("foo", "1.0.2") => [dep("bar")]),
pkg!(("foo", "1.0.1") => [dep("baz")]),
pkg!("bar" => [dep_req("foo", "=2.0.2")]),
pkg!("baz"),
]);
let res = resolve(vec![dep_req("foo", "^1")], &reg).unwrap();
assert_contains(
&res,
&names(&[("root", "1.0.0"), ("foo", "1.0.1"), ("baz", "1.0.0")]),
);
}
#[test]
fn resolving_backtrack_features() {
// test for cargo/issues/4347
let mut bad = dep("bar");
bad.set_features(vec!["bad"]);
let reg = registry(vec![
pkg!(("foo", "1.0.2") => [bad]),
pkg!(("foo", "1.0.1") => [dep("bar")]),
pkg!("bar"),
]);
let res = resolve(vec![dep_req("foo", "^1")], &reg).unwrap();
assert_contains(
&res,
&names(&[("root", "1.0.0"), ("foo", "1.0.1"), ("bar", "1.0.0")]),
);
}
#[test]
fn resolving_allows_multiple_compatible_versions() {
let reg = registry(vec![
pkg!(("foo", "1.0.0")),
pkg!(("foo", "2.0.0")),
pkg!(("foo", "0.1.0")),
pkg!(("foo", "0.2.0")),
pkg!("bar" => ["d1", "d2", "d3", "d4"]),
pkg!("d1" => [dep_req("foo", "1")]),
pkg!("d2" => [dep_req("foo", "2")]),
pkg!("d3" => [dep_req("foo", "0.1")]),
pkg!("d4" => [dep_req("foo", "0.2")]),
]);
let res = resolve(vec![dep("bar")], &reg).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("foo", "2.0.0"),
("foo", "0.1.0"),
("foo", "0.2.0"),
("d1", "1.0.0"),
("d2", "1.0.0"),
("d3", "1.0.0"),
("d4", "1.0.0"),
("bar", "1.0.0"),
]),
);
}
#[test]
fn resolving_with_deep_backtracking() {
let reg = registry(vec![
pkg!(("foo", "1.0.1") => [dep_req("bar", "1")]),
pkg!(("foo", "1.0.0") => [dep_req("bar", "2")]),
pkg!(("bar", "1.0.0") => [dep_req("baz", "=1.0.2"),
dep_req("other", "1")]),
pkg!(("bar", "2.0.0") => [dep_req("baz", "=1.0.1")]),
pkg!(("baz", "1.0.2") => [dep_req("other", "2")]),
pkg!(("baz", "1.0.1")),
pkg!(("dep_req", "1.0.0")),
pkg!(("dep_req", "2.0.0")),
]);
let res = resolve(vec![dep_req("foo", "1")], &reg).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("bar", "2.0.0"),
("baz", "1.0.1"),
]),
);
}
#[test]
fn resolving_with_sys_crates() {
// This is based on issues/4902
// With `l` a normal library we get 2copies so everyone gets the newest compatible.
// But `l-sys` a library with a links attribute we make sure there is only one.
let reg = registry(vec![
pkg!(("l-sys", "0.9.1")),
pkg!(("l-sys", "0.10.0")),
pkg!(("l", "0.9.1")),
pkg!(("l", "0.10.0")),
pkg!(("d", "1.0.0") => [dep_req("l-sys", ">=0.8.0, <=0.10.0"), dep_req("l", ">=0.8.0, <=0.10.0")]),
pkg!(("r", "1.0.0") => [dep_req("l-sys", "0.9"), dep_req("l", "0.9")]),
]);
let res = resolve(vec![dep_req("d", "1"), dep_req("r", "1")], &reg).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("d", "1.0.0"),
("r", "1.0.0"),
("l-sys", "0.9.1"),
("l", "0.9.1"),
("l", "0.10.0"),
]),
);
}
#[test]
fn resolving_with_constrained_sibling_backtrack_parent() {
// There is no point in considering all of the backtrack_trap{1,2}
// candidates since they can't change the result of failing to
// resolve 'constrained'. Cargo should (ideally) skip past them and resume
// resolution once the activation of the parent, 'bar', is rolled back.
// Note that the traps are slightly more constrained to make sure they
// get picked first.
let mut reglist = vec![
pkg!(("foo", "1.0.0") => [dep_req("bar", "1.0"),
dep_req("constrained", "=1.0.0")]),
pkg!(("bar", "1.0.0") => [dep_req("backtrack_trap1", "1.0.2"),
dep_req("backtrack_trap2", "1.0.2"),
dep_req("constrained", "1.0.0")]),
pkg!(("constrained", "1.0.0")),
pkg!(("backtrack_trap1", "1.0.0")),
pkg!(("backtrack_trap2", "1.0.0")),
];
// Bump this to make the test harder - it adds more versions of bar that will
// fail to resolve, and more versions of the traps to consider.
const NUM_BARS_AND_TRAPS: usize = 50; // minimum 2
for i in 1..NUM_BARS_AND_TRAPS {
let vsn = format!("1.0.{}", i);
reglist.push(
pkg!(("bar", vsn.clone()) => [dep_req("backtrack_trap1", "1.0.2"),
dep_req("backtrack_trap2", "1.0.2"),
dep_req("constrained", "1.0.1")]),
);
reglist.push(pkg!(("backtrack_trap1", vsn.clone())));
reglist.push(pkg!(("backtrack_trap2", vsn.clone())));
reglist.push(pkg!(("constrained", vsn.clone())));
}
let reg = registry(reglist);
let res = resolve(vec![dep_req("foo", "1")], &reg).unwrap();
assert_contains(
&res,
&names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("bar", "1.0.0"),
("constrained", "1.0.0"),
]),
);
}
#[test]
fn resolving_with_many_equivalent_backtracking() {
let mut reglist = Vec::new();
const DEPTH: usize = 200;
const BRANCHING_FACTOR: usize = 100;
// Each level depends on the next but the last level does not exist.
// Without cashing we need to test every path to the last level O(BRANCHING_FACTOR ^ DEPTH)
// and this test will time out. With cashing we need to discover that none of these
// can be activated O(BRANCHING_FACTOR * DEPTH)
for l in 0..DEPTH {
let name = format!("level{}", l);
let next = format!("level{}", l + 1);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())]));
}
}
let reg = registry(reglist.clone());
let res = resolve(vec![dep("level0")], &reg);
assert!(res.is_err());
// It is easy to write code that quickly returns an error.
// Lets make sure we can find a good answer if it is there.
reglist.push(pkg!(("level0", "1.0.0")));
let reg = registry(reglist.clone());
let res = resolve(vec![dep("level0")], &reg).unwrap();
assert_contains(&res, &names(&[("root", "1.0.0"), ("level0", "1.0.0")]));
// Make sure we have not special case no candidates.
reglist.push(pkg!(("constrained", "1.1.0")));
reglist.push(pkg!(("constrained", "1.0.0")));
reglist.push(
pkg!((format!("level{}", DEPTH).as_str(), "1.0.0") => [dep_req("constrained", "=1.0.0")]),
);
let reg = registry(reglist.clone());
let res = resolve(vec![dep("level0"), dep("constrained")], &reg).unwrap();
assert_contains(
&res,
&names(&[
("root", "1.0.0"),
("level0", "1.0.0"),
("constrained", "1.1.0"),
]),
);
let reg = registry(reglist.clone());
let res = resolve(vec![dep_req("level0", "1.0.1"), dep("constrained")], &reg).unwrap();
assert_contains(
&res,
&names(&[
("root", "1.0.0"),
(format!("level{}", DEPTH).as_str(), "1.0.0"),
("constrained", "1.0.0"),
]),
);
let reg = registry(reglist);
let res = resolve(
vec![dep_req("level0", "1.0.1"), dep_req("constrained", "1.1.0")],
&reg,
);
assert!(res.is_err());
}
#[test]
fn resolving_with_deep_traps() {
let mut reglist = Vec::new();
const DEPTH: usize = 200;
const BRANCHING_FACTOR: usize = 100;
// Each backtrack_trap depends on the next, and adds a backtrack frame.
// None of witch is going to help with `bad`.
for l in 0..DEPTH {
let name = format!("backtrack_trap{}", l);
let next = format!("backtrack_trap{}", l + 1);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())]));
}
}
{
let name = format!("backtrack_trap{}", DEPTH);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str())));
}
}
{
// slightly less constrained to make sure `cloaking` gets picked last.
for i in 1..(BRANCHING_FACTOR + 10) {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!(("cloaking", vsn.as_str()) => [dep_req("bad", "1.0.1")]));
}
}
let reg = registry(reglist);
let res = resolve(vec![dep("backtrack_trap0"), dep("cloaking")], &reg);
assert!(res.is_err());
}
#[test]
fn resolving_with_constrained_cousins_backtrack() {
let mut reglist = Vec::new();
const DEPTH: usize = 100;
const BRANCHING_FACTOR: usize = 50;
// Each backtrack_trap depends on the next.
// The last depends on a specific ver of constrained.
for l in 0..DEPTH {
let name = format!("backtrack_trap{}", l);
let next = format!("backtrack_trap{}", l + 1);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())]));
}
}
{
let name = format!("backtrack_trap{}", DEPTH);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(
pkg!((name.as_str(), vsn.as_str()) => [dep_req("constrained", ">=1.1.0, <=2.0.0")]),
);
}
}
{
// slightly less constrained to make sure `constrained` gets picked last.
for i in 0..(BRANCHING_FACTOR + 10) {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!(("constrained", vsn.as_str())));
}
reglist.push(pkg!(("constrained", "1.1.0")));
reglist.push(pkg!(("constrained", "2.0.0")));
reglist.push(pkg!(("constrained", "2.0.1")));
}
reglist.push(pkg!(("cloaking", "1.0.0") => [dep_req("constrained", "~1.0.0")]));
let reg = registry(reglist.clone());
// `backtrack_trap0 = "*"` is a lot of ways of saying `constrained = ">=1.1.0, <=2.0.0"`
// but `constrained= "2.0.1"` is already picked.
// Only then to try and solve `constrained= "~1.0.0"` which is incompatible.
let res = resolve(
vec![
dep("backtrack_trap0"),
dep_req("constrained", "2.0.1"),
dep("cloaking"),
],
&reg,
);
assert!(res.is_err());
// Each level depends on the next but the last depends on incompatible deps.
// Let's make sure that we can cache that a dep has incompatible deps.
for l in 0..DEPTH {
let name = format!("level{}", l);
let next = format!("level{}", l + 1);
for i in 1..BRANCHING_FACTOR {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!((name.as_str(), vsn.as_str()) => [dep(next.as_str())]));
}
}
reglist.push(
pkg!((format!("level{}", DEPTH).as_str(), "1.0.0") => [dep("backtrack_trap0"),
dep("cloaking")
]),
);
let reg = registry(reglist);
let res = resolve(vec![dep("level0"), dep_req("constrained", "2.0.1")], &reg);
assert!(res.is_err());
let res = resolve(vec![dep("level0"), dep_req("constrained", "2.0.0")], &reg).unwrap();
assert_contains(
&res,
&names(&[("constrained", "2.0.0"), ("cloaking", "1.0.0")]),
);
}
#[test]
fn resolving_with_constrained_sibling_backtrack_activation() {
// It makes sense to resolve most-constrained deps first, but
// with that logic the backtrack traps here come between the two
// attempted resolutions of 'constrained'. When backtracking,
// cargo should skip past them and resume resolution once the
// number of activations for 'constrained' changes.
let mut reglist = vec![
pkg!(("foo", "1.0.0") => [dep_req("bar", "=1.0.0"),
dep_req("backtrack_trap1", "1.0"),
dep_req("backtrack_trap2", "1.0"),
dep_req("constrained", "<=1.0.60")]),
pkg!(("bar", "1.0.0") => [dep_req("constrained", ">=1.0.60")]),
];
// Bump these to make the test harder, but you'll also need to
// change the version constraints on `constrained` above. To correctly
// exercise Cargo, the relationship between the values is:
// NUM_CONSTRAINED - vsn < NUM_TRAPS < vsn
// to make sure the traps are resolved between `constrained`.
const NUM_TRAPS: usize = 45; // min 1
const NUM_CONSTRAINED: usize = 100; // min 1
for i in 0..NUM_TRAPS {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!(("backtrack_trap1", vsn.clone())));
reglist.push(pkg!(("backtrack_trap2", vsn.clone())));
}
for i in 0..NUM_CONSTRAINED {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!(("constrained", vsn.clone())));
}
let reg = registry(reglist);
let res = resolve(vec![dep_req("foo", "1")], &reg).unwrap();
assert_contains(
&res,
&names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("bar", "1.0.0"),
("constrained", "1.0.60"),
]),
);
}
#[test]
fn resolving_with_public_constrained_sibling() {
// It makes sense to resolve most-constrained deps first, but
// with that logic the backtrack traps here come between the two
// attempted resolutions of 'constrained'. When backtracking,
// cargo should skip past them and resume resolution once the
// number of activations for 'constrained' changes.
let mut reglist = vec![
pkg!(("foo", "1.0.0") => [dep_req("bar", "=1.0.0"),
dep_req("backtrack_trap1", "1.0"),
dep_req("backtrack_trap2", "1.0"),
dep_req("constrained", "<=60")]),
pkg!(("bar", "1.0.0") => [dep_req_kind("constrained", ">=60", Kind::Normal, true)]),
];
// Bump these to make the test harder, but you'll also need to
// change the version constraints on `constrained` above. To correctly
// exercise Cargo, the relationship between the values is:
// NUM_CONSTRAINED - vsn < NUM_TRAPS < vsn
// to make sure the traps are resolved between `constrained`.
const NUM_TRAPS: usize = 45; // min 1
const NUM_CONSTRAINED: usize = 100; // min 1
for i in 0..NUM_TRAPS {
let vsn = format!("1.0.{}", i);
reglist.push(pkg!(("backtrack_trap1", vsn.clone())));
reglist.push(pkg!(("backtrack_trap2", vsn.clone())));
}
for i in 0..NUM_CONSTRAINED {
let vsn = format!("{}.0.0", i);
reglist.push(pkg!(("constrained", vsn.clone())));
}
let reg = registry(reglist);
let _ = resolve_and_validated(vec![dep_req("foo", "1")], &reg, None);
}
#[test]
fn resolving_with_constrained_sibling_transitive_dep_effects() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
// be taken to not miss the transitive effects of alternatives. E.g.
// in the right-to-left resolution of the graph below, B may
// affect whether D is successfully resolved.
//
// A
// / | \
// B C D
// | |
// C D
let reg = registry(vec![
pkg!(("A", "1.0.0") => [dep_req("B", "1.0"),
dep_req("C", "1.0"),
dep_req("D", "1.0.100")]),
pkg!(("B", "1.0.0") => [dep_req("C", ">=1.0.0")]),
pkg!(("B", "1.0.1") => [dep_req("C", ">=1.0.1")]),
pkg!(("C", "1.0.0") => [dep_req("D", "1.0.0")]),
pkg!(("C", "1.0.1") => [dep_req("D", ">=1.0.1,<1.0.100")]),
pkg!(("C", "1.0.2") => [dep_req("D", ">=1.0.2,<1.0.100")]),
pkg!(("D", "1.0.0")),
pkg!(("D", "1.0.1")),
pkg!(("D", "1.0.2")),
pkg!(("D", "1.0.100")),
pkg!(("D", "1.0.101")),
pkg!(("D", "1.0.102")),
pkg!(("D", "1.0.103")),
pkg!(("D", "1.0.104")),
pkg!(("D", "1.0.105")),
]);
let res = resolve(vec![dep_req("A", "1")], &reg).unwrap();
assert_same(
&res,
&names(&[
("root", "1.0.0"),
("A", "1.0.0"),
("B", "1.0.0"),
("C", "1.0.0"),
("D", "1.0.105"),
]),
);
}
#[test]
fn incomplete_information_skipping() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
// be taken to not miss the transitive effects of alternatives.
// Fuzzing discovered that for some reason cargo was skipping based
// on incomplete information in the following case:
// minimized bug found in:
// https://github.com/rust-lang/cargo/commit/003c29b0c71e5ea28fbe8e72c148c755c9f3f8d9
let input = vec![
pkg!(("a", "1.0.0")),
pkg!(("a", "1.1.0")),
pkg!("b" => [dep("a")]),
pkg!(("c", "1.0.0")),
pkg!(("c", "1.1.0")),
pkg!("d" => [dep_req("c", "=1.0")]),
pkg!(("e", "1.0.0")),
pkg!(("e", "1.1.0") => [dep_req("c", "1.1")]),
pkg!("to_yank"),
pkg!(("f", "1.0.0") => [
dep("to_yank"),
dep("d"),
]),
pkg!(("f", "1.1.0") => [dep("d")]),
pkg!("g" => [
dep("b"),
dep("e"),
dep("f"),
]),
];
let reg = registry(input.clone());
let res = resolve(vec![dep("g")], &reg).unwrap();
let package_to_yank = "to_yank".to_pkgid();
// this package is not used in the resolution.
assert!(!res.contains(&package_to_yank));
// so when we yank it
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| package_to_yank != x.package_id())
.collect(),
);
assert_eq!(input.len(), new_reg.len() + 1);
// it should still build
assert!(resolve(vec![dep("g")], &new_reg).is_ok());
}
#[test]
fn incomplete_information_skipping_2() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
// be taken to not miss the transitive effects of alternatives.
// Fuzzing discovered that for some reason cargo was skipping based
// on incomplete information in the following case:
// https://github.com/rust-lang/cargo/commit/003c29b0c71e5ea28fbe8e72c148c755c9f3f8d9
let input = vec![
pkg!(("b", "3.8.10")),
pkg!(("b", "8.7.4")),
pkg!(("b", "9.4.6")),
pkg!(("c", "1.8.8")),
pkg!(("c", "10.2.5")),
pkg!(("d", "4.1.2") => [
dep_req("bad", "=6.10.9"),
]),
pkg!(("d", "5.5.6")),
pkg!(("d", "5.6.10")),
pkg!(("to_yank", "8.0.1")),
pkg!(("to_yank", "8.8.1")),
pkg!(("e", "4.7.8") => [
dep_req("d", ">=5.5.6, <=5.6.10"),
dep_req("to_yank", "=8.0.1"),
]),
pkg!(("e", "7.4.9") => [
dep_req("bad", "=4.7.5"),
]),
pkg!("f" => [
dep_req("d", ">=4.1.2, <=5.5.6"),
]),
pkg!("g" => [
dep("bad"),
]),
pkg!(("h", "3.8.3") => [
dep("g"),
]),
pkg!(("h", "6.8.3") => [
dep("f"),
]),
pkg!(("h", "8.1.9") => [
dep_req("to_yank", "=8.8.1"),
]),
pkg!("i" => [
dep("b"),
dep("c"),
dep("e"),
dep("h"),
]),
];
let reg = registry(input.clone());
let res = resolve(vec![dep("i")], &reg).unwrap();
let package_to_yank = ("to_yank", "8.8.1").to_pkgid();
// this package is not used in the resolution.
assert!(!res.contains(&package_to_yank));
// so when we yank it
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| package_to_yank != x.package_id())
.collect(),
);
assert_eq!(input.len(), new_reg.len() + 1);
// it should still build
assert!(resolve(vec![dep("i")], &new_reg).is_ok());
}
#[test]
fn incomplete_information_skipping_3() {
// When backtracking due to a failed dependency, if Cargo is
// trying to be clever and skip irrelevant dependencies, care must
// be taken to not miss the transitive effects of alternatives.
// Fuzzing discovered that for some reason cargo was skipping based
// on incomplete information in the following case:
// minimized bug found in:
// https://github.com/rust-lang/cargo/commit/003c29b0c71e5ea28fbe8e72c148c755c9f3f8d9
let input = vec![
pkg! {("to_yank", "3.0.3")},
pkg! {("to_yank", "3.3.0")},
pkg! {("to_yank", "3.3.1")},
pkg! {("a", "3.3.0") => [
dep_req("to_yank", "=3.0.3"),
] },
pkg! {("a", "3.3.2") => [
dep_req("to_yank", "<=3.3.0"),
] },
pkg! {("b", "0.1.3") => [
dep_req("a", "=3.3.0"),
] },
pkg! {("b", "2.0.2") => [
dep_req("to_yank", "3.3.0"),
dep("a"),
] },
pkg! {("b", "2.3.3") => [
dep_req("to_yank", "3.3.0"),
dep_req("a", "=3.3.0"),
] },
];
let reg = registry(input.clone());
let res = resolve(vec![dep("b")], &reg).unwrap();
let package_to_yank = ("to_yank", "3.0.3").to_pkgid();
// this package is not used in the resolution.
assert!(!res.contains(&package_to_yank));
// so when we yank it
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| package_to_yank != x.package_id())
.collect(),
);
assert_eq!(input.len(), new_reg.len() + 1);
// it should still build
assert!(resolve(vec![dep("b")], &new_reg).is_ok());
}
#[test]
fn resolving_but_no_exists() {
let reg = registry(vec![]);
let res = resolve(vec![dep_req("foo", "1")], &reg);
assert!(res.is_err());
assert_eq!(
res.err().unwrap().to_string(),
"no matching package named `foo` found\n\
location searched: registry `https://example.com/`\n\
required by package `root v1.0.0 (registry `https://example.com/`)`\
"
);
}
#[test]
fn resolving_cycle() {
let reg = registry(vec![pkg!("foo" => ["foo"])]);
let _ = resolve(vec![dep_req("foo", "1")], &reg);
}
#[test]
fn hard_equality() {
let reg = registry(vec![
pkg!(("foo", "1.0.1")),
pkg!(("foo", "1.0.0")),
pkg!(("bar", "1.0.0") => [dep_req("foo", "1.0.0")]),
]);
let res = resolve(vec![dep_req("bar", "1"), dep_req("foo", "=1.0.0")], &reg).unwrap();
assert_same(
&res,
&names(&[("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0")]),
);
}
#[test]
fn large_conflict_cache() {
let mut input = vec![
pkg!(("last", "0.0.0") => [dep("bad")]), // just to make sure last is less constrained
];
let mut root_deps = vec![dep("last")];
const NUM_VERSIONS: u8 = 20;
for name in 0..=NUM_VERSIONS {
// a large number of conflicts can easily be generated by a sys crate.
let sys_name = format!("{}-sys", (b'a' + name) as char);
let in_len = input.len();
input.push(pkg!(("last", format!("{}.0.0", in_len)) => [dep_req(&sys_name, "=0.0.0")]));
root_deps.push(dep_req(&sys_name, ">= 0.0.1"));
// a large number of conflicts can also easily be generated by a major release version.
let plane_name = format!("{}", (b'a' + name) as char);
let in_len = input.len();
input.push(pkg!(("last", format!("{}.0.0", in_len)) => [dep_req(&plane_name, "=1.0.0")]));
root_deps.push(dep_req(&plane_name, ">= 1.0.1"));
for i in 0..=NUM_VERSIONS {
input.push(pkg!((&sys_name, format!("{}.0.0", i))));
input.push(pkg!((&plane_name, format!("1.0.{}", i))));
}
}
let reg = registry(input);
let _ = resolve(root_deps, &reg);
}
#[test]
fn off_by_one_bug() {
let input = vec![
pkg!(("A-sys", "0.0.1")),
pkg!(("A-sys", "0.0.4")),
pkg!(("A-sys", "0.0.6")),
pkg!(("A-sys", "0.0.7")),
pkg!(("NA", "0.0.0") => [dep_req("A-sys", "<= 0.0.5"),]),
pkg!(("NA", "0.0.1") => [dep_req("A-sys", ">= 0.0.6, <= 0.0.8"),]),
pkg!(("a", "0.0.1")),
pkg!(("a", "0.0.2")),
pkg!(("aa", "0.0.0") => [dep_req("A-sys", ">= 0.0.4, <= 0.0.6"),dep_req("NA", "<= 0.0.0"),]),
pkg!(("f", "0.0.3") => [dep("NA"),dep_req("a", "<= 0.0.2"),dep("aa"),]),
];
let reg = registry(input);
let _ = resolve_and_validated(vec![dep("f")], &reg, None);
}
#[test]
fn conflict_store_bug() {
let input = vec![
pkg!(("A", "0.0.3")),
pkg!(("A", "0.0.5")),
pkg!(("A", "0.0.9") => [dep("bad"),]),
pkg!(("A", "0.0.10") => [dep("bad"),]),
pkg!(("L-sys", "0.0.1") => [dep("bad"),]),
pkg!(("L-sys", "0.0.5")),
pkg!(("R", "0.0.4") => [
dep_req("L-sys", "= 0.0.5"),
]),
pkg!(("R", "0.0.6")),
pkg!(("a-sys", "0.0.5")),
pkg!(("a-sys", "0.0.11")),
pkg!(("c", "0.0.12") => [
dep_req("R", ">= 0.0.3, <= 0.0.4"),
]),
pkg!(("c", "0.0.13") => [
dep_req("a-sys", ">= 0.0.8, <= 0.0.11"),
]),
pkg!(("c0", "0.0.6") => [
dep_req("L-sys", "<= 0.0.2"),
]),
pkg!(("c0", "0.0.10") => [
dep_req("A", ">= 0.0.9, <= 0.0.10"),
dep_req("a-sys", "= 0.0.5"),
]),
pkg!("j" => [
dep_req("A", ">= 0.0.3, <= 0.0.5"),
dep_req("R", ">=0.0.4, <= 0.0.6"),
dep_req("c", ">= 0.0.9"),
dep_req("c0", ">= 0.0.6"),
]),
];
let reg = registry(input);
let _ = resolve_and_validated(vec![dep("j")], &reg, None);
}
#[test]
fn conflict_store_more_then_one_match() {
let input = vec![
pkg!(("A", "0.0.0")),
pkg!(("A", "0.0.1")),
pkg!(("A-sys", "0.0.0")),
pkg!(("A-sys", "0.0.1")),
pkg!(("A-sys", "0.0.2")),
pkg!(("A-sys", "0.0.3")),
pkg!(("A-sys", "0.0.12")),
pkg!(("A-sys", "0.0.16")),
pkg!(("B-sys", "0.0.0")),
pkg!(("B-sys", "0.0.1")),
pkg!(("B-sys", "0.0.2") => [dep_req("A-sys", "= 0.0.12"),]),
pkg!(("BA-sys", "0.0.0") => [dep_req("A-sys","= 0.0.16"),]),
pkg!(("BA-sys", "0.0.1") => [dep("bad"),]),
pkg!(("BA-sys", "0.0.2") => [dep("bad"),]),
pkg!("nA" => [
dep("A"),
dep_req("A-sys", "<= 0.0.3"),
dep("B-sys"),
dep("BA-sys"),
]),
];
let reg = registry(input);
let _ = resolve_and_validated(vec![dep("nA")], &reg, None);
}
#[test]
fn cyclic_good_error_message() {
let input = vec![
pkg!(("A", "0.0.0") => [dep("C")]),
pkg!(("B", "0.0.0") => [dep("C")]),
pkg!(("C", "0.0.0") => [dep("A")]),
];
let reg = registry(input);
let error = resolve(vec![dep("A"), dep("B")], &reg).unwrap_err();
println!("{}", error);
assert_eq!("\
cyclic package dependency: package `A v0.0.0 (registry `https://example.com/`)` depends on itself. Cycle:
package `A v0.0.0 (registry `https://example.com/`)`
... which is depended on by `C v0.0.0 (registry `https://example.com/`)`
... which is depended on by `A v0.0.0 (registry `https://example.com/`)`\
", error.to_string());
}