mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
274 lines
11 KiB
Rust
274 lines
11 KiB
Rust
use std::io::IsTerminal;
|
|
|
|
use cargo::util::GlobalContext;
|
|
use cargo_util::is_ci;
|
|
|
|
use resolver_tests::{
|
|
helpers::{dep_req, registry, remove_dep},
|
|
registry_strategy, resolve, resolve_and_validated, resolve_with_global_context,
|
|
sat::SatResolver,
|
|
PrettyPrintRegistry,
|
|
};
|
|
|
|
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() || !std::io::stderr().is_terminal() {
|
|
// This attempts to make sure that CI will fail fast,
|
|
0
|
|
} else {
|
|
// but that local builds will give a small clear test case.
|
|
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_resolver = SatResolver::new(®);
|
|
|
|
// There is only a small chance that a 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()))],
|
|
®,
|
|
&mut sat_resolver,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 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)
|
|
) {
|
|
let mut gctx = GlobalContext::default().unwrap();
|
|
gctx.nightly_features_allowed = true;
|
|
gctx
|
|
.configure(
|
|
1,
|
|
false,
|
|
None,
|
|
false,
|
|
false,
|
|
false,
|
|
&None,
|
|
&["minimal-versions".to_string()],
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
|
|
let reg = registry(input.clone());
|
|
|
|
// There is only a small chance that a crate will be interesting.
|
|
// So we try some of the most complicated.
|
|
for this in input.iter().rev().take(10) {
|
|
let deps = vec![dep_req(&this.name(), &format!("={}", this.version()))];
|
|
let res = resolve(deps.clone(), ®);
|
|
let mres = resolve_with_global_context(deps, ®, &gctx);
|
|
|
|
// `minimal-versions` changes what order the candidates are tried but not the existence of a solution.
|
|
prop_assert_eq!(
|
|
res.is_ok(),
|
|
mres.is_ok(),
|
|
"minimal-versions and regular resolver disagree about whether `{} = \"={}\"` 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_direct_minimum_version_error_implications(
|
|
PrettyPrintRegistry(input) in registry_strategy(50, 20, 60)
|
|
) {
|
|
let mut gctx = GlobalContext::default().unwrap();
|
|
gctx.nightly_features_allowed = true;
|
|
gctx
|
|
.configure(
|
|
1,
|
|
false,
|
|
None,
|
|
false,
|
|
false,
|
|
false,
|
|
&None,
|
|
&["direct-minimal-versions".to_string()],
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
|
|
let reg = registry(input.clone());
|
|
|
|
// There is only a small chance that a crate will be interesting.
|
|
// So we try some of the most complicated.
|
|
for this in input.iter().rev().take(10) {
|
|
let deps = vec![dep_req(&this.name(), &format!("={}", this.version()))];
|
|
let res = resolve(deps.clone(), ®);
|
|
let mres = resolve_with_global_context(deps, ®, &gctx);
|
|
|
|
// `direct-minimal-versions` reduces the number of available solutions,
|
|
// so we verify that we do not come up with solutions not seen in `maximal-versions`.
|
|
if res.is_err() {
|
|
prop_assert!(
|
|
mres.is_err(),
|
|
"direct-minimal-versions should not have more solutions than the regular, maximal resolver but found one when resolving `{} = \"={}\"`",
|
|
this.name(),
|
|
this.version()
|
|
)
|
|
}
|
|
if mres.is_ok() {
|
|
prop_assert!(
|
|
res.is_ok(),
|
|
"direct-minimal-versions should not have more solutions than the regular, maximal resolver but found one when resolving `{} = \"={}\"`",
|
|
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 a 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()))],
|
|
®,
|
|
).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 a 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()))],
|
|
®,
|
|
);
|
|
|
|
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(
|
|
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<_>>(),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|