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(&reg);
// 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()))],
&reg,
&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(), &reg);
let mres = resolve_with_global_context(deps, &reg, &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(), &reg);
let mres = resolve_with_global_context(deps, &reg, &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()))],
&reg,
).is_ok() {
prop_assert!(
resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&removed_reg,
).is_ok(),
"full index worked for `{} = \"={}\"` but removing some deps broke it!",
this.name(),
this.version(),
)
}
}
}
/// NOTE: if you think this test has failed spuriously see the note at the top of this macro.
#[test]
fn prop_limited_independence_of_irrelevant_alternatives(
PrettyPrintRegistry(input) in registry_strategy(50, 20, 60),
indexes_to_unpublish in prop::collection::vec(any::<prop::sample::Index>(), ..10)
) {
let reg = registry(input.clone());
// There is only a small chance that 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()))],
&reg,
);
match res {
Ok(r) => {
// If resolution was successful, then unpublishing a version of a crate
// that was not selected should not change that.
let not_selected: Vec<_> = input
.iter()
.cloned()
.filter(|x| !r.contains(&x.package_id()))
.collect();
if !not_selected.is_empty() {
let indexes_to_unpublish: Vec<_> = indexes_to_unpublish.iter().map(|x| x.get(&not_selected)).collect();
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| !indexes_to_unpublish.contains(&x))
.collect(),
);
let res = resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&new_reg,
);
// Note: that we can not assert that the two `res` are identical
// as the resolver does depend on irrelevant alternatives.
// It uses how constrained a dependency requirement is
// to determine what order to evaluate requirements.
prop_assert!(
res.is_ok(),
"unpublishing {:?} stopped `{} = \"={}\"` from working",
indexes_to_unpublish.iter().map(|x| x.package_id()).collect::<Vec<_>>(),
this.name(),
this.version()
)
}
}
Err(_) => {
// If resolution was unsuccessful, then it should stay unsuccessful
// even if any version of a crate is unpublished.
let indexes_to_unpublish: Vec<_> = indexes_to_unpublish.iter().map(|x| x.get(&input)).collect();
let new_reg = registry(
input
.iter()
.cloned()
.filter(|x| !indexes_to_unpublish.contains(&x))
.collect(),
);
let res = resolve(
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&new_reg,
);
prop_assert!(
res.is_err(),
"full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!",
this.name(),
this.version(),
indexes_to_unpublish.iter().map(|x| x.package_id()).collect::<Vec<_>>(),
)
}
}
}
}
}