diff --git a/crates/resolver-tests/README.md b/crates/resolver-tests/README.md new file mode 100644 index 000000000000..ee777fe0896b --- /dev/null +++ b/crates/resolver-tests/README.md @@ -0,0 +1,13 @@ +# resolver-tests + +## The aim + +This crate aims to test the resolution of cargo's resolver. It implements a [SAT solver](https://en.wikipedia.org/wiki/SAT_solver) to compare resolution of cargo's resolver. +This ensures that cargo's dependency resolution is valid on the [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem). + +## About the test + +The cargo's resolver is very sensitive to what order it tries to evaluate constraints. This makes it incredibly difficult +to be sure that a handful of tests actually covers all the important permutations of decision-making. The tests not only needs +to hit all the corner cases, it needs to try all of the orders of evaluation. So we use fuzz testing to cover more permutations as possible. + diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index 1ced997bd959..0c01f08f55a9 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -31,6 +31,7 @@ pub fn resolve(deps: Vec, registry: &[Summary]) -> CargoResult, registry: &[Summary], @@ -198,6 +199,9 @@ fn log_bits(x: usize) -> usize { (num_bits::() as u32 - x.leading_zeros()) as usize } +// At this point is possible to select every version of every package. +// So we need to mark certain versions as incompatible with each other. +// We could add a clause not A, not B for all A and B that are incompatible, fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Var]) { if vars.len() <= 1 { return; @@ -210,6 +214,8 @@ fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Va solver.add_clause(&[vars[1].negative(), vars[2].negative()]); return; } + // There are more efficient ways to do it for large numbers of versions. + // // 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 = solver.new_var_iter(log_bits(vars.len())).collect(); @@ -257,6 +263,7 @@ struct SatResolveInner { impl SatResolve { 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. let var_for_is_packages_used: HashMap = registry .iter() .map(|s| (s.package_id(), cnf.new_var())) @@ -290,6 +297,10 @@ impl SatResolve { let empty_vec = vec![]; + // Now different versions can conflict, but dependencies might not be selected. + // So we need to add clauses that ensure that if a package is selected for each dependency a version + // that satisfies that dependency is selected. + // // active packages need each of there `deps` to be satisfied for p in registry.iter() { for dep in p.dependencies() {