tests/release_gates.rs
//! End-to-end coverage for the release gates `nexus_floor`,
//! `integration_roundtrip` and `docs_fresh` through their *public* API
//! (`nornir::release::gate::*` + `nornir::docs::*`) — the same entry points
//! the MCP, gRPC and CLI surfaces call.
//!
//! The gate functions themselves carry unit tests inline in
//! `src/release/gate.rs`; this file exercises the real cross-module wiring,
//! most importantly `docs_fresh`, whose check lives in the `docs` module and
//! can only be driven with a real `.nornir/` scaffold + rendered artifact on
//! disk.
use std::path::Path;
use nornir::bench::{BenchResult, BenchRun};
use nornir::docs::{self, RepoLayout};
use nornir::release::gate;
fn write(p: &Path, body: &str) {
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(p, body).unwrap();
}
fn result(name: &str, kv: &[(&str, f64)]) -> BenchResult {
let mut r = BenchResult {
name: name.into(),
metrics: Default::default(),
source: Default::default(),
};
for (k, v) in kv {
r.metrics
.insert((*k).to_string(), serde_json::Value::from(*v));
}
r
}
fn run(results: Vec<BenchResult>) -> BenchRun {
BenchRun {
// this fixture is where the run is made, so it mints the id; it did
// not look at the box
id: BenchRun::new_id(),
machine_facts: None,
date: "2026-01-01".into(),
timestamp: Some("2026-01-01T00:00:00+00:00".into()),
version: "0.1.0".into(),
machine: "ci".into(),
cores: 1,
results,
tests: Vec::new(),
}
}
// ─── nexus_floor ────────────────────────────────────────────────────────
#[test]
fn nexus_floor_pass_and_fail() {
// Pass: holger throughput at/above the nexus floor for every result.
let green = run(vec![result(
"decode",
&[("holger_ops_sec", 4200.0), ("nexus_ops_sec", 1000.0)],
)]);
assert!(gate::nexus_floor(&green).is_ok());
// Fail: one result dips below the floor → release-blocking.
let red = run(vec![result(
"decode",
&[("holger_ops_sec", 800.0), ("nexus_ops_sec", 1000.0)],
)]);
let err = gate::nexus_floor(&red).unwrap_err().to_string();
assert!(err.contains("nexus floor"), "got: {err}");
}
// ─── integration_roundtrip ──────────────────────────────────────────────
#[test]
fn integration_roundtrip_pass_and_fail() {
// Pass: every kind's round-trip closure succeeds.
let ok = gate::integration_roundtrip(&["blob", "symbol"], |_k| Ok(()));
assert!(ok.is_ok());
// Fail: a kind's round-trip fails → error is contextualised with the kind.
let err = gate::integration_roundtrip(&["blob", "symbol"], |k| {
if k == "symbol" {
anyhow::bail!("fetch != publish")
}
Ok(())
})
.unwrap_err();
assert!(
format!("{err:#}").contains("roundtrip failed for symbol"),
"got: {err:#}"
);
}
// ─── docs_fresh ─────────────────────────────────────────────────────────
/// Scaffold a `.nornir/README.md` source whose body is fully static (no
/// `nornir:gen` markers) so the rendered output is deterministic and doesn't
/// depend on bench data — keeps the freshness check about render-drift only.
fn scaffold_repo(root: &Path) {
let layout = RepoLayout::new(root);
std::fs::create_dir_all(layout.nornir_dir()).unwrap();
write(
&layout.source_of("README.md"),
"# demo\n\nHand-authored source under `.nornir/`.\n",
);
}
fn ctx_for<'a>(root: &'a Path, run: &'a BenchRun) -> docs::Ctx<'a> {
// workspace_root == repo_root is fine for a single-repo fixture.
docs::Ctx::new(root, root, Some(run))
.with_path_dep_cloner(&nornir::security::docs_path_dep_cloner)
}
#[test]
fn docs_fresh_passes_right_after_render() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
scaffold_repo(root);
let layout = RepoLayout::new(root);
let run = run(vec![result("decode", &[("holger_ops_sec", 1.0)])]);
// Render writes the generated artifact to disk…
docs::render_all(&layout, &ctx_for(root, &run)).unwrap();
// …so the freshness gate must now pass (disk == fresh in-memory render).
docs::render_check_all(&layout, &ctx_for(root, &run))
.expect("docs_fresh should pass immediately after render");
}
#[test]
fn docs_fresh_fails_when_output_is_stale() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
scaffold_repo(root);
let layout = RepoLayout::new(root);
let run = run(vec![result("decode", &[("holger_ops_sec", 1.0)])]);
docs::render_all(&layout, &ctx_for(root, &run)).unwrap();
// Tamper with the generated output (drift the source-of-truth vs artifact).
// The output is read-only after render, so re-grant write first.
let out = layout.output_of("README.md");
let mut perms = std::fs::metadata(&out).unwrap().permissions();
#[allow(clippy::permissions_set_readonly_false)]
perms.set_readonly(false);
std::fs::set_permissions(&out, perms).unwrap();
std::fs::write(&out, "# tampered — out of sync with .nornir/README.md\n").unwrap();
let err = docs::render_check_all(&layout, &ctx_for(root, &run))
.expect_err("docs_fresh must fail on stale output");
let msg = format!("{err:#}");
assert!(msg.contains("out of date"), "got: {msg}");
}
#[test]
fn docs_fresh_fails_when_source_edited_but_not_re_rendered() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
scaffold_repo(root);
let layout = RepoLayout::new(root);
let run = run(vec![result("decode", &[("holger_ops_sec", 1.0)])]);
docs::render_all(&layout, &ctx_for(root, &run)).unwrap();
// Edit the SOURCE but forget to re-render → the artifact is now stale.
write(
&layout.source_of("README.md"),
"# demo\n\nNEW paragraph added to the source after the last render.\n",
);
let err = docs::render_check_all(&layout, &ctx_for(root, &run))
.expect_err("editing the source without re-rendering must fail docs_fresh");
assert!(format!("{err:#}").contains("out of date"), "got: {err:#}");
}
/// Regression: `docs_fresh` (via `assemble_and_check`) must NOT hinge on a
/// bench run existing. A repo with no recorded `BenchRun` (e.g. nornir itself,
/// which ships no `nornir-bench.rs`) used to false-fail the gate with
/// "no bench runs in iceberg …". With `run = None`:
/// - a GENERATED README (carrying the marker) passes outright;
/// - a hand-written README passes too (no version to assert without a run).
/// A hand-written README WITH a run still enforces the `v<version>` headline.
#[test]
fn assemble_and_check_passes_without_a_bench_run() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// 1. Generated README + no run → Ok (the marker short-circuits).
write(
&root.join("README.md"),
&format!("{}\n# demo\n\ngenerated body\n", docs::GENERATED_MARKER),
);
docs::assemble_and_check(root, None)
.expect("generated README must pass docs_fresh with no bench run");
// 2. Hand-written README (no marker) + no run → Ok (nothing to assert).
write(
root.join("README.md").as_path(),
"# demo\n\nhand-written, no version line\n",
);
docs::assemble_and_check(root, None)
.expect("hand-written README must pass docs_fresh when there is no run to name a version");
// 3. Hand-written README + a run → still enforces the v<version> headline.
let r = run(vec![result("decode", &[("holger_ops_sec", 1.0)])]); // version 0.1.0
let err = docs::assemble_and_check(root, Some(&r))
.expect_err("hand-written README missing v0.1.0 must fail when a run is present");
assert!(format!("{err:#}").contains("v0.1.0"), "got: {err:#}");
write(
root.join("README.md").as_path(),
"# demo v0.1.0\n\nnow names the version\n",
);
docs::assemble_and_check(root, Some(&r)).expect("hand-written README naming v0.1.0 must pass");
}
// ─── plan #5: release test-DRIVE (tests_gate + run_tests_gate) ──────────────
//
// Two layers, both through the PUBLIC API the MCP/gRPC/CLI gate calls:
// * `tests_gate` — the pure verdict over already-collected rows (green→Ok,
// red→Err, regression named). Fast; runs by default.
// * `run_tests_gate` — the HEAVY end-to-end drive: it actually compiles +
// runs a fixture crate's matrix, folds with history, and verdicts. Gated
// `#[ignore]` (heavy: shells cargo) per the heavy-test policy.
use nornir::test_matrix::Aspect;
use nornir::warehouse::test_results::{TestResultRow, status};
#[test]
fn tests_gate_green_pass_red_fail_regression_through_public_api() {
// Green newest run → Ok.
let green = vec![
TestResultRow::unit("g", "fix", "fix", "t::ok", status::PASS, 1.0, 100, ""),
TestResultRow::unit("g", "fix", "fix", "t::ok2", status::PASS, 1.0, 100, ""),
];
gate::tests_gate(&green).expect("all-green run passes the release tests gate");
// No run recorded → RED (a release must have exercised its tests).
assert!(gate::tests_gate(&[]).is_err(), "no run → gate red");
// Newest run red → Err, and a test green-last-time now failing is flagged a
// REGRESSION.
let regressed = vec![
TestResultRow::unit("g", "fix", "fix", "t::ok", status::PASS, 1.0, 100, ""),
TestResultRow::unit("r", "fix", "fix", "t::ok", status::FAIL, 1.0, 200, "broke"),
];
let err = gate::tests_gate(®ressed).unwrap_err().to_string();
assert!(err.contains("RED"), "{err}");
assert!(err.contains("REGRESSION"), "names the regression: {err}");
assert!(err.contains("t::ok"), "names the regressed test: {err}");
}
/// Scaffold a minimal cargo crate at `root` whose single unit test passes
/// (`pass=true`) or fails (`pass=false`).
fn scaffold_test_fixture(root: &Path, pass: bool) {
write(
&root.join("Cargo.toml"),
"[package]\nname = \"fix\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n[dependencies]\n",
);
let body = if pass {
"#[test]\nfn it_works() { assert_eq!(2 + 2, 4); }\n"
} else {
"#[test]\nfn it_works() { assert_eq!(2 + 2, 5); }\n"
};
write(&root.join("src/lib.rs"), body);
}
#[test]
#[ignore = "heavy: compiles + cargo-runs a fixture crate's test matrix; use --include-ignored to run"]
fn run_tests_gate_drives_matrix_and_passes_green_fails_red() {
// Only the Unit aspect keeps the drive fast + toolchain-light (no clippy/fmt/
// audit required on the box).
let aspects = vec![Aspect::Unit];
// GREEN fixture: the drive runs the matrix, the gate passes, and the freshly
// -run rows come back (so the caller can persist them).
let green_dir = tempfile::tempdir().unwrap();
scaffold_test_fixture(green_dir.path(), true);
let fresh = gate::run_tests_gate(green_dir.path(), &[], &aspects)
.expect("green fixture passes the release tests gate");
assert!(!fresh.is_empty(), "the drive produced + returned test rows");
assert!(
fresh.iter().any(|r| r.status == status::PASS),
"the green fixture's passing test is recorded"
);
assert!(
!fresh.iter().any(|r| status::is_red(&r.status)),
"no red rows in the green run"
);
// RED fixture: the same drive must FAIL the gate (the failing test is red).
let red_dir = tempfile::tempdir().unwrap();
scaffold_test_fixture(red_dir.path(), false);
let err = gate::run_tests_gate(red_dir.path(), &[], &aspects)
.expect_err("red fixture must fail the release tests gate")
.to_string();
assert!(err.contains("RED"), "got: {err}");
}