mirror of https://github.com/rust-lang/cargo
876 lines
29 KiB
Rust
876 lines
29 KiB
Rust
#![allow(clippy::print_stderr)]
|
|
|
|
use std::cell::RefCell;
|
|
use std::cmp::{max, min};
|
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
use std::fmt;
|
|
use std::fmt::Write;
|
|
use std::rc::Rc;
|
|
use std::sync::OnceLock;
|
|
use std::task::Poll;
|
|
use std::time::Instant;
|
|
|
|
use cargo::core::dependency::DepKind;
|
|
use cargo::core::resolver::{self, ResolveOpts, VersionOrdering, VersionPreferences};
|
|
use cargo::core::Resolve;
|
|
use cargo::core::ResolveVersion;
|
|
use cargo::core::{Dependency, PackageId, Registry, Summary};
|
|
use cargo::core::{GitReference, SourceId};
|
|
use cargo::sources::source::QueryKind;
|
|
use cargo::sources::IndexSummary;
|
|
use cargo::util::{CargoResult, GlobalContext, IntoUrl};
|
|
use cargo_util_schemas::manifest::RustVersion;
|
|
|
|
use proptest::collection::{btree_map, vec};
|
|
use proptest::prelude::*;
|
|
use proptest::sample::Index;
|
|
use proptest::string::string_regex;
|
|
use varisat::ExtendFormula;
|
|
|
|
pub fn resolve(deps: Vec<Dependency>, registry: &[Summary]) -> CargoResult<Vec<PackageId>> {
|
|
resolve_with_global_context(deps, registry, &GlobalContext::default().unwrap())
|
|
}
|
|
|
|
pub fn resolve_and_validated(
|
|
deps: Vec<Dependency>,
|
|
registry: &[Summary],
|
|
sat_resolve: Option<SatResolve>,
|
|
) -> CargoResult<Vec<PackageId>> {
|
|
let resolve =
|
|
resolve_with_global_context_raw(deps.clone(), registry, &GlobalContext::default().unwrap());
|
|
|
|
match resolve {
|
|
Err(e) => {
|
|
let sat_resolve = sat_resolve.unwrap_or_else(|| SatResolve::new(registry));
|
|
if sat_resolve.sat_resolve(&deps) {
|
|
panic!(
|
|
"the resolve err but the sat_resolve thinks this will work:\n{}",
|
|
sat_resolve.use_packages().unwrap()
|
|
);
|
|
}
|
|
Err(e)
|
|
}
|
|
Ok(resolve) => {
|
|
let mut stack = vec![pkg_id("root")];
|
|
let mut used = HashSet::new();
|
|
let mut links = HashSet::new();
|
|
while let Some(p) = stack.pop() {
|
|
assert!(resolve.contains(&p));
|
|
if used.insert(p) {
|
|
// in the tests all `links` crates end in `-sys`
|
|
if p.name().ends_with("-sys") {
|
|
assert!(links.insert(p.name()));
|
|
}
|
|
stack.extend(resolve.deps(p).map(|(dp, deps)| {
|
|
for d in deps {
|
|
assert!(d.matches_id(dp));
|
|
}
|
|
dp
|
|
}));
|
|
}
|
|
}
|
|
let out = resolve.sort();
|
|
assert_eq!(out.len(), used.len());
|
|
|
|
let sat_resolve = sat_resolve.unwrap_or_else(|| SatResolve::new(registry));
|
|
if !sat_resolve.sat_is_valid_solution(&out) {
|
|
panic!(
|
|
"the sat_resolve err but the resolve thinks this will work:\n{:?}",
|
|
resolve
|
|
);
|
|
}
|
|
Ok(out)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn resolve_with_global_context(
|
|
deps: Vec<Dependency>,
|
|
registry: &[Summary],
|
|
gctx: &GlobalContext,
|
|
) -> CargoResult<Vec<PackageId>> {
|
|
let resolve = resolve_with_global_context_raw(deps, registry, gctx)?;
|
|
Ok(resolve.sort())
|
|
}
|
|
|
|
pub fn resolve_with_global_context_raw(
|
|
deps: Vec<Dependency>,
|
|
registry: &[Summary],
|
|
gctx: &GlobalContext,
|
|
) -> CargoResult<Resolve> {
|
|
struct MyRegistry<'a> {
|
|
list: &'a [Summary],
|
|
used: HashSet<PackageId>,
|
|
}
|
|
impl<'a> Registry for MyRegistry<'a> {
|
|
fn query(
|
|
&mut self,
|
|
dep: &Dependency,
|
|
kind: QueryKind,
|
|
f: &mut dyn FnMut(IndexSummary),
|
|
) -> Poll<CargoResult<()>> {
|
|
for summary in self.list.iter() {
|
|
let matched = match kind {
|
|
QueryKind::Exact => dep.matches(summary),
|
|
QueryKind::Alternatives => true,
|
|
QueryKind::Normalized => true,
|
|
};
|
|
if matched {
|
|
self.used.insert(summary.package_id());
|
|
f(IndexSummary::Candidate(summary.clone()));
|
|
}
|
|
}
|
|
Poll::Ready(Ok(()))
|
|
}
|
|
|
|
fn describe_source(&self, _src: SourceId) -> String {
|
|
String::new()
|
|
}
|
|
|
|
fn is_replaced(&self, _src: SourceId) -> bool {
|
|
false
|
|
}
|
|
|
|
fn block_until_ready(&mut self) -> CargoResult<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
impl<'a> Drop for MyRegistry<'a> {
|
|
fn drop(&mut self) {
|
|
if std::thread::panicking() && self.list.len() != self.used.len() {
|
|
// we found a case that causes a panic and did not use all of the input.
|
|
// lets print the part of the input that was used for minimization.
|
|
eprintln!(
|
|
"Part used befor drop: {:?}",
|
|
PrettyPrintRegistry(
|
|
self.list
|
|
.iter()
|
|
.filter(|s| { self.used.contains(&s.package_id()) })
|
|
.cloned()
|
|
.collect()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
let mut registry = MyRegistry {
|
|
list: registry,
|
|
used: HashSet::new(),
|
|
};
|
|
let summary = Summary::new(
|
|
pkg_id("root"),
|
|
deps,
|
|
&BTreeMap::new(),
|
|
None::<&String>,
|
|
None::<RustVersion>,
|
|
)
|
|
.unwrap();
|
|
let opts = ResolveOpts::everything();
|
|
let start = Instant::now();
|
|
let mut version_prefs = VersionPreferences::default();
|
|
if gctx.cli_unstable().minimal_versions {
|
|
version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst)
|
|
}
|
|
let resolve = resolver::resolve(
|
|
&[(summary, opts)],
|
|
&[],
|
|
&mut registry,
|
|
&version_prefs,
|
|
ResolveVersion::with_rust_version(None),
|
|
Some(gctx),
|
|
);
|
|
|
|
// 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().as_secs() < 60);
|
|
resolve
|
|
}
|
|
|
|
const fn num_bits<T>() -> usize {
|
|
std::mem::size_of::<T>() * 8
|
|
}
|
|
|
|
fn log_bits(x: usize) -> usize {
|
|
if x == 0 {
|
|
return 0;
|
|
}
|
|
assert!(x > 0);
|
|
(num_bits::<usize>() as u32 - x.leading_zeros()) as usize
|
|
}
|
|
|
|
fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Var]) {
|
|
if vars.len() <= 1 {
|
|
return;
|
|
} else if vars.len() == 2 {
|
|
solver.add_clause(&[vars[0].negative(), vars[1].negative()]);
|
|
return;
|
|
} else if vars.len() == 3 {
|
|
solver.add_clause(&[vars[0].negative(), vars[1].negative()]);
|
|
solver.add_clause(&[vars[0].negative(), vars[2].negative()]);
|
|
solver.add_clause(&[vars[1].negative(), vars[2].negative()]);
|
|
return;
|
|
}
|
|
// use the "Binary Encoding" from
|
|
// https://www.it.uu.se/research/group/astra/ModRef10/papers/Alan%20M.%20Frisch%20and%20Paul%20A.%20Giannoros.%20SAT%20Encodings%20of%20the%20At-Most-k%20Constraint%20-%20ModRef%202010.pdf
|
|
let bits: Vec<varisat::Var> = solver.new_var_iter(log_bits(vars.len())).collect();
|
|
for (i, p) in vars.iter().enumerate() {
|
|
for b in 0..bits.len() {
|
|
solver.add_clause(&[p.negative(), bits[b].lit(((1 << b) & i) > 0)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn sat_at_most_one_by_key<K: std::hash::Hash + Eq>(
|
|
cnf: &mut impl varisat::ExtendFormula,
|
|
data: impl Iterator<Item = (K, varisat::Var)>,
|
|
) -> HashMap<K, Vec<varisat::Var>> {
|
|
// no two packages with the same links set
|
|
let mut by_keys: HashMap<K, Vec<varisat::Var>> = HashMap::new();
|
|
for (p, v) in data {
|
|
by_keys.entry(p).or_default().push(v)
|
|
}
|
|
for key in by_keys.values() {
|
|
sat_at_most_one(cnf, key);
|
|
}
|
|
by_keys
|
|
}
|
|
|
|
/// Resolution can be reduced to the SAT problem. So this is an alternative implementation
|
|
/// of the resolver that uses a SAT library for the hard work. This is intended to be easy to read,
|
|
/// as compared to the real resolver.
|
|
///
|
|
/// For the subset of functionality that are currently made by `registry_strategy` this will,
|
|
/// find a valid resolution if one exists. The big thing that the real resolver does,
|
|
/// that this one does not do is work with features and optional dependencies.
|
|
///
|
|
/// The SAT library dose not optimize for the newer version,
|
|
/// so the selected packages may not match the real resolver.
|
|
#[derive(Clone)]
|
|
pub struct SatResolve(Rc<RefCell<SatResolveInner>>);
|
|
|
|
struct SatResolveInner {
|
|
solver: varisat::Solver<'static>,
|
|
var_for_is_packages_used: HashMap<PackageId, varisat::Var>,
|
|
by_name: HashMap<&'static str, Vec<PackageId>>,
|
|
}
|
|
|
|
impl SatResolve {
|
|
pub fn new(registry: &[Summary]) -> Self {
|
|
let mut cnf = varisat::CnfFormula::new();
|
|
let var_for_is_packages_used: HashMap<PackageId, varisat::Var> = registry
|
|
.iter()
|
|
.map(|s| (s.package_id(), cnf.new_var()))
|
|
.collect();
|
|
|
|
// no two packages with the same links set
|
|
sat_at_most_one_by_key(
|
|
&mut cnf,
|
|
registry
|
|
.iter()
|
|
.map(|s| (s.links(), var_for_is_packages_used[&s.package_id()]))
|
|
.filter(|(l, _)| l.is_some()),
|
|
);
|
|
|
|
// no two semver compatible versions of the same package
|
|
sat_at_most_one_by_key(
|
|
&mut cnf,
|
|
var_for_is_packages_used
|
|
.iter()
|
|
.map(|(p, &v)| (p.as_activations_key(), v)),
|
|
);
|
|
|
|
let mut by_name: HashMap<&'static str, Vec<PackageId>> = HashMap::new();
|
|
|
|
for p in registry.iter() {
|
|
by_name
|
|
.entry(p.name().as_str())
|
|
.or_default()
|
|
.push(p.package_id())
|
|
}
|
|
|
|
let empty_vec = vec![];
|
|
|
|
// active packages need each of there `deps` to be satisfied
|
|
for p in registry.iter() {
|
|
for dep in p.dependencies() {
|
|
let mut matches: Vec<varisat::Lit> = by_name
|
|
.get(dep.package_name().as_str())
|
|
.unwrap_or(&empty_vec)
|
|
.iter()
|
|
.filter(|&p| dep.matches_id(*p))
|
|
.map(|p| var_for_is_packages_used[&p].positive())
|
|
.collect();
|
|
// ^ the `dep` is satisfied or `p` is not active
|
|
matches.push(var_for_is_packages_used[&p.package_id()].negative());
|
|
cnf.add_clause(&matches);
|
|
}
|
|
}
|
|
|
|
let mut solver = varisat::Solver::new();
|
|
solver.add_formula(&cnf);
|
|
|
|
// We dont need to `solve` now. We know that "use nothing" will satisfy all the clauses so far.
|
|
// But things run faster if we let it spend some time figuring out how the constraints interact before we add assumptions.
|
|
solver
|
|
.solve()
|
|
.expect("docs say it can't error in default config");
|
|
SatResolve(Rc::new(RefCell::new(SatResolveInner {
|
|
solver,
|
|
var_for_is_packages_used,
|
|
by_name,
|
|
})))
|
|
}
|
|
pub fn sat_resolve(&self, deps: &[Dependency]) -> bool {
|
|
let mut s = self.0.borrow_mut();
|
|
let mut assumption = vec![];
|
|
let mut this_call = None;
|
|
|
|
// the starting `deps` need to be satisfied
|
|
for dep in deps.iter() {
|
|
let empty_vec = vec![];
|
|
let matches: Vec<varisat::Lit> = s
|
|
.by_name
|
|
.get(dep.package_name().as_str())
|
|
.unwrap_or(&empty_vec)
|
|
.iter()
|
|
.filter(|&p| dep.matches_id(*p))
|
|
.map(|p| s.var_for_is_packages_used[p].positive())
|
|
.collect();
|
|
if matches.is_empty() {
|
|
return false;
|
|
} else if matches.len() == 1 {
|
|
assumption.extend_from_slice(&matches)
|
|
} else {
|
|
if this_call.is_none() {
|
|
let new_var = s.solver.new_var();
|
|
this_call = Some(new_var);
|
|
assumption.push(new_var.positive());
|
|
}
|
|
let mut matches = matches;
|
|
matches.push(this_call.unwrap().negative());
|
|
s.solver.add_clause(&matches);
|
|
}
|
|
}
|
|
|
|
s.solver.assume(&assumption);
|
|
|
|
s.solver
|
|
.solve()
|
|
.expect("docs say it can't error in default config")
|
|
}
|
|
pub fn sat_is_valid_solution(&self, pids: &[PackageId]) -> bool {
|
|
let mut s = self.0.borrow_mut();
|
|
for p in pids {
|
|
if p.name().as_str() != "root" && !s.var_for_is_packages_used.contains_key(p) {
|
|
return false;
|
|
}
|
|
}
|
|
let assumption: Vec<_> = s
|
|
.var_for_is_packages_used
|
|
.iter()
|
|
.map(|(p, v)| v.lit(pids.contains(p)))
|
|
.collect();
|
|
|
|
s.solver.assume(&assumption);
|
|
|
|
s.solver
|
|
.solve()
|
|
.expect("docs say it can't error in default config")
|
|
}
|
|
fn use_packages(&self) -> Option<String> {
|
|
self.0.borrow().solver.model().map(|lits| {
|
|
let lits: HashSet<_> = lits
|
|
.iter()
|
|
.filter(|l| l.is_positive())
|
|
.map(|l| l.var())
|
|
.collect();
|
|
let mut out = String::new();
|
|
out.push_str("used:\n");
|
|
for (p, v) in self.0.borrow().var_for_is_packages_used.iter() {
|
|
if lits.contains(v) {
|
|
writeln!(&mut out, " {}", p).unwrap();
|
|
}
|
|
}
|
|
out
|
|
})
|
|
}
|
|
}
|
|
|
|
pub trait ToDep {
|
|
fn to_dep(self) -> Dependency;
|
|
}
|
|
|
|
impl ToDep for &'static str {
|
|
fn to_dep(self) -> Dependency {
|
|
Dependency::parse(self, Some("1.0.0"), registry_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
|
|
}
|
|
}
|
|
|
|
impl<'a> ToPkgId for &'a str {
|
|
fn to_pkgid(&self) -> PackageId {
|
|
PackageId::try_new(*self, "1.0.0", registry_loc()).unwrap()
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<str>, U: AsRef<str>> ToPkgId for (T, U) {
|
|
fn to_pkgid(&self) -> PackageId {
|
|
let (name, vers) = self;
|
|
PackageId::try_new(name.as_ref(), vers.as_ref(), registry_loc()).unwrap()
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! pkg {
|
|
($pkgid:expr => [$($deps:expr),+ $(,)* ]) => ({
|
|
let d: Vec<Dependency> = vec![$($deps.to_dep()),+];
|
|
$crate::pkg_dep($pkgid, d)
|
|
});
|
|
|
|
($pkgid:expr) => ({
|
|
$crate::pkg($pkgid)
|
|
})
|
|
}
|
|
|
|
fn registry_loc() -> SourceId {
|
|
static EXAMPLE_DOT_COM: OnceLock<SourceId> = OnceLock::new();
|
|
let example_dot = EXAMPLE_DOT_COM.get_or_init(|| {
|
|
SourceId::for_registry(&"https://example.com".into_url().unwrap()).unwrap()
|
|
});
|
|
*example_dot
|
|
}
|
|
|
|
pub fn pkg<T: ToPkgId>(name: T) -> Summary {
|
|
pkg_dep(name, Vec::new())
|
|
}
|
|
|
|
pub fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> 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,
|
|
None::<RustVersion>,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn pkg_id(name: &str) -> PackageId {
|
|
PackageId::try_new(name, "1.0.0", registry_loc()).unwrap()
|
|
}
|
|
|
|
fn pkg_id_loc(name: &str, loc: &str) -> PackageId {
|
|
let remote = loc.into_url();
|
|
let master = GitReference::Branch("master".to_string());
|
|
let source_id = SourceId::for_git(&remote.unwrap(), master).unwrap();
|
|
|
|
PackageId::try_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,
|
|
None::<RustVersion>,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn remove_dep(sum: &Summary, ind: usize) -> Summary {
|
|
let mut deps = sum.dependencies().to_vec();
|
|
deps.remove(ind);
|
|
// note: more things will need to be copied over in the future, but it works for now.
|
|
Summary::new(
|
|
sum.package_id(),
|
|
deps,
|
|
&BTreeMap::new(),
|
|
sum.links().map(|a| a.as_str()),
|
|
None::<RustVersion>,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn dep(name: &str) -> Dependency {
|
|
dep_req(name, "*")
|
|
}
|
|
|
|
pub fn dep_req(name: &str, req: &str) -> Dependency {
|
|
Dependency::parse(name, Some(req), registry_loc()).unwrap()
|
|
}
|
|
|
|
pub fn dep_req_kind(name: &str, req: &str, kind: DepKind) -> Dependency {
|
|
let mut dep = dep_req(name, req);
|
|
dep.set_kind(kind);
|
|
dep
|
|
}
|
|
|
|
pub fn dep_loc(name: &str, location: &str) -> Dependency {
|
|
let url = location.into_url().unwrap();
|
|
let master = GitReference::Branch("master".to_string());
|
|
let source_id = SourceId::for_git(&url, master).unwrap();
|
|
Dependency::parse(name, Some("1.0.0"), source_id).unwrap()
|
|
}
|
|
|
|
pub fn dep_kind(name: &str, kind: DepKind) -> Dependency {
|
|
dep(name).set_kind(kind).clone()
|
|
}
|
|
|
|
pub fn registry(pkgs: Vec<Summary>) -> Vec<Summary> {
|
|
pkgs
|
|
}
|
|
|
|
pub fn names<P: ToPkgId>(names: &[P]) -> Vec<PackageId> {
|
|
names.iter().map(|name| name.to_pkgid()).collect()
|
|
}
|
|
|
|
pub fn loc_names(names: &[(&'static str, &'static str)]) -> Vec<PackageId> {
|
|
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<Summary>);
|
|
|
|
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() {
|
|
if d.kind() == DepKind::Normal
|
|
&& &d.version_req().to_string() == "*"
|
|
&& !d.is_public()
|
|
{
|
|
write!(f, "dep(\"{}\"),", d.name_in_toml())?;
|
|
} else if d.kind() == DepKind::Normal && !d.is_public() {
|
|
write!(
|
|
f,
|
|
"dep_req(\"{}\", \"{}\"),",
|
|
d.name_in_toml(),
|
|
d.version_req()
|
|
)?;
|
|
} else {
|
|
write!(
|
|
f,
|
|
"dep_req_kind(\"{}\", \"{}\", {}, {}),",
|
|
d.name_in_toml(),
|
|
d.version_req(),
|
|
match d.kind() {
|
|
DepKind::Development => "DepKind::Development",
|
|
DepKind::Build => "DepKind::Build",
|
|
DepKind::Normal => "DepKind::Normal",
|
|
},
|
|
d.is_public()
|
|
)?;
|
|
}
|
|
}
|
|
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!(("foo", "2.0.0") => [dep_req("bar", "*")]),
|
|
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!(("cat", "1.0.2") => [dep_req_kind("other", "2", DepKind::Build)]),
|
|
pkg!(("cat", "1.0.3") => [dep_req_kind("other", "2", DepKind::Development)]),
|
|
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!((\"foo\", \"2.0.0\") => [dep(\"bar\"),]),\
|
|
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!((\"cat\", \"1.0.2\") => [dep_req_kind(\"other\", \"^2\", DepKind::Build, false),]),\
|
|
pkg!((\"cat\", \"1.0.3\") => [dep_req_kind(\"other\", \"^2\", DepKind::Development, false),]),\
|
|
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<Value = PrettyPrintRegistry> {
|
|
let name = string_regex("[A-Za-z][A-Za-z0-9_-]*(-sys)?").unwrap();
|
|
|
|
let raw_version = ..max_versions.pow(3);
|
|
let version_from_raw = move |r: usize| {
|
|
let major = ((r / max_versions) / max_versions) % max_versions;
|
|
let minor = (r / max_versions) % max_versions;
|
|
let patch = r % max_versions;
|
|
format!("{}.{}.{}", major, minor, patch)
|
|
};
|
|
|
|
// If this is false then 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_map(raw_version, allow_deps, 1..=max_versions).prop_map(move |ver| {
|
|
ver.into_iter()
|
|
.map(|a| (version_from_raw(a.0), a.1))
|
|
.collect::<Vec<_>>()
|
|
});
|
|
|
|
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::<Index>(), any::<Index>());
|
|
let raw_dependency = (any::<Index>(), any::<Index>(), raw_version_range, 0..=1);
|
|
|
|
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);
|
|
|
|
// By default a package depends only on other packages that have a smaller name,
|
|
// this helps make sure that all things in the resulting index are DAGs.
|
|
// If this is true then the DAG is maintained with grater instead.
|
|
let reverse_alphabetical = any::<bool>().no_shrink();
|
|
|
|
(
|
|
list_of_crates_with_versions,
|
|
list_of_raw_dependency,
|
|
reverse_alphabetical,
|
|
)
|
|
.prop_map(
|
|
|(crate_vers_by_name, raw_dependencies, reverse_alphabetical)| {
|
|
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), k) in raw_dependencies {
|
|
let (a, b) = order_index(a, b, len_all_pkgid);
|
|
let (a, b) = if reverse_alphabetical { (b, a) } else { (a, b) };
|
|
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 s_last_index = s.len() - 1;
|
|
let (c, d) = order_index(c, d, s.len());
|
|
|
|
dependency_by_pkgid[b].push(dep_req_kind(
|
|
dep_name,
|
|
&if c == 0 && d == s_last_index {
|
|
"*".to_string()
|
|
} else if c == 0 {
|
|
format!("<={}", s[d].0)
|
|
} else if d == s_last_index {
|
|
format!(">={}", s[c].0)
|
|
} else if c == d {
|
|
format!("={}", s[c].0)
|
|
} else {
|
|
format!(">={}, <={}", s[c].0, s[d].0)
|
|
},
|
|
match k {
|
|
0 => DepKind::Normal,
|
|
1 => DepKind::Build,
|
|
// => DepKind::Development, // Development has no impact so don't gen
|
|
_ => panic!("bad index for DepKind"),
|
|
},
|
|
))
|
|
}
|
|
|
|
let mut out: Vec<Summary> = 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();
|
|
|
|
if reverse_alphabetical {
|
|
// make sure the complicated cases are at the end
|
|
out.reverse();
|
|
}
|
|
|
|
PrettyPrintRegistry(out)
|
|
},
|
|
)
|
|
}
|
|
|
|
/// 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() {
|
|
use proptest::strategy::ValueTree;
|
|
use proptest::test_runner::TestRunner;
|
|
|
|
let mut dis = [0; 21];
|
|
|
|
let strategy = registry_strategy(50, 20, 60);
|
|
let mut test_runner = TestRunner::deterministic();
|
|
for _ in 0..128 {
|
|
let PrettyPrintRegistry(input) = strategy
|
|
.new_tree(&mut TestRunner::new_with_rng(
|
|
Default::default(),
|
|
test_runner.new_rng(),
|
|
))
|
|
.unwrap()
|
|
.current();
|
|
let reg = registry(input.clone());
|
|
for this in input.iter().rev().take(10) {
|
|
let res = resolve(
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
panic!(
|
|
"In 1280 tries we did not see a wide enough distribution of dependency trees! dis: {:?}",
|
|
dis
|
|
);
|
|
}
|
|
|
|
/// This test is to test the generator to ensure
|
|
/// that it makes registries that include multiple versions of the same library
|
|
#[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);
|
|
let mut test_runner = TestRunner::deterministic();
|
|
for _ in 0..128 {
|
|
let PrettyPrintRegistry(input) = strategy
|
|
.new_tree(&mut TestRunner::new_with_rng(
|
|
Default::default(),
|
|
test_runner.new_rng(),
|
|
))
|
|
.unwrap()
|
|
.current();
|
|
let reg = registry(input.clone());
|
|
for this in input.iter().rev().take(10) {
|
|
let res = resolve(
|
|
vec![dep_req(&this.name(), &format!("={}", this.version()))],
|
|
®,
|
|
);
|
|
if let Ok(mut res) = res {
|
|
let res_len = res.len();
|
|
res.sort_by_key(|s| s.name());
|
|
res.dedup_by_key(|s| s.name());
|
|
dis[min(res_len - res.len(), dis.len() - 1)] += 1;
|
|
}
|
|
if dis.iter().all(|&x| x > 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
panic!(
|
|
"In 1280 tries we did not see a wide enough distribution of multiple versions of the same library! dis: {:?}",
|
|
dis
|
|
);
|
|
}
|
|
|
|
/// Assert `xs` contains `elems`
|
|
#[track_caller]
|
|
pub fn assert_contains<A: PartialEq + std::fmt::Debug>(xs: &[A], elems: &[A]) {
|
|
for elem in elems {
|
|
assert!(
|
|
xs.contains(elem),
|
|
"missing element\nset: {xs:?}\nmissing: {elem:?}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn assert_same<A: PartialEq + std::fmt::Debug>(a: &[A], b: &[A]) {
|
|
assert_eq!(a.len(), b.len(), "not equal\n{a:?}\n{b:?}");
|
|
assert_contains(b, a);
|
|
}
|