diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 819aec9c2d5..f34b4cf23e3 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -300,8 +300,8 @@ fn activate_deps_loop( // Reset all of our local variables used with the // contents of `frame` to complete our backtrack. cur = frame.cur; - cx = frame.context_backup; - remaining_deps = frame.deps_backup; + cx = frame.context; + remaining_deps = frame.remaining_deps; remaining_candidates = frame.remaining_candidates; parent = frame.parent; dep = frame.dep; @@ -345,8 +345,8 @@ fn activate_deps_loop( let backtrack = if has_another { Some(BacktrackFrame { cur, - context_backup: Context::clone(&cx), - deps_backup: remaining_deps.clone(), + context: Context::clone(&cx), + remaining_deps: remaining_deps.clone(), remaining_candidates: remaining_candidates.clone(), parent: Summary::clone(&parent), dep: Dependency::clone(&dep), @@ -555,7 +555,7 @@ fn activate_deps_loop( // for error messages anyway so we can live with a little // imprecision. if let Some(b) = backtrack { - cx = b.context_backup; + cx = b.context; } } @@ -629,8 +629,8 @@ fn activate( #[derive(Clone)] struct BacktrackFrame { cur: usize, - context_backup: Context, - deps_backup: RemainingDeps, + context: Context, + remaining_deps: RemainingDeps, remaining_candidates: RemainingCandidates, parent: Summary, dep: Dependency, @@ -780,7 +780,7 @@ fn find_candidate( while let Some(mut frame) = backtrack_stack.pop() { let next = frame.remaining_candidates.next( &mut frame.conflicting_activations, - &frame.context_backup, + &frame.context, &frame.dep, ); let (candidate, has_another) = match next { @@ -798,7 +798,7 @@ fn find_candidate( // completely skip this backtrack frame and move on to the next. if !backtracked { if frame - .context_backup + .context .is_conflicting(Some(parent.package_id()), conflicting_activations) { trace!( diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs index a7ab6e321b7..b9494546db7 100644 --- a/src/cargo/core/resolver/types.rs +++ b/src/cargo/core/resolver/types.rs @@ -47,12 +47,12 @@ impl ResolverProgress { config.shell().status("Resolving", "dependency graph...")?; } } - // The largest test in our sweet takes less then 5000 ticks + // The largest test in our suite takes less then 5000 ticks // with all the algorithm improvements. // If any of them are removed then it takes more than I am willing to measure. // So lets fail the test fast if we have ben running for two long. debug_assert!(self.ticks < 50_000); - // The largest test in our sweet takes less then 30 sec + // The largest test in our suite takes less then 30 sec // with all the improvements to how fast a tick can go. // If any of them are removed then it takes more than I am willing to measure. // So lets fail the test fast if we have ben running for two long. diff --git a/tests/testsuite/resolve.rs b/tests/testsuite/resolve.rs index ba050f094f0..eb8713094e9 100644 --- a/tests/testsuite/resolve.rs +++ b/tests/testsuite/resolve.rs @@ -1,405 +1,19 @@ -use std::cmp::PartialEq; -use std::cmp::{max, min}; -use std::collections::{BTreeMap, HashSet}; use std::env; -use std::fmt; -use std::time::{Duration, Instant}; -use cargo::core::dependency::Kind::{self, Development}; -use cargo::core::resolver::{self, Method}; -use cargo::core::source::{GitReference, SourceId}; -use cargo::core::{enable_nightly_features, Dependency, PackageId, Registry, Summary}; -use cargo::util::{CargoResult, Config, ToUrl}; +use cargo::core::dependency::Kind::Development; +use cargo::core::{enable_nightly_features, Dependency}; +use cargo::util::Config; use support::project; use support::registry::Package; +use support::resolver::{ + assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names, names, pkg, pkg_dep, + pkg_id, pkg_loc, registry, registry_strategy, resolve, resolve_with_config, + PrettyPrintRegistry, ToDep, ToPkgId, +}; -use proptest::collection::{btree_map, btree_set, vec}; +use proptest::collection::vec; use proptest::prelude::*; -use proptest::sample::Index; -use proptest::strategy::ValueTree; -use proptest::string::string_regex; -use proptest::test_runner::TestRunner; - -fn resolve( - pkg: &PackageId, - deps: Vec, - registry: &[Summary], -) -> CargoResult> { - resolve_with_config(pkg, deps, registry, None) -} - -fn resolve_with_config( - pkg: &PackageId, - deps: Vec, - registry: &[Summary], - config: Option<&Config>, -) -> CargoResult> { - struct MyRegistry<'a>(&'a [Summary]); - impl<'a> Registry for MyRegistry<'a> { - fn query( - &mut self, - dep: &Dependency, - f: &mut FnMut(Summary), - fuzzy: bool, - ) -> CargoResult<()> { - for summary in self.0.iter() { - if fuzzy || dep.matches(summary) { - f(summary.clone()); - } - } - Ok(()) - } - } - let mut registry = MyRegistry(registry); - let summary = Summary::new( - pkg.clone(), - deps, - &BTreeMap::>::new(), - None::, - false, - ).unwrap(); - let method = Method::Everything; - let start = Instant::now(); - let resolve = resolver::resolve( - &[(summary, method)], - &[], - &mut registry, - &HashSet::new(), - config, - false, - )?; - - // The largest test in our sweet takes less then 30 sec. - // So lets fail the test if we have ben running for two long. - assert!(start.elapsed() < Duration::from_secs(60)); - let res = resolve.iter().cloned().collect(); - Ok(res) -} - -trait ToDep { - fn to_dep(self) -> Dependency; -} - -impl ToDep for &'static str { - fn to_dep(self) -> Dependency { - Dependency::parse_no_deprecated(self, Some("1.0.0"), ®istry_loc()).unwrap() - } -} - -impl ToDep for Dependency { - fn to_dep(self) -> Dependency { - self - } -} - -trait ToPkgId { - fn to_pkgid(&self) -> PackageId; -} - -impl ToPkgId for PackageId { - fn to_pkgid(&self) -> PackageId { - self.clone() - } -} - -impl<'a> ToPkgId for &'a str { - fn to_pkgid(&self) -> PackageId { - PackageId::new(*self, "1.0.0", ®istry_loc()).unwrap() - } -} - -impl, U: AsRef> ToPkgId for (T, U) { - fn to_pkgid(&self) -> PackageId { - let (name, vers) = self; - PackageId::new(name.as_ref(), vers.as_ref(), ®istry_loc()).unwrap() - } -} - -macro_rules! pkg { - ($pkgid:expr => [$($deps:expr),+ $(,)* ]) => ({ - let d: Vec = vec![$($deps.to_dep()),+]; - pkg_dep($pkgid, d) - }); - - ($pkgid:expr) => ({ - pkg($pkgid) - }) -} - -fn registry_loc() -> SourceId { - lazy_static! { - static ref EXAMPLE_DOT_COM: SourceId = - SourceId::for_registry(&"http://example.com".to_url().unwrap()).unwrap(); - } - EXAMPLE_DOT_COM.clone() -} - -fn pkg(name: T) -> Summary { - pkg_dep(name, Vec::new()) -} - -fn pkg_dep(name: T, dep: Vec) -> Summary { - let pkgid = name.to_pkgid(); - let link = if pkgid.name().ends_with("-sys") { - Some(pkgid.name().as_str()) - } else { - None - }; - Summary::new( - name.to_pkgid(), - dep, - &BTreeMap::>::new(), - link, - false, - ).unwrap() -} - -fn pkg_id(name: &str) -> PackageId { - PackageId::new(name, "1.0.0", ®istry_loc()).unwrap() -} - -fn pkg_id_loc(name: &str, loc: &str) -> PackageId { - let remote = loc.to_url(); - let master = GitReference::Branch("master".to_string()); - let source_id = SourceId::for_git(&remote.unwrap(), master).unwrap(); - - PackageId::new(name, "1.0.0", &source_id).unwrap() -} - -fn pkg_loc(name: &str, loc: &str) -> Summary { - let link = if name.ends_with("-sys") { - Some(name) - } else { - None - }; - Summary::new( - pkg_id_loc(name, loc), - Vec::new(), - &BTreeMap::>::new(), - link, - false, - ).unwrap() -} - -fn dep(name: &str) -> Dependency { - dep_req(name, "1.0.0") -} -fn dep_req(name: &str, req: &str) -> Dependency { - Dependency::parse_no_deprecated(name, Some(req), ®istry_loc()).unwrap() -} - -fn dep_loc(name: &str, location: &str) -> Dependency { - let url = location.to_url().unwrap(); - let master = GitReference::Branch("master".to_string()); - let source_id = SourceId::for_git(&url, master).unwrap(); - Dependency::parse_no_deprecated(name, Some("1.0.0"), &source_id).unwrap() -} -fn dep_kind(name: &str, kind: Kind) -> Dependency { - dep(name).set_kind(kind).clone() -} - -fn registry(pkgs: Vec) -> Vec { - pkgs -} - -fn names(names: &[P]) -> Vec { - names.iter().map(|name| name.to_pkgid()).collect() -} - -fn loc_names(names: &[(&'static str, &'static str)]) -> Vec { - names - .iter() - .map(|&(name, loc)| pkg_id_loc(name, loc)) - .collect() -} - -/// By default `Summary` and `Dependency` have a very verbose `Debug` representation. -/// This replaces with a representation that uses constructors from this file. -/// -/// If `registry_strategy` is improved to modify more fields -/// then this needs to update to display the corresponding constructor. -struct PrettyPrintRegistry(Vec); - -impl fmt::Debug for PrettyPrintRegistry { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "vec![")?; - for s in &self.0 { - if s.dependencies().is_empty() { - write!(f, "pkg!((\"{}\", \"{}\")),", s.name(), s.version())?; - } else { - write!(f, "pkg!((\"{}\", \"{}\") => [", s.name(), s.version())?; - for d in s.dependencies() { - write!( - f, - "dep_req(\"{}\", \"{}\"),", - d.name_in_toml(), - d.version_req() - )?; - } - write!(f, "]),")?; - } - } - write!(f, "]") - } -} - -#[test] -fn meta_test_deep_pretty_print_registry() { - assert_eq!( - &format!( - "{:?}", - PrettyPrintRegistry(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")), - ]) - ), - "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\")),]" - ) -} - -/// This generates a random registry index. -/// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..) -/// This strategy has a high probability of having valid dependencies -fn registry_strategy( - max_crates: usize, - max_versions: usize, - shrinkage: usize, -) -> impl Strategy { - let name = string_regex("[A-Za-z_-][A-Za-z0-9_-]*(-sys)?").unwrap(); - - let raw_version = [..max_versions; 3]; - let version_from_raw = |v: &[usize; 3]| format!("{}.{}.{}", v[0], v[1], v[2]); - - // If this is false than the crate will depend on the nonexistent "bad" - // instead of the complex set we generated for it. - let allow_deps = prop::bool::weighted(0.99); - - let list_of_versions = - btree_set((raw_version, allow_deps), 1..=max_versions).prop_map(move |ver| { - ver.iter() - .map(|a| (version_from_raw(&a.0), a.1)) - .collect::>() - }); - - let list_of_crates_with_versions = - btree_map(name, list_of_versions, 1..=max_crates).prop_map(|mut vers| { - // root is the name of the thing being compiled - // so it would be confusing to have it in the index - vers.remove("root"); - // bad is a name reserved for a dep that won't work - vers.remove("bad"); - vers - }); - - // each version of each crate can depend on each crate smaller then it. - // In theory shrinkage should be 2, but in practice we get better trees with a larger value. - let max_deps = max_versions * (max_crates * (max_crates - 1)) / shrinkage; - - let raw_version_range = (any::(), any::()); - let raw_dependency = (any::(), any::(), raw_version_range); - - fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) { - let (a, b) = (a.index(size), b.index(size)); - (min(a, b), max(a, b)) - } - - let list_of_raw_dependency = vec(raw_dependency, ..=max_deps); - - (list_of_crates_with_versions, list_of_raw_dependency).prop_map( - |(crate_vers_by_name, raw_dependencies)| { - let list_of_pkgid: Vec<_> = crate_vers_by_name - .iter() - .flat_map(|(name, vers)| vers.iter().map(move |x| ((name.as_str(), &x.0), x.1))) - .collect(); - let len_all_pkgid = list_of_pkgid.len(); - let mut dependency_by_pkgid = vec![vec![]; len_all_pkgid]; - for (a, b, (c, d)) in raw_dependencies { - let (a, b) = order_index(a, b, len_all_pkgid); - let ((dep_name, _), _) = list_of_pkgid[a]; - if (list_of_pkgid[b].0).0 == dep_name { - continue; - } - let s = &crate_vers_by_name[dep_name]; - let (c, d) = order_index(c, d, s.len()); - - dependency_by_pkgid[b].push(dep_req( - &dep_name, - &if c == d { - format!("={}", s[c].0) - } else { - format!(">={}, <={}", s[c].0, s[d].0) - }, - )) - } - - PrettyPrintRegistry( - list_of_pkgid - .into_iter() - .zip(dependency_by_pkgid.into_iter()) - .map(|(((name, ver), allow_deps), deps)| { - pkg_dep( - (name, ver).to_pkgid(), - if !allow_deps { - vec![dep_req("bad", "*")] - } else { - let mut deps = deps; - deps.sort_by_key(|d| d.name_in_toml()); - deps.dedup_by_key(|d| d.name_in_toml()); - deps - }, - ) - }).collect(), - ) - }, - ) -} - -/// This test is to test the generator to ensure -/// that it makes registries with large dependency trees -#[test] -fn meta_test_deep_trees_from_strategy() { - let mut dis = [0; 21]; - - let strategy = registry_strategy(50, 10, 50); - for _ in 0..256 { - let PrettyPrintRegistry(input) = strategy - .new_tree(&mut TestRunner::default()) - .unwrap() - .current(); - let reg = registry(input.clone()); - for this in input.iter().rev().take(10) { - let res = resolve( - &pkg_id("root"), - vec![dep_req(&this.name(), &format!("={}", this.version()))], - ®, - ); - dis[res.as_ref().map(|x| min(x.len(), dis.len()) - 1).unwrap_or(0)] += 1; - if dis.iter().all(|&x| x > 0) { - return; - } - } - } - - assert!( - dis.iter().all(|&x| x > 0), - "In 2560 tries we did not see a wide enough distribution of dependency trees! dis:{:?}", - dis - ); -} proptest! { #![proptest_config(ProptestConfig { @@ -524,18 +138,6 @@ fn test_resolving_empty_dependency_list() { assert_eq!(res, names(&["root"])); } -/// Assert `xs` contains `elems` -fn assert_contains(xs: &[A], elems: &[A]) { - for elem in elems { - assert!(xs.contains(elem)); - } -} - -fn assert_same(a: &[A], b: &[A]) { - assert_eq!(a.len(), b.len()); - assert_contains(b, a); -} - #[test] fn test_resolving_only_package() { let reg = registry(vec![pkg!("foo")]); diff --git a/tests/testsuite/support/mod.rs b/tests/testsuite/support/mod.rs index 497610caf67..f9ddb5380b4 100644 --- a/tests/testsuite/support/mod.rs +++ b/tests/testsuite/support/mod.rs @@ -137,6 +137,8 @@ pub mod git; pub mod paths; pub mod publish; pub mod registry; +#[macro_use] +pub mod resolver; /* * diff --git a/tests/testsuite/support/resolver.rs b/tests/testsuite/support/resolver.rs new file mode 100644 index 00000000000..98b7b0f54b2 --- /dev/null +++ b/tests/testsuite/support/resolver.rs @@ -0,0 +1,413 @@ +use std::cmp::PartialEq; +use std::cmp::{max, min}; +use std::collections::{BTreeMap, HashSet}; +use std::fmt; +use std::time::{Duration, Instant}; + +use cargo::core::dependency::Kind; +use cargo::core::resolver::{self, Method}; +use cargo::core::source::{GitReference, SourceId}; +use cargo::core::{Dependency, PackageId, Registry, Summary}; +use cargo::util::{CargoResult, Config, ToUrl}; + +use proptest::collection::{btree_map, btree_set, vec}; +use proptest::prelude::*; +use proptest::sample::Index; +use proptest::strategy::ValueTree; +use proptest::string::string_regex; +use proptest::test_runner::TestRunner; + +pub fn resolve( + pkg: &PackageId, + deps: Vec, + registry: &[Summary], +) -> CargoResult> { + resolve_with_config(pkg, deps, registry, None) +} + +pub fn resolve_with_config( + pkg: &PackageId, + deps: Vec, + registry: &[Summary], + config: Option<&Config>, +) -> CargoResult> { + struct MyRegistry<'a>(&'a [Summary]); + impl<'a> Registry for MyRegistry<'a> { + fn query( + &mut self, + dep: &Dependency, + f: &mut FnMut(Summary), + fuzzy: bool, + ) -> CargoResult<()> { + for summary in self.0.iter() { + if fuzzy || dep.matches(summary) { + f(summary.clone()); + } + } + Ok(()) + } + } + let mut registry = MyRegistry(registry); + let summary = Summary::new( + pkg.clone(), + deps, + &BTreeMap::>::new(), + None::, + false, + ).unwrap(); + let method = Method::Everything; + let start = Instant::now(); + let resolve = resolver::resolve( + &[(summary, method)], + &[], + &mut registry, + &HashSet::new(), + config, + false, + )?; + + // 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() < Duration::from_secs(60)); + let res = resolve.iter().cloned().collect(); + Ok(res) +} + +pub trait ToDep { + fn to_dep(self) -> Dependency; +} + +impl ToDep for &'static str { + fn to_dep(self) -> Dependency { + Dependency::parse_no_deprecated(self, Some("1.0.0"), ®istry_loc()).unwrap() + } +} + +impl ToDep for Dependency { + fn to_dep(self) -> Dependency { + self + } +} + +pub trait ToPkgId { + fn to_pkgid(&self) -> PackageId; +} + +impl ToPkgId for PackageId { + fn to_pkgid(&self) -> PackageId { + self.clone() + } +} + +impl<'a> ToPkgId for &'a str { + fn to_pkgid(&self) -> PackageId { + PackageId::new(*self, "1.0.0", ®istry_loc()).unwrap() + } +} + +impl, U: AsRef> ToPkgId for (T, U) { + fn to_pkgid(&self) -> PackageId { + let (name, vers) = self; + PackageId::new(name.as_ref(), vers.as_ref(), ®istry_loc()).unwrap() + } +} + +macro_rules! pkg { + ($pkgid:expr => [$($deps:expr),+ $(,)* ]) => ({ + let d: Vec = vec![$($deps.to_dep()),+]; + pkg_dep($pkgid, d) + }); + + ($pkgid:expr) => ({ + pkg($pkgid) + }) +} + +fn registry_loc() -> SourceId { + lazy_static! { + static ref EXAMPLE_DOT_COM: SourceId = + SourceId::for_registry(&"http://example.com".to_url().unwrap()).unwrap(); + } + EXAMPLE_DOT_COM.clone() +} + +pub fn pkg(name: T) -> Summary { + pkg_dep(name, Vec::new()) +} + +pub fn pkg_dep(name: T, dep: Vec) -> Summary { + let pkgid = name.to_pkgid(); + let link = if pkgid.name().ends_with("-sys") { + Some(pkgid.name().as_str()) + } else { + None + }; + Summary::new( + name.to_pkgid(), + dep, + &BTreeMap::>::new(), + link, + false, + ).unwrap() +} + +pub fn pkg_id(name: &str) -> PackageId { + PackageId::new(name, "1.0.0", ®istry_loc()).unwrap() +} + +fn pkg_id_loc(name: &str, loc: &str) -> PackageId { + let remote = loc.to_url(); + let master = GitReference::Branch("master".to_string()); + let source_id = SourceId::for_git(&remote.unwrap(), master).unwrap(); + + PackageId::new(name, "1.0.0", &source_id).unwrap() +} + +pub fn pkg_loc(name: &str, loc: &str) -> Summary { + let link = if name.ends_with("-sys") { + Some(name) + } else { + None + }; + Summary::new( + pkg_id_loc(name, loc), + Vec::new(), + &BTreeMap::>::new(), + link, + false, + ).unwrap() +} + +pub fn dep(name: &str) -> Dependency { + dep_req(name, "1.0.0") +} +pub fn dep_req(name: &str, req: &str) -> Dependency { + Dependency::parse_no_deprecated(name, Some(req), ®istry_loc()).unwrap() +} + +pub fn dep_loc(name: &str, location: &str) -> Dependency { + let url = location.to_url().unwrap(); + let master = GitReference::Branch("master".to_string()); + let source_id = SourceId::for_git(&url, master).unwrap(); + Dependency::parse_no_deprecated(name, Some("1.0.0"), &source_id).unwrap() +} +pub fn dep_kind(name: &str, kind: Kind) -> Dependency { + dep(name).set_kind(kind).clone() +} + +pub fn registry(pkgs: Vec) -> Vec { + pkgs +} + +pub fn names(names: &[P]) -> Vec { + names.iter().map(|name| name.to_pkgid()).collect() +} + +pub fn loc_names(names: &[(&'static str, &'static str)]) -> Vec { + names + .iter() + .map(|&(name, loc)| pkg_id_loc(name, loc)) + .collect() +} + +/// By default `Summary` and `Dependency` have a very verbose `Debug` representation. +/// This replaces with a representation that uses constructors from this file. +/// +/// If `registry_strategy` is improved to modify more fields +/// then this needs to update to display the corresponding constructor. +pub struct PrettyPrintRegistry(pub Vec); + +impl fmt::Debug for PrettyPrintRegistry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "vec![")?; + for s in &self.0 { + if s.dependencies().is_empty() { + write!(f, "pkg!((\"{}\", \"{}\")),", s.name(), s.version())?; + } else { + write!(f, "pkg!((\"{}\", \"{}\") => [", s.name(), s.version())?; + for d in s.dependencies() { + write!( + f, + "dep_req(\"{}\", \"{}\"),", + d.name_in_toml(), + d.version_req() + )?; + } + write!(f, "]),")?; + } + } + write!(f, "]") + } +} + +#[test] +fn meta_test_deep_pretty_print_registry() { + assert_eq!( + &format!( + "{:?}", + PrettyPrintRegistry(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")), + ]) + ), + "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\")),]" + ) +} + +/// This generates a random registry index. +/// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..) +/// This strategy has a high probability of having valid dependencies +pub fn registry_strategy( + max_crates: usize, + max_versions: usize, + shrinkage: usize, +) -> impl Strategy { + let name = string_regex("[A-Za-z_-][A-Za-z0-9_-]*(-sys)?").unwrap(); + + let raw_version = [..max_versions; 3]; + let version_from_raw = |v: &[usize; 3]| format!("{}.{}.{}", v[0], v[1], v[2]); + + // If this is false than the crate will depend on the nonexistent "bad" + // instead of the complex set we generated for it. + let allow_deps = prop::bool::weighted(0.99); + + let list_of_versions = + btree_set((raw_version, allow_deps), 1..=max_versions).prop_map(move |ver| { + ver.iter() + .map(|a| (version_from_raw(&a.0), a.1)) + .collect::>() + }); + + let list_of_crates_with_versions = + btree_map(name, list_of_versions, 1..=max_crates).prop_map(|mut vers| { + // root is the name of the thing being compiled + // so it would be confusing to have it in the index + vers.remove("root"); + // bad is a name reserved for a dep that won't work + vers.remove("bad"); + vers + }); + + // each version of each crate can depend on each crate smaller then it. + // In theory shrinkage should be 2, but in practice we get better trees with a larger value. + let max_deps = max_versions * (max_crates * (max_crates - 1)) / shrinkage; + + let raw_version_range = (any::(), any::()); + let raw_dependency = (any::(), any::(), raw_version_range); + + fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) { + let (a, b) = (a.index(size), b.index(size)); + (min(a, b), max(a, b)) + } + + let list_of_raw_dependency = vec(raw_dependency, ..=max_deps); + + (list_of_crates_with_versions, list_of_raw_dependency).prop_map( + |(crate_vers_by_name, raw_dependencies)| { + let list_of_pkgid: Vec<_> = crate_vers_by_name + .iter() + .flat_map(|(name, vers)| vers.iter().map(move |x| ((name.as_str(), &x.0), x.1))) + .collect(); + let len_all_pkgid = list_of_pkgid.len(); + let mut dependency_by_pkgid = vec![vec![]; len_all_pkgid]; + for (a, b, (c, d)) in raw_dependencies { + let (a, b) = order_index(a, b, len_all_pkgid); + let ((dep_name, _), _) = list_of_pkgid[a]; + if (list_of_pkgid[b].0).0 == dep_name { + continue; + } + let s = &crate_vers_by_name[dep_name]; + let (c, d) = order_index(c, d, s.len()); + + dependency_by_pkgid[b].push(dep_req( + &dep_name, + &if c == d { + format!("={}", s[c].0) + } else { + format!(">={}, <={}", s[c].0, s[d].0) + }, + )) + } + + PrettyPrintRegistry( + list_of_pkgid + .into_iter() + .zip(dependency_by_pkgid.into_iter()) + .map(|(((name, ver), allow_deps), deps)| { + pkg_dep( + (name, ver).to_pkgid(), + if !allow_deps { + vec![dep_req("bad", "*")] + } else { + let mut deps = deps; + deps.sort_by_key(|d| d.name_in_toml()); + deps.dedup_by_key(|d| d.name_in_toml()); + deps + }, + ) + }).collect(), + ) + }, + ) +} + +/// This test is to test the generator to ensure +/// that it makes registries with large dependency trees +#[test] +fn meta_test_deep_trees_from_strategy() { + let mut dis = [0; 21]; + + let strategy = registry_strategy(50, 10, 50); + for _ in 0..256 { + let PrettyPrintRegistry(input) = strategy + .new_tree(&mut TestRunner::default()) + .unwrap() + .current(); + let reg = registry(input.clone()); + for this in input.iter().rev().take(10) { + let res = resolve( + &pkg_id("root"), + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + ); + dis[res + .as_ref() + .map(|x| min(x.len(), dis.len()) - 1) + .unwrap_or(0)] += 1; + if dis.iter().all(|&x| x > 0) { + return; + } + } + } + + assert!( + dis.iter().all(|&x| x > 0), + "In 2560 tries we did not see a wide enough distribution of dependency trees! dis:{:?}", + dis + ); +} + +/// Assert `xs` contains `elems` +pub fn assert_contains(xs: &[A], elems: &[A]) { + for elem in elems { + assert!(xs.contains(elem)); + } +} + +pub fn assert_same(a: &[A], b: &[A]) { + assert_eq!(a.len(), b.len()); + assert_contains(b, a); +}