Skip to content

Commit

Permalink
test: refactor resolver test functions
Browse files Browse the repository at this point in the history
  • Loading branch information
x-hgg-x committed Sep 24, 2024
1 parent d7bffc3 commit 03bdcb6
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 97 deletions.
166 changes: 107 additions & 59 deletions crates/resolver-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,61 @@ use std::task::Poll;
use std::time::Instant;

use cargo::core::dependency::DepKind;
use cargo::core::resolver::{self, ResolveOpts, VersionOrdering, VersionPreferences};
use cargo::core::resolver::features::RequestedFeatures;
use cargo::core::resolver::{self, CliFeatures, 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 root_summary(deps: Vec<Dependency>) -> CargoResult<Summary> {
Summary::new(
pkg_id("root"),
deps,
&BTreeMap::new(),
None::<&String>,
None,
)
}

pub fn resolve(deps: Vec<Dependency>, registry: &[Summary]) -> CargoResult<Vec<PackageId>> {
resolve_with_global_context(deps, registry, &GlobalContext::default().unwrap())
resolve_with_global_context(
registry,
&GlobalContext::default().unwrap(),
&root_summary(deps).unwrap(),
&CliFeatures::new_all(true),
)
}

// Verify that the resolution of cargo resolver can pass the verification of SAT
pub fn resolve_and_validated(
deps: Vec<Dependency>,
registry: &[Summary],
sat_resolve: Option<SatResolve>,
sat_resolver: &SatResolver,
root_summary: &Summary,
root_cli_features: &CliFeatures,
) -> CargoResult<Vec<PackageId>> {
let resolve =
resolve_with_global_context_raw(deps.clone(), registry, &GlobalContext::default().unwrap());
let resolve = resolve_with_global_context_raw(
registry,
&GlobalContext::default().unwrap(),
root_summary,
root_cli_features,
);

match resolve {
Err(e) => {
let sat_resolve = sat_resolve.unwrap_or_else(|| SatResolve::new(registry));
if sat_resolve.sat_resolve(&deps) {
if sat_resolver.sat_resolve(root_summary, root_cli_features) {
panic!(
"the resolve err but the sat_resolve thinks this will work:\n{}",
sat_resolve.use_packages().unwrap()
"`resolve()` returned an error but the sat resolver thinks this will work:\n{}",
sat_resolver.used_packages().unwrap()
);
}
Err(e)
Expand All @@ -73,11 +92,10 @@ pub fn resolve_and_validated(
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) {
if !sat_resolver.sat_is_valid_solution(&out) {
panic!(
"the sat_resolve err but the resolve thinks this will work:\n{:?}",
resolve
"`resolve()` thinks this will work, but the solution is \
invalid according to the sat resolver:\n{resolve:?}",
);
}
Ok(out)
Expand All @@ -86,18 +104,20 @@ pub fn resolve_and_validated(
}

pub fn resolve_with_global_context(
deps: Vec<Dependency>,
registry: &[Summary],
gctx: &GlobalContext,
root_summary: &Summary,
root_cli_features: &CliFeatures,
) -> CargoResult<Vec<PackageId>> {
let resolve = resolve_with_global_context_raw(deps, registry, gctx)?;
let resolve = resolve_with_global_context_raw(registry, gctx, root_summary, root_cli_features)?;
Ok(resolve.sort())
}

pub fn resolve_with_global_context_raw(
deps: Vec<Dependency>,
registry: &[Summary],
gctx: &GlobalContext,
root_summary: &Summary,
root_cli_features: &CliFeatures,
) -> CargoResult<Resolve> {
struct MyRegistry<'a> {
list: &'a [Summary],
Expand Down Expand Up @@ -158,22 +178,20 @@ pub fn resolve_with_global_context_raw(
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 opts = ResolveOpts::new(
true,
RequestedFeatures::CliFeatures(root_cli_features.clone()),
);

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)],
&[(root_summary.clone(), opts)],
&[],
&mut registry,
&version_prefs,
Expand Down Expand Up @@ -249,18 +267,18 @@ fn sat_at_most_one_by_key<K: std::hash::Hash + Eq>(
/// 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,
/// The SAT library does not optimize for the newer version,
/// so the selected packages may not match the real resolver.
#[derive(Clone)]
pub struct SatResolve(Rc<RefCell<SatResolveInner>>);
pub struct SatResolver(Rc<RefCell<SatResolverInner>>);

struct SatResolveInner {
struct SatResolverInner {
solver: varisat::Solver<'static>,
var_for_is_packages_used: HashMap<PackageId, varisat::Var>,
by_name: HashMap<&'static str, Vec<PackageId>>,
}

impl SatResolve {
impl SatResolver {
pub fn new(registry: &[Summary]) -> Self {
let mut cnf = varisat::CnfFormula::new();
// That represents each package version which is set to "true" if the packages in the lock file and "false" if it is unused.
Expand Down Expand Up @@ -325,19 +343,21 @@ impl SatResolve {
solver
.solve()
.expect("docs say it can't error in default config");
SatResolve(Rc::new(RefCell::new(SatResolveInner {

SatResolver(Rc::new(RefCell::new(SatResolverInner {
solver,
var_for_is_packages_used,
by_name,
})))
}
pub fn sat_resolve(&self, deps: &[Dependency]) -> bool {

pub fn sat_resolve(&self, root_summary: &Summary, _root_cli_features: &CliFeatures) -> 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() {
for dep in root_summary.dependencies().iter() {
let empty_vec = vec![];
let matches: Vec<varisat::Lit> = s
.by_name
Expand Down Expand Up @@ -369,6 +389,7 @@ impl SatResolve {
.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 {
Expand All @@ -388,7 +409,8 @@ impl SatResolve {
.solve()
.expect("docs say it can't error in default config")
}
fn use_packages(&self) -> Option<String> {

fn used_packages(&self) -> Option<String> {
self.0.borrow().solver.model().map(|lits| {
let lits: HashSet<_> = lits
.iter()
Expand All @@ -409,18 +431,40 @@ impl SatResolve {

pub trait ToDep {
fn to_dep(self) -> Dependency;
fn to_opt_dep(self) -> Dependency;
fn to_dep_with(self, features: &[&'static str]) -> Dependency;
}

impl ToDep for &'static str {
fn to_dep(self) -> Dependency {
Dependency::parse(self, Some("1.0.0"), registry_loc()).unwrap()
}
fn to_opt_dep(self) -> Dependency {
let mut dep = self.to_dep();
dep.set_optional(true);
dep
}
fn to_dep_with(self, features: &[&'static str]) -> Dependency {
let mut dep = self.to_dep();
dep.set_default_features(false);
dep.set_features(features.into_iter().copied());
dep
}
}

impl ToDep for Dependency {
fn to_dep(self) -> Dependency {
self
}
fn to_opt_dep(mut self) -> Dependency {
self.set_optional(true);
self
}
fn to_dep_with(mut self, features: &[&'static str]) -> Dependency {
self.set_default_features(false);
self.set_features(features.into_iter().copied());
self
}
}

pub trait ToPkgId {
Expand Down Expand Up @@ -448,8 +492,8 @@ impl<T: AsRef<str>, U: AsRef<str>> ToPkgId for (T, U) {

#[macro_export]
macro_rules! pkg {
($pkgid:expr => [$($deps:expr),+ $(,)* ]) => ({
let d: Vec<Dependency> = vec![$($deps.to_dep()),+];
($pkgid:expr => [$($deps:expr),* $(,)? ]) => ({
let d: Vec<Dependency> = vec![$($deps.to_dep()),*];
$crate::pkg_dep($pkgid, d)
});

Expand All @@ -473,18 +517,29 @@ pub fn pkg<T: ToPkgId>(name: T) -> Summary {
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())
Some(pkgid.name())
} else {
None
};
Summary::new(
name.to_pkgid(),
dep,
&BTreeMap::new(),
link,
None::<RustVersion>,
)
.unwrap()
Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), link, None).unwrap()
}

pub fn pkg_dep_with<T: ToPkgId>(
name: T,
dep: Vec<Dependency>,
features: &[(&'static str, &[&'static str])],
) -> Summary {
let pkgid = name.to_pkgid();
let link = if pkgid.name().ends_with("-sys") {
Some(pkgid.name())
} else {
None
};
let features = features
.into_iter()
.map(|&(name, values)| (name.into(), values.into_iter().map(|&v| v.into()).collect()))
.collect();
Summary::new(name.to_pkgid(), dep, &features, link, None).unwrap()
}

pub fn pkg_id(name: &str) -> PackageId {
Expand All @@ -510,7 +565,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary {
Vec::new(),
&BTreeMap::new(),
link,
None::<RustVersion>,
None,
)
.unwrap()
}
Expand All @@ -519,14 +574,7 @@ 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()
Summary::new(sum.package_id(), deps, &BTreeMap::new(), sum.links(), None).unwrap()
}

pub fn dep(name: &str) -> Dependency {
Expand Down Expand Up @@ -629,7 +677,7 @@ fn meta_test_deep_pretty_print_registry() {
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")]),
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")),
Expand All @@ -654,8 +702,8 @@ fn meta_test_deep_pretty_print_registry() {
}

/// This generates a random registry index.
/// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..)
/// This strategy has a high probability of having valid dependencies
/// 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,
Expand Down
Loading

0 comments on commit 03bdcb6

Please sign in to comment.