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::(), 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 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::(), ..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::>(), 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::>(), ) } } } } }