diff --git a/crates/rattler_libsolv_rs/Cargo.toml b/crates/rattler_libsolv_rs/Cargo.toml index c8da21450..98d9a45f1 100644 --- a/crates/rattler_libsolv_rs/Cargo.toml +++ b/crates/rattler_libsolv_rs/Cargo.toml @@ -19,3 +19,4 @@ elsa = "1.9.0" [dev-dependencies] insta = "1.31.0" indexmap = "2.0.0" +tracing-test = "0.2.4" diff --git a/crates/rattler_libsolv_rs/src/id.rs b/crates/rattler_libsolv_rs/src/id.rs index f552c7121..a4e53e39a 100644 --- a/crates/rattler_libsolv_rs/src/id.rs +++ b/crates/rattler_libsolv_rs/src/id.rs @@ -1,4 +1,7 @@ use crate::arena::ArenaId; +use crate::solvable::DisplaySolvable; +use crate::{PackageName, Pool, VersionSet}; +use std::fmt::Display; /// The id associated to a package name #[repr(transparent)] @@ -51,6 +54,14 @@ impl SolvableId { pub(crate) fn is_null(self) -> bool { self.0 == u32::MAX } + + /// Returns an object that enables formatting the solvable. + pub fn display( + self, + pool: &Pool, + ) -> DisplaySolvable { + pool.resolve_internal_solvable(self).display(pool) + } } impl ArenaId for SolvableId { diff --git a/crates/rattler_libsolv_rs/src/problem.rs b/crates/rattler_libsolv_rs/src/problem.rs index 44a641fc7..bd6ab5c23 100644 --- a/crates/rattler_libsolv_rs/src/problem.rs +++ b/crates/rattler_libsolv_rs/src/problem.rs @@ -304,7 +304,7 @@ impl ProblemGraph { } } - pool.resolve_internal_solvable(solvable_2).to_string() + solvable_2.display(pool).to_string() } ProblemNode::UnresolvedDependency => "unresolved".to_string(), }; @@ -312,7 +312,8 @@ impl ProblemGraph { write!( f, "\"{}\" -> \"{}\"[color={color}, label=\"{label}\"];", - solvable, target + solvable.display(pool), + target )?; } } diff --git a/crates/rattler_libsolv_rs/src/solvable.rs b/crates/rattler_libsolv_rs/src/solvable.rs index f6ad63768..896c6feb5 100644 --- a/crates/rattler_libsolv_rs/src/solvable.rs +++ b/crates/rattler_libsolv_rs/src/solvable.rs @@ -1,5 +1,6 @@ use crate::id::NameId; +use crate::{PackageName, Pool, VersionSet}; use std::fmt::{Display, Formatter}; /// A solvable represents a single candidate of a package. @@ -57,13 +58,30 @@ impl InternalSolvable { pub fn solvable(&self) -> &Solvable { self.get_solvable().expect("unexpected root solvable") } + + pub fn display<'pool, VS: VersionSet, N: PackageName + Display>( + &'pool self, + pool: &'pool Pool, + ) -> DisplaySolvable<'pool, VS, N> { + DisplaySolvable { + pool, + solvable: self, + } + } } -impl Display for InternalSolvable { +pub struct DisplaySolvable<'pool, VS: VersionSet, N: PackageName> { + pool: &'pool Pool, + solvable: &'pool InternalSolvable, +} + +impl<'pool, VS: VersionSet, N: PackageName + Display> Display for DisplaySolvable<'pool, VS, N> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match &self.inner { + match &self.solvable.inner { SolvableInner::Root => write!(f, ""), - SolvableInner::Package(p) => write!(f, "{}", &p.inner), + SolvableInner::Package(p) => { + write!(f, "{}={}", self.pool.resolve_package_name(p.name), &p.inner) + } } } } diff --git a/crates/rattler_libsolv_rs/src/solver/clause.rs b/crates/rattler_libsolv_rs/src/solver/clause.rs index dfc2af993..828936877 100644 --- a/crates/rattler_libsolv_rs/src/solver/clause.rs +++ b/crates/rattler_libsolv_rs/src/solver/clause.rs @@ -490,15 +490,15 @@ impl Debug for ClauseDebug<'_, VS, N> write!( f, "{} requires {match_spec}", - self.pool.resolve_internal_solvable(solvable_id) + solvable_id.display(self.pool), ) } Clause::Constrains(s1, s2, vset_id) => { write!( f, "{} excludes {} by {}", - self.pool.resolve_internal_solvable(s1), - self.pool.resolve_internal_solvable(s2), + s1.display(self.pool), + s2.display(self.pool), self.pool.resolve_version_set(vset_id) ) } @@ -506,8 +506,8 @@ impl Debug for ClauseDebug<'_, VS, N> write!( f, "{} is locked, so {} is forbidden", - self.pool.resolve_internal_solvable(locked), - self.pool.resolve_internal_solvable(forbidden) + locked.display(self.pool), + forbidden.display(self.pool) ) } Clause::ForbidMultipleInstances(s1, _) => { diff --git a/crates/rattler_libsolv_rs/src/solver/mod.rs b/crates/rattler_libsolv_rs/src/solver/mod.rs index fac3b7a4a..470c01623 100644 --- a/crates/rattler_libsolv_rs/src/solver/mod.rs +++ b/crates/rattler_libsolv_rs/src/solver/mod.rs @@ -11,7 +11,6 @@ use crate::{ }; use elsa::{FrozenMap, FrozenVec}; -use itertools::Itertools; use std::{collections::HashSet, fmt::Display, marker::PhantomData}; use clause::{Clause, ClauseState, Literal}; @@ -454,10 +453,7 @@ impl> Sol .map_err(|_| clause_id)?; if decided { - tracing::info!( - "Set {} = false", - self.pool().resolve_internal_solvable(solvable_id) - ); + tracing::info!("Set {} = false", solvable_id.display(self.pool())); } } } @@ -552,9 +548,9 @@ impl> Sol level += 1; tracing::info!( - "=== Install {} at level {level} (required by {})", - self.pool().resolve_internal_solvable(solvable), - self.pool().resolve_internal_solvable(required_by), + "╤══ Install {} at level {level} (required by {})", + solvable.display(self.pool()), + required_by.display(self.pool()), ); self.decision_tracker @@ -565,22 +561,22 @@ impl> Sol let r = self.propagate(level); let Err((conflicting_solvable, attempted_value, conflicting_clause)) = r else { // Propagation succeeded - tracing::info!("=== Propagation succeeded"); + tracing::info!("╘══ Propagation succeeded"); break; }; { tracing::info!( - "=== Propagation conflicted: could not set {solvable} to {attempted_value}", - solvable = self.pool().resolve_internal_solvable(conflicting_solvable) + "├─ Propagation conflicted: could not set {solvable} to {attempted_value}", + solvable = solvable.display(self.pool()) ); tracing::info!( - "During unit propagation for clause: {:?}", + "│ During unit propagation for clause: {:?}", self.clauses[conflicting_clause].debug(self.pool()) ); tracing::info!( - "Previously decided value: {}. Derived from: {:?}", + "│ Previously decided value: {}. Derived from: {:?}", !attempted_value, self.clauses[self .decision_tracker @@ -594,7 +590,7 @@ impl> Sol } if level == 1 { - tracing::info!("=== UNSOLVABLE"); + tracing::info!("╘══ UNSOLVABLE"); for decision in self.decision_tracker.stack() { let clause = &self.clauses[decision.derived_from]; let level = self.decision_tracker.level(decision.solvable_id); @@ -607,7 +603,7 @@ impl> Sol tracing::info!( "* ({level}) {action} {}. Reason: {:?}", - self.pool().resolve_internal_solvable(decision.solvable_id), + decision.solvable_id.display(self.pool()), clause.debug(self.pool()), ); } @@ -619,7 +615,7 @@ impl> Sol self.analyze(level, conflicting_solvable, conflicting_clause); level = new_level; - tracing::info!("=== Backtracked to level {level}"); + tracing::info!("├─ Backtracked to level {level}"); // Optimization: propagate right now, since we know that the clause is a unit clause let decision = literal.satisfying_value(); @@ -630,8 +626,8 @@ impl> Sol ) .expect("bug: solvable was already decided!"); tracing::info!( - "=== Propagate after learn: {} = {decision}", - self.pool().resolve_internal_solvable(literal.solvable_id) + "├─ Propagate after learn: {} = {decision}", + literal.solvable_id.display(self.pool()) ); } @@ -674,8 +670,8 @@ impl> Sol if decided { tracing::info!( - "Propagate assertion {} = {}", - self.pool().resolve_internal_solvable(literal.solvable_id), + "├─ Propagate assertion {} = {}", + literal.solvable_id.display(self.pool()), decision ); } @@ -766,10 +762,8 @@ impl> Sol Clause::ForbidMultipleInstances(..) => {} _ => { tracing::info!( - "Propagate {} = {}. {:?}", - self.provider - .pool() - .resolve_internal_solvable(remaining_watch.solvable_id), + "├─ Propagate {} = {}. {:?}", + remaining_watch.solvable_id.display(self.provider.pool()), remaining_watch.satisfying_value(), clause.debug(self.provider.pool()), ); @@ -983,16 +977,14 @@ impl> Sol self.watches.start_watching(clause, clause_id); } - tracing::info!( - "Learnt disjunction:\n{}", - learnt - .into_iter() - .format_with("\n", |lit, f| f(&format_args!( - "- {}{}", - if lit.negate { "NOT " } else { "" }, - self.pool().resolve_internal_solvable(lit.solvable_id) - ))) - ); + tracing::info!("├─ Learnt disjunction:",); + for lit in learnt { + tracing::info!( + "│ - {}{}", + if lit.negate { "NOT " } else { "" }, + lit.solvable_id.display(self.pool()) + ); + } // Should revert at most to the root level let target_level = back_track_to.max(1); @@ -1289,7 +1281,7 @@ mod test { use std::fmt::Write; let mut buf = String::new(); for &solvable_id in solvables { - writeln!(buf, "{}", pool.resolve_internal_solvable(solvable_id)).unwrap(); + writeln!(buf, "{}", solvable_id.display(pool)).unwrap(); } buf @@ -1413,8 +1405,12 @@ mod test { use std::fmt::Write; let mut display_result = String::new(); for &solvable_id in &solved { - let solvable = solver.pool().resolve_internal_solvable(solvable_id); - writeln!(display_result, "{solvable}").unwrap(); + writeln!( + display_result, + "{solvable}", + solvable = solvable_id.display(solver.pool()) + ) + .unwrap(); } insta::assert_snapshot!(display_result); @@ -1521,8 +1517,8 @@ mod test { let result = transaction_to_string(&solver.pool(), &solved); insta::assert_snapshot!(result, @r###" - 2 - 1 + b=2 + a=1 "###); } // @@ -1554,9 +1550,9 @@ mod test { let result = transaction_to_string(&solver.pool(), &solved); insta::assert_snapshot!(result, @r###" - 2 - 2 - 2 + b=2 + c=2 + a=2 "###); } @@ -1572,8 +1568,8 @@ mod test { let result = transaction_to_string(&solver.pool(), &solved); insta::assert_snapshot!(result, @r###" - 2 - 5 + a=2 + b=5 "###); } @@ -1716,4 +1712,62 @@ mod test { let error = solve_unsat(provider, &["a", "c"]); insta::assert_snapshot!(error); } + + #[test] + #[tracing_test::traced_test] + fn test_unsat_constrains_2() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("a", 1, vec!["b"]), + ("a", 2, vec!["b"]), + ("b", 1, vec!["c 1"]), + ("b", 2, vec!["c 2"]), + ]); + + provider.add_package("c", 1.into(), &vec![], &vec!["a 3"]); + provider.add_package("c", 2.into(), &vec![], &vec!["a 3"]); + let error = solve_unsat(provider, &["a"]); + insta::assert_snapshot!(error); + } + + #[test] + fn test_missing_dep() { + let provider = + BundleBoxProvider::from_packages(&[("a", 2, vec!["missing"]), ("a", 1, vec![])]); + let requirements = provider.requirements(&["a"]); + let mut solver = Solver::new(provider); + let result = match solver.solve(requirements) { + Ok(result) => transaction_to_string(solver.pool(), &result), + Err(problem) => problem + .display_user_friendly(&solver, &DefaultSolvableDisplay) + .to_string(), + }; + insta::assert_snapshot!(result); + } + + #[test] + #[tracing_test::traced_test] + fn test_no_backtracking() { + let provider = BundleBoxProvider::from_packages(&[ + ("quetz-server", 2, vec!["pydantic 10..20"]), + ("quetz-server", 1, vec!["pydantic 0..10"]), + ("pydantic", 1, vec![]), + ("pydantic", 2, vec![]), + ("pydantic", 3, vec![]), + ("pydantic", 4, vec![]), + ("pydantic", 5, vec![]), + ("pydantic", 6, vec![]), + ("pydantic", 7, vec![]), + ("pydantic", 8, vec![]), + ("pydantic", 9, vec![]), + ("pydantic", 10, vec![]), + ("pydantic", 11, vec![]), + ("pydantic", 12, vec![]), + ("pydantic", 13, vec![]), + ("pydantic", 14, vec![]), + ]); + let requirements = provider.requirements(&["quetz-server", "pydantic 0..10"]); + let mut solver = Solver::new(provider); + let solved = solver.solve(requirements).unwrap(); + insta::assert_snapshot!(transaction_to_string(solver.pool(), &solved)); + } } diff --git a/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__missing_dep.snap b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__missing_dep.snap new file mode 100644 index 000000000..eef54c319 --- /dev/null +++ b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__missing_dep.snap @@ -0,0 +1,6 @@ +--- +source: crates/rattler_libsolv_rs/src/solver/mod.rs +expression: result +--- +a=1 + diff --git a/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__no_backtracking.snap b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__no_backtracking.snap new file mode 100644 index 000000000..73fc6f210 --- /dev/null +++ b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__no_backtracking.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_libsolv_rs/src/solver/mod.rs +expression: "transaction_to_string(solver.pool(), &solved)" +--- +quetz-server=1 +pydantic=9 + diff --git a/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__resolve_with_conflict.snap b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__resolve_with_conflict.snap index bda53b091..e15535f29 100644 --- a/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__resolve_with_conflict.snap +++ b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__resolve_with_conflict.snap @@ -2,7 +2,7 @@ source: crates/rattler_libsolv_rs/src/solver/mod.rs expression: display_result --- -0 -3 -7 +conflicting=0 +asdf=3 +efgh=7 diff --git a/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__unsat_constrains_2.snap b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__unsat_constrains_2.snap new file mode 100644 index 000000000..38b75e9b3 --- /dev/null +++ b/crates/rattler_libsolv_rs/src/solver/snapshots/rattler_libsolv_rs__solver__test__unsat_constrains_2.snap @@ -0,0 +1,27 @@ +--- +source: crates/rattler_libsolv_rs/src/solver/mod.rs +expression: error +--- +The following packages are incompatible +|-- a * cannot be installed because there are no viable options: + |-- a 2 would require + |-- b *, which cannot be installed because there are no viable options: + |-- b 2 would require + |-- c 2..3, which cannot be installed because there are no viable options: + |-- c 2 would constrain + |-- a 3..4 , which conflicts with any installable versions previously reported + |-- b 1 would require + |-- c 1..2, which cannot be installed because there are no viable options: + |-- c 1 would constrain + |-- a 3..4 , which conflicts with any installable versions previously reported + |-- a 1 would require + |-- b *, which cannot be installed because there are no viable options: + |-- b 2 would require + |-- c 2..3, which cannot be installed because there are no viable options: + |-- c 2 would constrain + |-- a 3..4 , which conflicts with any installable versions previously reported + |-- b 1 would require + |-- c 1..2, which cannot be installed because there are no viable options: + |-- c 1 would constrain + |-- a 3..4 , which conflicts with any installable versions previously reported +