diff --git a/.gitignore b/.gitignore index add90d768b9..5274e5d529f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ -/target -/tests/testsuite/support/cargo-test-macro/target +target Cargo.lock .cargo /config.stamp diff --git a/.travis.yml b/.travis.yml index c093938a35b..35dd6011943 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,13 @@ matrix: - (cd src/doc && mdbook build --dest-dir ../../target/doc) || travis_terminate 1 if: branch != master OR type = pull_request + - name: resolver tests + rust: stable + before_script: true + script: + - cargo test --manifest-path crates/resolver-tests/Cargo.toml + if: branch != master OR type = pull_request + exclude: - rust: stable diff --git a/Cargo.toml b/Cargo.toml index 4ed5dc922ab..d15aa2513f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ path = "src/cargo/lib.rs" atty = "0.2" byteorder = "1.2" bytesize = "1.0" -crates-io = { path = "src/crates-io", version = "0.26" } +crates-io = { path = "crates/crates-io", version = "0.26" } crossbeam-utils = "0.6" crypto-hash = "0.3.1" curl = { version = "0.4.21", features = ['http2'] } @@ -102,9 +102,7 @@ features = [ [dev-dependencies] bufstream = "0.1" -proptest = "0.9.1" -varisat = "0.2.1" -cargo-test-macro = { "path" = "tests/testsuite/support/cargo-test-macro", version = "0.1.0" } +cargo-test-macro = { path = "crates/cargo-test-macro", version = "0.1.0" } [[bin]] name = "cargo" diff --git a/tests/testsuite/support/cargo-test-macro/Cargo.toml b/crates/cargo-test-macro/Cargo.toml similarity index 100% rename from tests/testsuite/support/cargo-test-macro/Cargo.toml rename to crates/cargo-test-macro/Cargo.toml diff --git a/tests/testsuite/support/cargo-test-macro/src/lib.rs b/crates/cargo-test-macro/src/lib.rs similarity index 100% rename from tests/testsuite/support/cargo-test-macro/src/lib.rs rename to crates/cargo-test-macro/src/lib.rs diff --git a/src/crates-io/Cargo.toml b/crates/crates-io/Cargo.toml similarity index 100% rename from src/crates-io/Cargo.toml rename to crates/crates-io/Cargo.toml diff --git a/src/crates-io/LICENSE-APACHE b/crates/crates-io/LICENSE-APACHE similarity index 100% rename from src/crates-io/LICENSE-APACHE rename to crates/crates-io/LICENSE-APACHE diff --git a/src/crates-io/LICENSE-MIT b/crates/crates-io/LICENSE-MIT similarity index 100% rename from src/crates-io/LICENSE-MIT rename to crates/crates-io/LICENSE-MIT diff --git a/src/crates-io/lib.rs b/crates/crates-io/lib.rs similarity index 100% rename from src/crates-io/lib.rs rename to crates/crates-io/lib.rs diff --git a/crates/resolver-tests/Cargo.toml b/crates/resolver-tests/Cargo.toml new file mode 100644 index 00000000000..1d98c6973cc --- /dev/null +++ b/crates/resolver-tests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "resolver-tests" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[dependencies] +cargo = { path = "../.." } +proptest = "0.9.1" +lazy_static = "1.3.0" +varisat = "0.2.1" +atty = "0.2.11" diff --git a/tests/testsuite/support/resolver.rs b/crates/resolver-tests/src/lib.rs similarity index 99% rename from tests/testsuite/support/resolver.rs rename to crates/resolver-tests/src/lib.rs index dc8ea5199d7..f13cfb72330 100644 --- a/tests/testsuite/support/resolver.rs +++ b/crates/resolver-tests/src/lib.rs @@ -4,8 +4,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt; use std::time::Instant; -use crate::support::slow_cpu_multiplier; - use cargo::core::dependency::Kind; use cargo::core::resolver::{self, Method}; use cargo::core::source::{GitReference, SourceId}; @@ -16,9 +14,7 @@ use cargo::util::{CargoResult, Config, Graph, ToUrl}; use proptest::collection::{btree_map, vec}; use proptest::prelude::*; use proptest::sample::Index; -use proptest::strategy::ValueTree; use proptest::string::string_regex; -use proptest::test_runner::TestRunner; use varisat::{self, ExtendFormula}; pub fn resolve( @@ -182,7 +178,7 @@ pub fn resolve_with_config_raw( // The largest test in our suite takes less then 30 sec. // So lets fail the test if we have ben running for two long. - assert!(start.elapsed() < slow_cpu_multiplier(60)); + assert!(start.elapsed().as_secs() < 60); resolve } @@ -493,14 +489,15 @@ impl, U: AsRef> ToPkgId for (T, U) { } } +#[macro_export] macro_rules! pkg { ($pkgid:expr => [$($deps:expr),+ $(,)* ]) => ({ let d: Vec = vec![$($deps.to_dep()),+]; - pkg_dep($pkgid, d) + $crate::pkg_dep($pkgid, d) }); ($pkgid:expr) => ({ - pkg($pkgid) + $crate::pkg($pkgid) }) } @@ -663,7 +660,7 @@ impl fmt::Debug for PrettyPrintRegistry { } } -#[cargo_test] +#[test] fn meta_test_deep_pretty_print_registry() { assert_eq!( &format!( @@ -839,8 +836,11 @@ pub fn registry_strategy( /// This test is to test the generator to ensure /// that it makes registries with large dependency trees -#[cargo_test] +#[test] fn meta_test_deep_trees_from_strategy() { + use proptest::strategy::ValueTree; + use proptest::test_runner::TestRunner; + let mut dis = [0; 21]; let strategy = registry_strategy(50, 20, 60); @@ -878,8 +878,11 @@ fn meta_test_deep_trees_from_strategy() { /// This test is to test the generator to ensure /// that it makes registries that include multiple versions of the same library -#[cargo_test] +#[test] fn meta_test_multiple_versions_strategy() { + use proptest::strategy::ValueTree; + use proptest::test_runner::TestRunner; + let mut dis = [0; 10]; let strategy = registry_strategy(50, 20, 60); diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs new file mode 100644 index 00000000000..6efe6d71114 --- /dev/null +++ b/crates/resolver-tests/tests/resolve.rs @@ -0,0 +1,1462 @@ +use std::env; + +use cargo::core::dependency::Kind; +use cargo::core::{enable_nightly_features, Dependency}; +use cargo::util::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 env::var("CI").is_ok() || !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 mut sat_resolve = SatResolve::new(®); + // 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( + pkg_id("root"), + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + Some(&mut sat_resolve), + ); + } + } + + /// 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( + pkg_id("root"), + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + ); + + let mres = resolve_with_config( + pkg_id("root"), + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + 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::(), any::()), ..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( + pkg_id("root"), + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + ).is_ok() { + prop_assert!( + resolve( + pkg_id("root"), + 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::(), ..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( + pkg_id("root"), + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + ); + + 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(¬_selected)).collect(); + + let new_reg = registry( + input + .iter() + .cloned() + .filter(|x| !indexes_to_unpublish.contains(&x)) + .collect(), + ); + + let res = resolve( + pkg_id("root"), + 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::>(), + 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( + pkg_id("root"), + 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::>(), + ) + } + } + } + } +} + +#[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(pkg_id("root"), vec![dep("kB")], ®, 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(pkg_id("root"), vec![dep("C")], ®, 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(pkg_id("root"), vec![dep("d")], ®, 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(pkg_id("root"), vec![dep("D")], ®, 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(pkg_id("root"), vec![dep("c")], ®, 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(pkg_id("root"), vec![dep("C")], ®, 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(pkg_id("root"), vec![dep("A")], ®, 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(pkg_id("root"), vec![dep("c")], ®, 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(pkg_id("root"), vec![dep("A")], ®, 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(pkg_id("root"), Vec::new(), ®istry(vec![])).unwrap(); + + assert_eq!(res, names(&["root"])); +} + +#[test] +fn test_resolving_only_package() { + let reg = registry(vec![pkg!("foo")]); + let res = resolve(pkg_id("root"), vec![dep("foo")], ®).unwrap(); + assert_same(&res, &names(&["root", "foo"])); +} + +#[test] +fn test_resolving_one_dep() { + let reg = registry(vec![pkg!("foo"), pkg!("bar")]); + let res = resolve(pkg_id("root"), vec![dep("foo")], ®).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(pkg_id("root"), vec![dep("foo"), dep("baz")], ®).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(pkg_id("root"), vec![dep("bar")], ®).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(pkg_id("root"), vec![dep("foo"), dep("bar")], ®).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( + pkg_id("root"), + vec![ + dep_loc("foo", "https://first.example.com"), + dep_loc("bar", "https://second.example.com"), + ], + ®, + ) + .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( + pkg_id("root"), + vec![dep("foo"), dep_kind("baz", Kind::Development)], + ®, + ) + .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(pkg_id("root"), vec![dep("foo")], ®).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(pkg_id("root"), vec![dep_req("foo", "=1.0.1")], ®).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( + pkg_id("root"), + vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], + ®, + ) + .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( + pkg_id("root"), + vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], + ®, + 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( + pkg_id("root"), + vec![dep_req("foo", "=1.0.1"), dep("bar")], + ® + ) + .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(pkg_id("root"), vec![dep("bar")], ®).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(pkg_id("root"), vec![dep("bar")], ®).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(pkg_id("root"), vec![dep_req("foo", "^1")], ®).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(pkg_id("root"), vec![dep_req("foo", "^1")], ®).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(pkg_id("root"), vec![dep("bar")], ®).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(pkg_id("root"), vec![dep_req("foo", "1")], ®).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( + pkg_id("root"), + vec![dep_req("d", "1"), dep_req("r", "1")], + ®, + ) + .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(pkg_id("root"), vec![dep_req("foo", "1")], ®).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(pkg_id("root"), vec![dep("level0")], ®); + + 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(pkg_id("root"), vec![dep("level0")], ®).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( + pkg_id("root"), + vec![dep("level0"), dep("constrained")], + ®, + ) + .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( + pkg_id("root"), + vec![dep_req("level0", "1.0.1"), dep("constrained")], + ®, + ) + .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( + pkg_id("root"), + vec![dep_req("level0", "1.0.1"), dep_req("constrained", "1.1.0")], + ®, + ); + + 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( + pkg_id("root"), + vec![dep("backtrack_trap0"), dep("cloaking")], + ®, + ); + + 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( + pkg_id("root"), + vec![ + dep("backtrack_trap0"), + dep_req("constrained", "2.0.1"), + dep("cloaking"), + ], + ®, + ); + + 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( + pkg_id("root"), + vec![dep("level0"), dep_req("constrained", "2.0.1")], + ®, + ); + + assert!(res.is_err()); + + let res = resolve( + pkg_id("root"), + vec![dep("level0"), dep_req("constrained", "2.0.0")], + ®, + ) + .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(pkg_id("root"), vec![dep_req("foo", "1")], ®).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_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(pkg_id("root"), vec![dep_req("A", "1")], ®).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(pkg_id("root"), vec![dep("g")], ®).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(pkg_id("root"), 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(pkg_id("root"), vec![dep("i")], ®).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(pkg_id("root"), 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(pkg_id("root"), vec![dep("b")], ®).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(pkg_id("root"), vec![dep("b")], &new_reg).is_ok()); +} + +#[test] +fn resolving_but_no_exists() { + let reg = registry(vec![]); + + let res = resolve(pkg_id("root"), vec![dep_req("foo", "1")], ®); + 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(pkg_id("root"), vec![dep_req("foo", "1")], ®); +} + +#[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( + pkg_id("root"), + vec![dep_req("bar", "1"), dep_req("foo", "=1.0.0")], + ®, + ) + .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(pkg_id("root"), root_deps, ®); +} + +#[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(pkg_id("root"), vec![dep("j")], ®, 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(pkg_id("root"), vec![dep("nA")], ®, None); +} diff --git a/tests/testsuite/resolve.rs b/tests/testsuite/resolve.rs index b004d1e4bf6..ae428d9a99f 100644 --- a/tests/testsuite/resolve.rs +++ b/tests/testsuite/resolve.rs @@ -1,622 +1,5 @@ -use std::env; - -use cargo::core::dependency::Kind; -use cargo::core::{enable_nightly_features, Dependency}; -use cargo::util::Config; - use crate::support::project; use crate::support::registry::Package; -use crate::support::resolver::{ - assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, dep_req_kind, loc_names, names, - pkg, pkg_dep, 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 env::var("CI").is_ok() || !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 mut sat_resolve = SatResolve::new(®); - // 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( - pkg_id("root"), - vec![dep_req(&this.name(), &format!("={}", this.version()))], - ®, - Some(&mut sat_resolve), - ); - } - } - - /// 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( - pkg_id("root"), - vec![dep_req(&this.name(), &format!("={}", this.version()))], - ®, - ); - - let mres = resolve_with_config( - pkg_id("root"), - vec![dep_req(&this.name(), &format!("={}", this.version()))], - ®, - 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::(), any::()), ..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( - pkg_id("root"), - vec![dep_req(&this.name(), &format!("={}", this.version()))], - ®, - ).is_ok() { - prop_assert!( - resolve( - pkg_id("root"), - 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::(), ..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( - pkg_id("root"), - vec![dep_req(&this.name(), &format!("={}", this.version()))], - ®, - ); - - 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(¬_selected)).collect(); - - let new_reg = registry( - input - .iter() - .cloned() - .filter(|x| !indexes_to_unpublish.contains(&x)) - .collect(), - ); - - let res = resolve( - pkg_id("root"), - 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::>(), - 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( - pkg_id("root"), - 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::>(), - ) - } - } - } - } -} - -#[cargo_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); - assert!(resolve_and_validated(pkg_id("root"), vec![dep("kB")], ®, None).is_err()); -} - -#[cargo_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(pkg_id("root"), vec![dep("C")], ®, None).unwrap(); - assert_same( - &res, - &names(&[ - ("root", "1.0.0"), - ("C", "1.0.0"), - ("B", "1.0.0"), - ("A", "0.1.0"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep("d")], ®, 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"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep("D")], ®, 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"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep("c")], ®, None).unwrap(); -} - -#[cargo_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(pkg_id("root"), vec![dep("C")], ®, None).unwrap(); -} - -#[cargo_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(pkg_id("root"), vec![dep("A")], ®, None).is_err()); -} - -#[cargo_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(pkg_id("root"), vec![dep("c")], ®, None).unwrap(); -} - -#[cargo_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(pkg_id("root"), vec![dep("A")], ®, None).unwrap(); -} - -#[cargo_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(); -} - -#[cargo_test] -fn test_resolving_empty_dependency_list() { - let res = resolve(pkg_id("root"), Vec::new(), ®istry(vec![])).unwrap(); - - assert_eq!(res, names(&["root"])); -} - -#[cargo_test] -fn test_resolving_only_package() { - let reg = registry(vec![pkg!("foo")]); - let res = resolve(pkg_id("root"), vec![dep("foo")], ®).unwrap(); - assert_same(&res, &names(&["root", "foo"])); -} - -#[cargo_test] -fn test_resolving_one_dep() { - let reg = registry(vec![pkg!("foo"), pkg!("bar")]); - let res = resolve(pkg_id("root"), vec![dep("foo")], ®).unwrap(); - assert_same(&res, &names(&["root", "foo"])); -} - -#[cargo_test] -fn test_resolving_multiple_deps() { - let reg = registry(vec![pkg!("foo"), pkg!("bar"), pkg!("baz")]); - let res = resolve(pkg_id("root"), vec![dep("foo"), dep("baz")], ®).unwrap(); - assert_same(&res, &names(&["root", "foo", "baz"])); -} - -#[cargo_test] -fn test_resolving_transitive_deps() { - let reg = registry(vec![pkg!("foo"), pkg!("bar" => ["foo"])]); - let res = resolve(pkg_id("root"), vec![dep("bar")], ®).unwrap(); - - assert_same(&res, &names(&["root", "foo", "bar"])); -} - -#[cargo_test] -fn test_resolving_common_transitive_deps() { - let reg = registry(vec![pkg!("foo" => ["bar"]), pkg!("bar")]); - let res = resolve(pkg_id("root"), vec![dep("foo"), dep("bar")], ®).unwrap(); - - assert_same(&res, &names(&["root", "foo", "bar"])); -} - -#[cargo_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( - pkg_id("root"), - vec![ - dep_loc("foo", "https://first.example.com"), - dep_loc("bar", "https://second.example.com"), - ], - ®, - ) - .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); -} - -#[cargo_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( - pkg_id("root"), - vec![dep("foo"), dep_kind("baz", Kind::Development)], - ®, - ) - .unwrap(); - - assert_same(&res, &names(&["root", "foo", "bar", "baz", "bat"])); -} - -#[cargo_test] -fn resolving_with_many_versions() { - let reg = registry(vec![pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2"))]); - - let res = resolve(pkg_id("root"), vec![dep("foo")], ®).unwrap(); - - assert_same(&res, &names(&[("root", "1.0.0"), ("foo", "1.0.2")])); -} - -#[cargo_test] -fn resolving_with_specific_version() { - let reg = registry(vec![pkg!(("foo", "1.0.1")), pkg!(("foo", "1.0.2"))]); - - let res = resolve(pkg_id("root"), vec![dep_req("foo", "=1.0.1")], ®).unwrap(); - - assert_same(&res, &names(&[("root", "1.0.0"), ("foo", "1.0.1")])); -} - -#[cargo_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( - pkg_id("root"), - vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], - ®, - ) - .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())); -} - -#[cargo_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( - pkg_id("root"), - vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")], - ®, - 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())); -} // Ensure that the "-Z minimal-versions" CLI option works and the minimal // version of a dependency ends up in the lock file. @@ -649,848 +32,3 @@ fn minimal_version_cli() { assert!(lock.contains("dep 1.0.0")); } - -#[cargo_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( - pkg_id("root"), - vec![dep_req("foo", "=1.0.1"), dep("bar")], - ® - ) - .is_err()); -} - -#[cargo_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(pkg_id("root"), vec![dep("bar")], ®).is_err()); -} - -#[cargo_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(pkg_id("root"), vec![dep("bar")], ®).is_err()); -} - -#[cargo_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(pkg_id("root"), vec![dep_req("foo", "^1")], ®).unwrap(); - - assert_contains( - &res, - &names(&[("root", "1.0.0"), ("foo", "1.0.1"), ("baz", "1.0.0")]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep_req("foo", "^1")], ®).unwrap(); - - assert_contains( - &res, - &names(&[("root", "1.0.0"), ("foo", "1.0.1"), ("bar", "1.0.0")]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep("bar")], ®).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"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep_req("foo", "1")], ®).unwrap(); - - assert_same( - &res, - &names(&[ - ("root", "1.0.0"), - ("foo", "1.0.0"), - ("bar", "2.0.0"), - ("baz", "1.0.1"), - ]), - ); -} - -#[cargo_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( - pkg_id("root"), - vec![dep_req("d", "1"), dep_req("r", "1")], - ®, - ) - .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"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep_req("foo", "1")], ®).unwrap(); - - assert_contains( - &res, - &names(&[ - ("root", "1.0.0"), - ("foo", "1.0.0"), - ("bar", "1.0.0"), - ("constrained", "1.0.0"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep("level0")], ®); - - 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(pkg_id("root"), vec![dep("level0")], ®).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( - pkg_id("root"), - vec![dep("level0"), dep("constrained")], - ®, - ) - .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( - pkg_id("root"), - vec![dep_req("level0", "1.0.1"), dep("constrained")], - ®, - ) - .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( - pkg_id("root"), - vec![dep_req("level0", "1.0.1"), dep_req("constrained", "1.1.0")], - ®, - ); - - assert!(res.is_err()); -} - -#[cargo_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( - pkg_id("root"), - vec![dep("backtrack_trap0"), dep("cloaking")], - ®, - ); - - assert!(res.is_err()); -} - -#[cargo_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( - pkg_id("root"), - vec![ - dep("backtrack_trap0"), - dep_req("constrained", "2.0.1"), - dep("cloaking"), - ], - ®, - ); - - 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( - pkg_id("root"), - vec![dep("level0"), dep_req("constrained", "2.0.1")], - ®, - ); - - assert!(res.is_err()); - - let res = resolve( - pkg_id("root"), - vec![dep("level0"), dep_req("constrained", "2.0.0")], - ®, - ) - .unwrap(); - - assert_contains( - &res, - &names(&[("constrained", "2.0.0"), ("cloaking", "1.0.0")]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep_req("foo", "1")], ®).unwrap(); - - assert_contains( - &res, - &names(&[ - ("root", "1.0.0"), - ("foo", "1.0.0"), - ("bar", "1.0.0"), - ("constrained", "1.0.60"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep_req("A", "1")], ®).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"), - ]), - ); -} - -#[cargo_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(pkg_id("root"), vec![dep("g")], ®).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(pkg_id("root"), vec![dep("g")], &new_reg).is_ok()); -} - -#[cargo_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(pkg_id("root"), vec![dep("i")], ®).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(pkg_id("root"), vec![dep("i")], &new_reg).is_ok()); -} - -#[cargo_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(pkg_id("root"), vec![dep("b")], ®).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(pkg_id("root"), vec![dep("b")], &new_reg).is_ok()); -} - -#[cargo_test] -fn resolving_but_no_exists() { - let reg = registry(vec![]); - - let res = resolve(pkg_id("root"), vec![dep_req("foo", "1")], ®); - 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/`)`\ - " - ); -} - -#[cargo_test] -fn resolving_cycle() { - let reg = registry(vec![pkg!("foo" => ["foo"])]); - - let _ = resolve(pkg_id("root"), vec![dep_req("foo", "1")], ®); -} - -#[cargo_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( - pkg_id("root"), - vec![dep_req("bar", "1"), dep_req("foo", "=1.0.0")], - ®, - ) - .unwrap(); - - assert_same( - &res, - &names(&[("root", "1.0.0"), ("foo", "1.0.0"), ("bar", "1.0.0")]), - ); -} - -#[cargo_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(pkg_id("root"), root_deps, ®); -} - -#[cargo_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(pkg_id("root"), vec![dep("j")], ®, None); -} - -#[cargo_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(pkg_id("root"), vec![dep("nA")], ®, None); -} diff --git a/tests/testsuite/support/mod.rs b/tests/testsuite/support/mod.rs index 9272b9a3ed1..cd09ea240ad 100644 --- a/tests/testsuite/support/mod.rs +++ b/tests/testsuite/support/mod.rs @@ -139,8 +139,6 @@ pub mod git; pub mod paths; pub mod publish; pub mod registry; -#[macro_use] -pub mod resolver; /* *