Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ via PyO3 that accelerates rez resolves while leaving the rest of rez untouched.
### Status

- ✅ **Solver** — complete and rez-faithful.
- ✅ **Validated 1:1** against rez's bundled 188-case benchmark dataset on
**solve status** and the **resolved `(name, version)` set** the
differential test currently does not enforce variant-index parity, only
same packages at the same versions.
- ✅ **Validated 1:1** against rez's bundled 188-case benchmark dataset
on **solve status**, the **resolved `(name, version)` set**, **and the
variant index** rez picked for each. The differential test enforces all
three; a divergence on any is a release blocker.
- ✅ **Fast** — see the [Benchmark](#benchmark) section below for a local
apples-to-apples measurement against rez 3.3.0.
- ✅ **Python bridge** — `pyrer.solve(...)` runs the ported solver (the
Expand Down
57 changes: 44 additions & 13 deletions crates/rer-resolver/tests/test_rez_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
//!
//! Every case must match rez exactly — this is a strict 1:1 gate:
//! * **Solve status** must match rez (`success` ↔ solved, `failed` ↔ failed).
//! * For solved cases, rer's resolved package set must equal rez's exactly
//! (ignoring order and variant index).
//! * For solved cases, rer's resolved package set must equal rez's exactly,
//! including the variant index (rez records each variant as
//! `"name/version/package.py[idx]"` — `[]` means "no variant", `[N]` means
//! variant N). A divergence on variant index — same package, same version,
//! different variant — is a real semantic difference (the variant's own
//! `requires` differ) and is treated as a test failure.
//! * A solver panic is always a hard failure.
//!
//! The test is `#[ignore]`d because the full release run takes minutes; it is
Expand Down Expand Up @@ -75,16 +79,28 @@ fn load_json<T: for<'de> Deserialize<'de>>(name: &str) -> Option<T> {
)
}

/// Normalize rez's resolved-package list (`"name/version/package.py[idx]"`) to
/// a sorted, deduped `(name, version)` set.
fn normalize_rez(entries: &[String]) -> Vec<(String, String)> {
let mut out: Vec<(String, String)> = entries
/// Normalize rez's resolved-package list (`"name/version/package.py[idx]"`)
/// to a sorted, deduped `(name, version, variant_index)` set.
///
/// `[]` (empty brackets) → `None`; `[N]` → `Some(N)`. Anything else is
/// skipped as malformed.
fn normalize_rez(entries: &[String]) -> Vec<(String, String, Option<usize>)> {
let mut out: Vec<(String, String, Option<usize>)> = entries
.iter()
.filter_map(|entry| {
let mut parts = entry.splitn(3, '/');
let name = parts.next()?.to_string();
let version = parts.next()?.to_string();
Some((name, version))
let suffix = parts.next()?; // e.g. "package.py[1]" or "package.py[]"
let open = suffix.find('[')?;
let close = suffix.rfind(']')?;
let inner = &suffix[open + 1..close];
let variant_index = if inner.is_empty() {
None
} else {
Some(inner.parse::<usize>().ok()?)
};
Some((name, version, variant_index))
})
.collect();
out.sort();
Expand All @@ -94,20 +110,21 @@ fn normalize_rez(entries: &[String]) -> Vec<(String, String)> {

/// Run one resolve through the rez-faithful solver.
///
/// Returns `Some(set)` of `(name, version)` pairs on success, or `None` if the
/// solve failed (including a construction error — rez would error, but the
/// benchmark records no error cases, so we treat it as a failed solve).
fn solve(request: &[String], repo: Rc<PackageRepo>) -> Option<Vec<(String, String)>> {
/// Returns `Some(set)` of `(name, version, variant_index)` triples on
/// success, or `None` if the solve failed (including a construction error —
/// rez would error, but the benchmark records no error cases, so we treat
/// it as a failed solve).
fn solve(request: &[String], repo: Rc<PackageRepo>) -> Option<Vec<(String, String, Option<usize>)>> {
let reqs: Vec<Requirement> = request.iter().map(|s| Requirement::parse(s)).collect();
let mut solver = Solver::new(reqs, repo).ok()?;
solver.solve();
match solver.status() {
SolverStatus::Solved => {
let mut set: Vec<(String, String)> = solver
let mut set: Vec<(String, String, Option<usize>)> = solver
.resolved_packages()
.unwrap()
.iter()
.map(|v| (v.name().to_string(), v.version().to_string()))
.map(|v| (v.name().to_string(), v.version().to_string(), v.index()))
.collect();
set.sort();
set.dedup();
Expand Down Expand Up @@ -142,6 +159,7 @@ fn test_rez_benchmark_correctness() {
let mut exact = 0usize;
let mut both_failed = 0usize;
let mut divergent: Vec<usize> = Vec::new();
let mut divergent_details: Vec<String> = Vec::new();
let mut status_mismatch: Vec<String> = Vec::new();
let mut panicked: Vec<usize> = Vec::new();

Expand Down Expand Up @@ -183,7 +201,17 @@ fn test_rez_benchmark_correctness() {
if rer_set == rez_set {
exact += 1;
} else {
// Build a compact diff: which (name, version, variant)
// entries are only in one side. Helps narrow down whether
// the divergence is a missing package, a wrong version,
// or just a different variant index.
let rer_only: Vec<_> = rer_set.iter().filter(|t| !rez_set.contains(t)).collect();
let rez_only: Vec<_> = rez_set.iter().filter(|t| !rer_set.contains(t)).collect();
divergent.push(i);
divergent_details.push(format!(
"case {i}: request={:?}\n rer-only: {:?}\n rez-only: {:?}",
case.request, rer_only, rez_only
));
}
}
None => {
Expand Down Expand Up @@ -211,6 +239,9 @@ fn test_rez_benchmark_correctness() {
);
if !divergent.is_empty() {
println!("divergent cases (solved, but a different set than rez): {divergent:?}");
for line in &divergent_details {
println!("{line}");
}
}
if !panicked.is_empty() {
println!("cases that panicked the solver: {panicked:?}");
Expand Down
4 changes: 0 additions & 4 deletions docs/content/docs/getting-started/rez-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,6 @@ solver for those resolves:
above falls back when these are configured.
- **Cyclic-failure detail.** Both solvers detect cycles; the human-
readable failure message differs in wording.
- **Variant-index parity.** The differential test currently checks
the resolved `(name, version)` set, not the variant index — variant
selection is rez-faithful by construction but is not enforced by
the test suite. See the [README's *Validated 1:1* note](../../../../#status).

## Sanity-checking against rez

Expand Down
Loading