use std::path::Path;
use anyhow::{anyhow, Context, Result};
use crate::bench::{history, BenchRun};
pub fn no_path_patches(repo_root: &Path) -> Result<()> {
let cargo = repo_root.join("Cargo.toml");
let text = std::fs::read_to_string(&cargo)
.with_context(|| format!("read {}", cargo.display()))?;
let mut in_patch = false;
for (i, line) in text.lines().enumerate() {
let l = line.trim();
if l.starts_with('#') {
continue;
}
if l.starts_with('[') {
in_patch = l.starts_with("[patch.crates-io")
|| l.starts_with("[patch.\"crates-io\"");
continue;
}
if in_patch && mentions_local_patch_source(l) {
return Err(anyhow!(
"[patch.crates-io] local/forked patch (path=/git=) at {}:{} — \
stripped on publish; strip before release",
cargo.display(),
i + 1
));
}
}
Ok(())
}
fn mentions_local_patch_source(l: &str) -> bool {
for key in ["path", "git"] {
let mut search_from = 0;
while let Some(rel) = l[search_from..].find(key) {
let pos = search_from + rel;
let before_is_ident = l[..pos]
.chars()
.next_back()
.map(|c| c.is_alphanumeric() || c == '_' || c == '-')
.unwrap_or(false);
let after_eq = l[pos + key.len()..].trim_start().starts_with('=');
if !before_is_ident && after_eq {
return true;
}
search_from = pos + key.len();
}
}
false
}
pub fn nexus_floor(run: &BenchRun) -> Result<()> {
for r in &run.results {
let h = r.metrics.get("holger_ops_sec").and_then(|v| v.as_f64());
let n = r.metrics.get("nexus_ops_sec").and_then(|v| v.as_f64());
if let (Some(h), Some(n)) = (h, n) {
if h < n {
return Err(anyhow!(
"nexus floor: {} holger={:.0} < nexus={:.0}",
r.name,
h,
n
));
}
}
}
Ok(())
}
pub fn no_regression(run: &BenchRun, history_path: &Path, max_drop_pct: f64) -> Result<()> {
let history = history::read_all(history_path)?;
no_regression_against(run, &history, max_drop_pct)
}
pub fn no_regression_against(run: &BenchRun, history: &[BenchRun], max_drop_pct: f64) -> Result<()> {
let same: Vec<&BenchRun> = history.iter().filter(|h| h.machine == run.machine).collect();
let Some(last) = pick_baseline(&same) else {
return Ok(());
};
for r in &run.results {
let Some(prev) = last.find(&r.name) else { continue };
for (key, new_val) in &r.metrics {
let Some(new_f) = new_val.as_f64() else { continue };
let Some(prev_f) = prev.metrics.get(key).and_then(|v| v.as_f64()) else {
continue;
};
if prev_f <= 0.0 {
continue;
}
let drop_pct = (prev_f - new_f) / prev_f * 100.0;
if drop_pct > max_drop_pct {
return Err(anyhow!(
"regression: {} {} dropped {:.1}% ({:.2} → {:.2})",
r.name,
key,
drop_pct,
prev_f,
new_f
));
}
}
}
Ok(())
}
fn pick_baseline<'a>(runs: &[&'a BenchRun]) -> Option<&'a BenchRun> {
if runs.is_empty() {
return None;
}
if runs.iter().all(|r| r.timestamp.is_some()) {
runs.iter().copied().max_by(|a, b| a.timestamp.cmp(&b.timestamp))
} else {
runs.last().copied()
}
}
pub const DEFAULT_REGRESSION_WINDOW: usize = 10;
pub const DEFAULT_REGRESSION_SIGMA: f64 = 3.0;
#[derive(Debug, Clone, Copy)]
pub struct MetricStats {
pub n: usize,
pub mean: f64,
pub stddev: f64,
}
impl MetricStats {
pub fn from_samples(samples: &[f64]) -> Self {
let n = samples.len();
if n == 0 {
return MetricStats { n: 0, mean: 0.0, stddev: 0.0 };
}
let mean = samples.iter().sum::<f64>() / n as f64;
let stddev = if n < 2 {
0.0
} else {
let var = samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1) as f64;
var.sqrt()
};
MetricStats { n, mean, stddev }
}
pub fn drop_sigma(&self, candidate: f64) -> f64 {
if self.stddev <= 0.0 {
return 0.0;
}
(self.mean - candidate) / self.stddev
}
}
fn baseline_window<'a>(machine: &str, history: &'a [BenchRun], window: usize) -> Vec<&'a BenchRun> {
let mut same: Vec<&BenchRun> = history.iter().filter(|h| h.machine == machine).collect();
if same.iter().all(|r| r.timestamp.is_some()) {
same.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
} else {
same.reverse();
}
if window > 0 && same.len() > window {
same.truncate(window);
}
same
}
pub fn no_regression_windowed(
run: &BenchRun,
history: &[BenchRun],
max_drop_pct: f64,
window: usize,
sigma: f64,
) -> Result<()> {
let baseline = baseline_window(&run.machine, history, window);
if baseline.is_empty() {
return Ok(());
}
for r in &run.results {
for (key, new_val) in &r.metrics {
let Some(new_f) = new_val.as_f64() else { continue };
let samples: Vec<f64> = baseline
.iter()
.filter_map(|h| h.find(&r.name))
.filter_map(|res| res.metrics.get(key).and_then(|v| v.as_f64()))
.filter(|f| *f > 0.0)
.collect();
if samples.is_empty() {
continue;
}
let stats = MetricStats::from_samples(&samples);
if stats.mean <= 0.0 {
continue;
}
let drop_pct = (stats.mean - new_f) / stats.mean * 100.0;
if drop_pct <= max_drop_pct {
continue; }
let z = stats.drop_sigma(new_f);
if stats.stddev > 0.0 && z < sigma {
continue;
}
let band = if stats.stddev > 0.0 {
format!("{z:.1}σ over {} run(s)", stats.n)
} else {
format!("no noise estimate, {} run(s)", stats.n)
};
return Err(anyhow!(
"regression: {} {} dropped {:.1}% (μ {:.2} → {:.2}; {})",
r.name,
key,
drop_pct,
stats.mean,
new_f,
band,
));
}
}
Ok(())
}
pub fn integration_roundtrip<F>(kinds: &[&str], mut run_one: F) -> Result<()>
where
F: FnMut(&str) -> Result<()>,
{
for k in kinds {
run_one(k).with_context(|| format!("roundtrip failed for {k}"))?;
}
Ok(())
}
pub fn integration_roundtrip_via_cargo_test(repo_root: &Path, kinds: &[&str]) -> Result<()> {
integration_roundtrip(kinds, |k| {
let test_name = format!("roundtrip_{k}");
let status = std::process::Command::new("cargo")
.args(["test", "--test", &test_name, "--release"])
.current_dir(repo_root)
.status()
.with_context(|| format!("spawn cargo test --test {test_name}"))?;
if !status.success() {
return Err(anyhow!("cargo test --test {test_name} exited {status}"));
}
Ok(())
})
}
#[derive(Debug, Clone)]
pub struct PathDepFinding {
pub manifest: std::path::PathBuf,
pub dep_name: String,
pub dep_path: String,
pub has_version: bool,
pub version_req: Option<String>,
}
impl PathDepFinding {
pub fn ok(&self) -> bool { self.has_version }
}
pub fn path_dep_audit(repo_root: &Path) -> Result<Vec<PathDepFinding>> {
let mut findings = Vec::new();
const AUDITED: [&str; 2] = ["dependencies", "build-dependencies"];
for manifest in walk_cargo_tomls(repo_root)? {
let text = std::fs::read_to_string(&manifest)
.with_context(|| format!("read {}", manifest.display()))?;
let doc: toml::Value = toml::from_str(&text)
.with_context(|| format!("parse {}", manifest.display()))?;
if is_publish_false(&doc) {
continue;
}
for section in AUDITED {
collect_path_deps(&doc, section, &manifest, &mut findings);
}
if let Some(targets) = doc.get("target").and_then(|t| t.as_table()) {
for (_cfg, t) in targets {
for section in AUDITED {
collect_path_deps(t, section, &manifest, &mut findings);
}
}
}
}
Ok(findings)
}
fn is_publish_false(doc: &toml::Value) -> bool {
match doc.get("package").and_then(|p| p.get("publish")) {
Some(toml::Value::Boolean(false)) => true,
Some(toml::Value::Array(a)) => a.is_empty(),
_ => false,
}
}
fn collect_path_deps(
parent: &toml::Value,
section: &str,
manifest: &Path,
out: &mut Vec<PathDepFinding>,
) {
let Some(deps) = parent.get(section).and_then(|d| d.as_table()) else { return };
for (name, v) in deps {
let Some(t) = v.as_table() else { continue };
let path = t.get("path").and_then(|p| p.as_str());
let Some(p) = path else { continue };
let version = t.get("version").and_then(|v| v.as_str()).map(|s| s.to_string());
out.push(PathDepFinding {
manifest: manifest.to_path_buf(),
dep_name: name.clone(),
dep_path: p.to_string(),
has_version: version.is_some(),
version_req: version,
});
}
}
fn walk_cargo_tomls(root: &Path) -> Result<Vec<std::path::PathBuf>> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).with_context(|| format!("read_dir {}", dir.display()))? {
let entry = entry?;
let p = entry.path();
let ft = entry.file_type()?;
if ft.is_symlink() {
continue;
}
if ft.is_dir() {
let cname = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
if matches!(cname, "target" | "node_modules") || cname.starts_with('.') {
continue;
}
stack.push(p);
} else if ft.is_file() && p.file_name().and_then(|n| n.to_str()) == Some("Cargo.toml") {
out.push(p);
}
}
}
Ok(out)
}
#[derive(Debug, Clone)]
pub struct CrateMetaCheck {
pub manifest: std::path::PathBuf,
pub crate_name: String,
pub version: String,
pub has_readme: bool,
pub has_license: bool,
pub license_expr: Option<String>,
pub has_repository: bool,
pub repository_url: Option<String>,
pub has_description: bool,
pub description_len: Option<usize>,
}
impl CrateMetaCheck {
pub fn ok(&self) -> bool {
self.has_readme && self.has_license && self.has_repository && self.has_description
}
}
pub fn crate_metadata_check(repo_root: &Path) -> Result<Vec<CrateMetaCheck>> {
let manifests = walk_cargo_tomls(repo_root)?;
let mut ws: std::collections::BTreeMap<String, String> = Default::default();
for manifest in &manifests {
let Ok(text) = std::fs::read_to_string(manifest) else { continue };
let Ok(doc) = toml::from_str::<toml::Value>(&text) else { continue };
if let Some(wp) = doc
.get("workspace")
.and_then(|w| w.get("package"))
.and_then(|p| p.as_table())
{
for k in ["version", "license", "repository", "description", "readme"] {
if let Some(v) = wp.get(k).and_then(|v| v.as_str()) {
ws.entry(k.to_string()).or_insert_with(|| v.to_string());
}
if k == "readme" {
if let Some(true) = wp.get(k).and_then(|v| v.as_bool()) {
ws.entry(k.to_string()).or_insert_with(|| "README.md".to_string());
}
}
}
}
}
let resolve = |field: Option<&toml::Value>, key: &str| -> Option<String> {
match field {
Some(toml::Value::String(s)) if !s.is_empty() => Some(s.clone()),
Some(toml::Value::Boolean(true)) if key == "readme" => Some("README.md".to_string()),
Some(toml::Value::Table(t)) if t.get("workspace").and_then(|w| w.as_bool()) == Some(true) => {
ws.get(key).cloned()
}
_ => None,
}
};
let mut out = Vec::new();
for manifest in &manifests {
let text = std::fs::read_to_string(manifest)
.with_context(|| format!("read {}", manifest.display()))?;
let doc: toml::Value = toml::from_str(&text)
.with_context(|| format!("parse {}", manifest.display()))?;
let Some(pkg) = doc.get("package").and_then(|p| p.as_table()) else { continue };
if pkg.get("publish").and_then(|p| p.as_bool()) == Some(false) { continue }
let crate_name = pkg.get("name").and_then(|n| n.as_str()).unwrap_or("?").to_string();
let version = resolve(pkg.get("version"), "version").unwrap_or_else(|| "0.0.0".to_string());
let license_expr = resolve(pkg.get("license"), "license");
let repo_url = resolve(pkg.get("repository"), "repository");
let desc = resolve(pkg.get("description"), "description");
let readme_ok = resolve(pkg.get("readme"), "readme").is_some();
out.push(CrateMetaCheck {
manifest: manifest.clone(),
crate_name,
version,
has_readme: readme_ok,
has_license: license_expr.as_ref().map(|s| !s.is_empty()).unwrap_or(false),
license_expr,
has_repository: repo_url.as_ref().map(|s| !s.is_empty()).unwrap_or(false),
repository_url: repo_url,
has_description: desc.as_ref().map(|s| !s.is_empty()).unwrap_or(false),
description_len: desc.as_ref().map(|s| s.len()),
});
}
Ok(out)
}
#[derive(Debug, Clone)]
pub struct LinkDecl {
pub crate_name: String,
pub version: String,
pub links_value: String,
pub manifest: std::path::PathBuf,
}
#[derive(Debug, Clone)]
pub struct LinksConflict {
pub links_value: String,
pub crates: Vec<(String, String)>, }
pub fn links_declarations_scan(repo_root: &Path) -> Result<Vec<LinkDecl>> {
let meta = cargo_metadata::MetadataCommand::new()
.current_dir(repo_root)
.exec()
.with_context(|| format!("cargo metadata in {}", repo_root.display()))?;
let mut out = Vec::new();
for pkg in &meta.packages {
if let Some(links) = &pkg.links {
out.push(LinkDecl {
crate_name: pkg.name.to_string(),
version: pkg.version.to_string(),
links_value: links.clone(),
manifest: pkg.manifest_path.clone().into_std_path_buf(),
});
}
}
Ok(out)
}
pub fn detect_links_conflicts(decls: &[LinkDecl]) -> Vec<LinksConflict> {
use std::collections::BTreeMap;
let mut buckets: BTreeMap<String, Vec<(String, String)>> = BTreeMap::new();
for d in decls {
buckets.entry(d.links_value.clone())
.or_default()
.push((d.crate_name.clone(), d.version.clone()));
}
buckets.into_iter()
.filter(|(_, c)| c.iter().map(|(n, _)| n).collect::<std::collections::HashSet<_>>().len() > 1)
.map(|(links_value, crates)| LinksConflict { links_value, crates })
.collect()
}
pub fn git_tag_matches_published(repo_root: &Path, version: &str) -> Result<bool> {
crate::gitio::tag_points_at_head(repo_root, &format!("v{version}"))
}
pub fn coverage_gate(report: &nornir_testmatrix::coverage::GateReport) -> Result<()> {
if report.is_green() {
return Ok(());
}
let mut parts: Vec<String> = Vec::new();
if !report.gap.missing.is_empty() {
let keys: Vec<&str> = report.gap.missing.iter().map(|n| n.id.as_str()).collect();
parts.push(format!(
"{} uncovered+un-allowlisted surface(s): {}",
report.gap.missing.len(),
keys.join(", ")
));
}
if !report.stale.is_empty() {
let keys: Vec<&str> = report.stale.iter().map(|e| e.key.as_str()).collect();
parts.push(format!("{} stale allowlist entr(ies): {}", report.stale.len(), keys.join(", ")));
}
Err(anyhow!("autonom completeness gate RED — {}", parts.join("; ")))
}
pub fn coverage_gate_all_workspaces(
report: &crate::autonom::megagate::MegaGateReport,
) -> Result<()> {
for wg in &report.workspaces {
if let Err(e) = coverage_gate(&wg.coverage) {
return Err(anyhow!(
"mega completeness gate RED — workspace `{}`: {}",
wg.workspace,
e
));
}
if let Some((green, total)) = wg.reachable {
if green != total {
return Err(anyhow!(
"mega completeness gate RED — workspace `{}`: only {}/{} resolved metro \
lines green (an unlit gating stop on a resolved chain)",
wg.workspace,
green,
total
));
}
}
}
Ok(())
}
pub fn coverage_gate_outcome(
repo: &str,
report: &nornir_testmatrix::coverage::GateReport,
) -> crate::cli_outcome::CommandOutcome {
use crate::cli_outcome::CommandOutcome;
let cmd = "release gate coverage";
let passed = report.is_green() && report.gap.total > 0;
let missing: Vec<&str> = report.gap.missing.iter().map(|n| n.id.as_str()).collect();
let stale: Vec<&str> = report.stale.iter().map(|e| e.key.as_str()).collect();
let data = serde_json::json!({
"repo": repo,
"gate": "coverage",
"passed": passed,
"covered": report.gap.covered,
"total": report.gap.total,
"missing_count": report.gap.missing.len(),
"stale_count": report.stale.len(),
"missing": missing,
"stale": stale,
});
let human = format!("release gate coverage `{repo}`: {}", report.summary());
CommandOutcome { command: cmd.into(), ok: passed, data, human }
}
pub fn gate_all_outcome(
repo: &str,
passed: &[String],
failed: &[(String, String)],
) -> crate::cli_outcome::CommandOutcome {
use crate::cli_outcome::CommandOutcome;
use std::fmt::Write;
let cmd = "release gate-all";
let gate_count = passed.len() + failed.len();
let all_pass = failed.is_empty() && gate_count > 0;
let failed_json: Vec<serde_json::Value> = failed
.iter()
.map(|(n, e)| serde_json::json!({ "name": n, "error": e }))
.collect();
let data = serde_json::json!({
"repo": repo,
"passed": passed,
"failed": failed_json,
"passed_count": passed.len(),
"failed_count": failed.len(),
"gate_count": gate_count,
"all_pass": all_pass,
});
let mut human = format!("=== gate-all: {repo} ===\n");
for n in passed {
let _ = writeln!(human, " ✓ {n}");
}
for (n, e) in failed {
let _ = writeln!(human, " ✗ {n}: {e}");
}
if gate_count == 0 {
let _ = write!(human, "NO gates configured — empty is not green");
} else if all_pass {
let _ = write!(human, "{} gate(s) passed", passed.len());
} else {
let _ = write!(human, "{} gate(s) failed", failed.len());
}
CommandOutcome { command: cmd.into(), ok: all_pass, data, human }
}
pub fn tests_gate(rows: &[crate::warehouse::test_results::TestResultRow]) -> Result<()> {
use crate::warehouse::test_results::{status, summarize_runs};
let summaries = summarize_runs(rows);
let newest = summaries
.first()
.ok_or_else(|| anyhow!("release tests gate RED — no test run recorded; run the matrix first"))?;
let red_cases: Vec<String> = rows
.iter()
.filter(|r| r.run_id == newest.run_id && status::is_red(&r.status))
.map(|r| format!("{}::{}", r.suite, r.test_name))
.collect();
let regressions: Vec<String> = if let Some(last_green) =
summaries.iter().skip(1).find(|s| s.green())
{
use std::collections::BTreeSet;
let was_green: BTreeSet<(&str, &str)> = rows
.iter()
.filter(|r| r.run_id == last_green.run_id && status::is_green(&r.status))
.map(|r| (r.suite.as_str(), r.test_name.as_str()))
.collect();
rows.iter()
.filter(|r| r.run_id == newest.run_id && status::is_red(&r.status))
.filter(|r| was_green.contains(&(r.suite.as_str(), r.test_name.as_str())))
.map(|r| format!("{}::{}", r.suite, r.test_name))
.collect()
} else {
Vec::new()
};
if newest.green() {
return Ok(());
}
let mut msg = format!(
"release tests gate RED — newest run {} has {} failing/stalled case(s)",
crate::warehouse::test_results::short_run(&newest.run_id),
newest.failed + newest.stalled,
);
if !red_cases.is_empty() {
msg.push_str(&format!(": {}", red_cases.join(", ")));
}
if !regressions.is_empty() {
msg.push_str(&format!(
"; REGRESSION (was green last green run): {}",
regressions.join(", ")
));
}
Err(anyhow!(msg))
}
pub fn run_tests_gate(
repo_root: &Path,
history: &[crate::warehouse::test_results::TestResultRow],
aspects: &[crate::test_matrix::Aspect],
) -> Result<Vec<crate::warehouse::test_results::TestResultRow>> {
let fresh = crate::test_matrix::run_full_matrix(repo_root, aspects);
if fresh.is_empty() {
return Err(anyhow!(
"release tests gate RED — the matrix produced no rows for {} (nothing ran)",
repo_root.display()
));
}
let mut all = history.to_vec();
all.extend(fresh.iter().cloned());
tests_gate(&all)?;
Ok(fresh)
}
#[derive(Debug, Default)]
pub struct RenderStageReport {
pub staged: Vec<std::path::PathBuf>,
pub book_built: bool,
}
pub fn render_and_stage_docs(
layout: &crate::docs::RepoLayout,
ctx: &crate::docs::Ctx,
) -> Result<RenderStageReport> {
let mut report = RenderStageReport::default();
let repo_root = layout.repo_root.clone();
let cfg = crate::docs::DocsRenderCfg::load(layout)?;
let mut changed: Vec<std::path::PathBuf> = Vec::new();
if cfg.wants_markdown() {
for r in crate::docs::render_all(layout, ctx)? {
if r.changed {
changed.push(r.output.clone());
}
}
for r in crate::docs::render_sources_in_place(layout, ctx)? {
if r.changed {
changed.push(r.path.clone());
}
}
}
let assets = layout.nornir_dir().join("assets");
if assets.exists() {
changed.push(assets);
}
#[cfg(feature = "docs-export")]
if cfg.wants_pdf() {
let format = crate::docs::DocFormat::parse("pdf")?;
let (bytes, _sources) = crate::docs::build_book(&repo_root, ctx, format)?;
let out = layout.export_path("book", format.extension());
if let Some(parent) = out.parent() {
std::fs::create_dir_all(parent)?;
}
let prev = std::fs::read(&out).ok();
if prev.as_deref() != Some(bytes.as_slice()) {
std::fs::write(&out, &bytes)
.with_context(|| format!("write {}", out.display()))?;
}
changed.push(out);
report.book_built = true;
}
#[cfg(not(feature = "docs-export"))]
let _ = &repo_root;
for path in changed {
if !path.exists() {
continue;
}
let staged = std::process::Command::new("git")
.arg("-C")
.arg(&repo_root)
.arg("add")
.arg("--")
.arg(&path)
.status()
.map(|s| s.success())
.unwrap_or(false);
if staged {
let rel = path.strip_prefix(&repo_root).unwrap_or(&path).to_path_buf();
report.staged.push(rel);
}
}
Ok(report)
}
#[cfg(test)]
mod render_stage_tests {
use super::*;
use tempfile::TempDir;
fn git_init(root: &Path) {
let ok = std::process::Command::new("git")
.arg("-C")
.arg(root)
.arg("init")
.status()
.map(|s| s.success())
.unwrap_or(false);
assert!(ok, "git init failed (is git installed?)");
}
fn staged_files(root: &Path) -> Vec<String> {
let out = std::process::Command::new("git")
.arg("-C")
.arg(root)
.args(["diff", "--cached", "--name-only"])
.output()
.unwrap();
String::from_utf8_lossy(&out.stdout)
.lines()
.map(|s| s.to_string())
.collect()
}
#[test]
fn renders_stages_and_leaves_no_drift() {
let t = TempDir::new().unwrap();
let root = t.path();
git_init(root);
let layout = crate::docs::RepoLayout::new(root);
std::fs::create_dir_all(layout.nornir_dir()).unwrap();
let sentinel = "AUTOTRIGGER-RENDER-SENTINEL-42";
std::fs::write(
layout.source_of("README.md"),
format!("# Title\n\n{sentinel}\n"),
)
.unwrap();
let ctx = crate::docs::Ctx::new(root, root, None);
assert!(
crate::docs::render_check_all(&layout, &ctx).is_err(),
"before render, docs_fresh check must fail on the missing artifact"
);
let report = render_and_stage_docs(&layout, &ctx).unwrap();
let rendered = std::fs::read_to_string(layout.output_of("README.md")).unwrap();
assert!(rendered.contains(sentinel), "rendered README must carry the injected body");
assert!(
rendered.starts_with(crate::docs::layout::GENERATED_HEADER_PREFIX),
"rendered README must carry the GENERATED header"
);
assert!(
report.staged.iter().any(|p| p.ends_with("README.md")),
"README.md should be reported staged; got {:?}",
report.staged
);
assert!(
staged_files(root).iter().any(|f| f == "README.md"),
"README.md must be in the git index"
);
crate::docs::render_check_all(&layout, &ctx)
.expect("after render+stage, docs_fresh check must pass (no drift)");
}
}
#[cfg(test)]
mod cargo_pipeline_tests {
use super::*;
#[test]
fn coverage_gate_passes_green_fails_on_gap_or_stale() {
use nornir_testmatrix::coverage::{Allowlist, AllowEntry, GateReport};
use nornir_testmatrix::discover::{cli_commands, Surface};
use std::collections::BTreeSet;
let mut surface = Surface::new();
surface.extend(cli_commands(["a", "b"])); let all: BTreeSet<String> =
["cli_command:a@na", "cli_command:b@na"].iter().map(|s| s.to_string()).collect();
let green = GateReport::compute("r", "ws", &surface, &all, &Allowlist::new());
assert!(coverage_gate(&green).is_ok());
let covered_one: BTreeSet<String> = ["cli_command:a@na".to_string()].into_iter().collect();
let red = GateReport::compute("r", "ws", &surface, &covered_one, &Allowlist::new());
let err = coverage_gate(&red).unwrap_err().to_string();
assert!(err.contains("uncovered"), "{err}");
assert!(err.contains("b"), "names the missing surface: {err}");
let stale_al = Allowlist {
entries: vec![AllowEntry { key: "cli_command:a@na".into(), reason: "old".into() }],
};
let stale = GateReport::compute("r", "ws", &surface, &all, &stale_al);
let err2 = coverage_gate(&stale).unwrap_err().to_string();
assert!(err2.contains("stale"), "{err2}");
}
#[test]
fn coverage_gate_outcome_ok_when_green_red_with_gap_data_when_failing() {
use nornir_testmatrix::coverage::{Allowlist, AllowEntry, GateReport};
use nornir_testmatrix::discover::{cli_commands, Surface};
use std::collections::BTreeSet;
let mut surface = Surface::new();
surface.extend(cli_commands(["a", "b"]));
let all: BTreeSet<String> =
["cli_command:a@na", "cli_command:b@na"].iter().map(|s| s.to_string()).collect();
let green = GateReport::compute("r", "ws", &surface, &all, &Allowlist::new());
let og = coverage_gate_outcome("nornir", &green);
assert!(og.ok, "a passing gate is ok");
assert!(og.is_sannr(), "a passing gate carries real verdict data");
assert_eq!(og.command, "release gate coverage");
assert_eq!(og.data["passed"], serde_json::json!(true));
assert_eq!(og.data["missing_count"], serde_json::json!(0));
assert_eq!(og.data["total"], serde_json::json!(2));
let covered_one: BTreeSet<String> = ["cli_command:a@na".to_string()].into_iter().collect();
let red = GateReport::compute("r", "ws", &surface, &covered_one, &Allowlist::new());
let orr = coverage_gate_outcome("nornir", &red);
assert!(!orr.ok, "a failing gate is ok:false (the correct red verdict)");
assert!(!orr.is_sannr(), "a red gate is a red surface");
assert_eq!(orr.data["passed"], serde_json::json!(false));
assert_eq!(orr.data["missing_count"], serde_json::json!(1));
assert_eq!(orr.data["missing"], serde_json::json!(["b"]));
assert!(orr.human.contains("RED"));
let stale_al = Allowlist {
entries: vec![AllowEntry { key: "cli_command:a@na".into(), reason: "old".into() }],
};
let stale = GateReport::compute("r", "ws", &surface, &all, &stale_al);
let os = coverage_gate_outcome("nornir", &stale);
assert!(!os.ok);
assert_eq!(os.data["stale_count"], serde_json::json!(1));
assert_eq!(os.data["stale"], serde_json::json!(["cli_command:a@na"]));
}
#[test]
fn gate_all_outcome_ok_when_all_pass_red_with_failure_data() {
let passed = vec!["no_path_patches".to_string(), "nexus_floor".to_string()];
let o = gate_all_outcome("nornir", &passed, &[]);
assert!(o.ok, "no failed gates ⇒ ok");
assert!(o.is_sannr(), "a green sweep carries the passed-gate list");
assert_eq!(o.command, "release gate-all");
assert_eq!(o.data["passed_count"], serde_json::json!(2));
assert_eq!(o.data["failed_count"], serde_json::json!(0));
assert_eq!(o.data["all_pass"], serde_json::json!(true));
assert!(o.human.contains("2 gate(s) passed"));
let failed = vec![("docs_fresh".to_string(), "README drifted".to_string())];
let r = gate_all_outcome("nornir", &["nexus_floor".to_string()], &failed);
assert!(!r.ok, "any failed gate ⇒ ok:false");
assert!(!r.is_sannr(), "a red sweep is a red surface");
assert_eq!(r.data["failed_count"], serde_json::json!(1));
assert_eq!(r.data["failed"][0]["name"], serde_json::json!("docs_fresh"));
assert_eq!(r.data["failed"][0]["error"], serde_json::json!("README drifted"));
assert!(r.human.contains("✗ docs_fresh: README drifted"));
assert!(r.human.contains("1 gate(s) failed"));
let empty = gate_all_outcome("nornir", &[], &[]);
assert!(!empty.ok, "empty gate set must not be green");
assert!(!empty.is_sannr(), "empty is not a true verdict");
assert_eq!(empty.data["gate_count"], serde_json::json!(0));
assert!(empty.human.contains("NO gates configured"));
}
#[test]
fn no_path_patches_flags_any_local_or_git_fork() {
let write = |body: &str| {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), body).unwrap();
(dir, )
};
let (d,) = write("[package]\nname=\"x\"\nversion=\"0.1.0\"\n");
assert!(no_path_patches(d.path()).is_ok());
let (d,) = write("[patch.crates-io]\nznippy = { path = \"../znippy\" }\n");
let e = no_path_patches(d.path()).unwrap_err().to_string();
assert!(e.contains(":2"), "names the offending line: {e}");
let (d,) = write("[patch.crates-io]\niceberg = { git = \"https://x/iceberg\" }\n");
assert!(no_path_patches(d.path()).is_err(), "non-znippy git fork must Err");
let (d,) = write("[patch.crates-io]\n# znippy = { path = \"../znippy\" }\n");
assert!(no_path_patches(d.path()).is_ok(), "comment must not flag");
let (d,) = write("[patch.crates-io]\nfoo = \"1.2\"\nbar = { version = \"1.2\" }\n");
assert!(no_path_patches(d.path()).is_ok(), "version pins are publishable");
let (d,) = write("[patch.\"crates-io\"]\nholger = { path = \"../holger\" }\n");
assert!(no_path_patches(d.path()).is_err());
let (d,) = write("[patch.crates-io.skade]\npath = \"../skade\"\n");
assert!(no_path_patches(d.path()).is_err());
let (d,) = write("[patch.crates-io]\npathfinder = \"0.1\"\n");
assert!(no_path_patches(d.path()).is_ok(), "pathfinder must not match `path`");
}
#[test]
fn crate_metadata_check_resolves_workspace_inheritance() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), r#"
[workspace]
members = ["member"]
[workspace.package]
version = "0.3.0"
license = "MIT OR Apache-2.0"
repository = "https://example.com/repo"
description = "shared description"
readme = true
"#).unwrap();
let member = dir.path().join("member");
std::fs::create_dir_all(&member).unwrap();
std::fs::write(member.join("Cargo.toml"), r#"
[package]
name = "member"
version.workspace = true
license.workspace = true
repository.workspace = true
description.workspace = true
readme.workspace = true
edition = "2021"
"#).unwrap();
let checks = crate_metadata_check(dir.path()).unwrap();
assert_eq!(checks.len(), 1, "{checks:?}");
let c = &checks[0];
assert_eq!(c.crate_name, "member");
assert!(c.has_license, "license.workspace=true resolves");
assert!(c.has_repository, "repository.workspace=true resolves");
assert!(c.has_description, "description.workspace=true resolves");
assert!(c.has_readme, "readme.workspace=true → workspace readme=true (bool)");
assert_eq!(c.version, "0.3.0", "version inherited from [workspace.package]");
assert!(c.ok(), "all four fields resolved ⇒ gate passes");
}
#[test]
fn path_dep_audit_ignores_dev_deps_and_unpublished_members() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), r#"
[package]
name = "x"
version = "0.1.0"
edition = "2021"
[dev-dependencies]
harness = { path = "../harness" }
[build-dependencies]
gen = { path = "../gen" }
"#).unwrap();
let findings = path_dep_audit(dir.path()).unwrap();
assert_eq!(findings.len(), 1, "{findings:?}");
assert_eq!(findings[0].dep_name, "gen");
let dir2 = tempfile::tempdir().unwrap();
std::fs::write(dir2.path().join("Cargo.toml"), r#"
[package]
name = "y"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
sibling = { path = "../sibling" }
"#).unwrap();
assert!(path_dep_audit(dir2.path()).unwrap().is_empty(), "publish=false skipped");
}
#[test]
fn tests_gate_passes_green_fails_on_red_and_names_regression() {
use crate::warehouse::test_results::{status, TestResultRow};
let err = tests_gate(&[]).unwrap_err().to_string();
assert!(err.contains("no test run"), "{err}");
let green = vec![
TestResultRow::unit("g1", "r", "r", "a::ok", status::PASS, 1.0, 100, ""),
TestResultRow::unit("g1", "r", "r", "a::also", status::PASS, 1.0, 100, ""),
];
tests_gate(&green).unwrap();
let red = vec![
TestResultRow::unit("g1", "r", "r", "a::ok", status::PASS, 1.0, 100, ""),
TestResultRow::unit("r2", "r", "r", "a::ok", status::PASS, 1.0, 200, ""),
TestResultRow::unit("r2", "r", "r", "a::broke", status::FAIL, 1.0, 200, "boom"),
];
let err = tests_gate(&red).unwrap_err().to_string();
assert!(err.contains("RED"), "{err}");
assert!(err.contains("a::broke"), "names the failing case: {err}");
let regressed = vec![
TestResultRow::unit("g1", "r", "r", "a::ok", status::PASS, 1.0, 100, ""),
TestResultRow::unit("r3", "r", "r", "a::ok", status::FAIL, 1.0, 300, "now broken"),
];
let err = tests_gate(®ressed).unwrap_err().to_string();
assert!(err.contains("REGRESSION"), "flags the regression: {err}");
assert!(err.contains("a::ok"), "names the regressed test: {err}");
let stalled = vec![
TestResultRow::unit("s1", "r", "r", "a::hang", status::STALLED, 9e4, 400, "no output 120s"),
];
assert!(tests_gate(&stalled).is_err(), "a stalled run is red");
}
#[test]
fn path_dep_audit_flags_missing_version() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), r#"
[package]
name = "x"
version = "0.1.0"
edition = "2021"
[dependencies]
sibling-a = { path = "../a" }
sibling-b = { path = "../b", version = "0.2" }
"#).unwrap();
let findings = path_dep_audit(dir.path()).unwrap();
assert_eq!(findings.len(), 2);
let bad = findings.iter().find(|f| f.dep_name == "sibling-a").unwrap();
assert!(!bad.ok());
let good = findings.iter().find(|f| f.dep_name == "sibling-b").unwrap();
assert!(good.ok());
assert_eq!(good.version_req.as_deref(), Some("0.2"));
}
#[test]
fn crate_metadata_check_flags_missing_fields() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), r#"
[package]
name = "y"
version = "0.1.0"
edition = "2021"
license = "MIT"
"#).unwrap();
let checks = crate_metadata_check(dir.path()).unwrap();
assert_eq!(checks.len(), 1);
let c = &checks[0];
assert!(c.has_license);
assert!(!c.has_repository);
assert!(!c.has_description);
assert!(!c.ok());
}
}
#[cfg(test)]
mod regression_tests {
use super::*;
use crate::bench::{BenchResult, BenchRun};
fn run(machine: &str, ts: &str, mbs: f64) -> BenchRun {
let mut r = BenchResult { name: "codec".into(), metrics: Default::default() };
r.metrics.insert("throughput_mbs".into(), serde_json::Value::from(mbs));
BenchRun {
date: ts[..10].to_string(),
timestamp: Some(ts.to_string()),
version: "0.1.0".into(),
machine: machine.into(),
cores: 1,
results: vec![r],
tests: Vec::new(),
}
}
#[test]
fn no_baseline_for_machine_passes() {
let candidate = run("ci", "2026-01-02T00:00:00+00:00", 100.0);
let history = vec![run("other-box", "2026-01-01T00:00:00+00:00", 999.0)];
assert!(no_regression_against(&candidate, &history, 10.0).is_ok());
}
#[test]
fn improvement_passes() {
let candidate = run("ci", "2026-01-02T00:00:00+00:00", 120.0);
let history = vec![run("ci", "2026-01-01T00:00:00+00:00", 100.0)];
assert!(no_regression_against(&candidate, &history, 10.0).is_ok());
}
#[test]
fn drop_within_threshold_passes() {
let candidate = run("ci", "2026-01-02T00:00:00+00:00", 95.0);
let history = vec![run("ci", "2026-01-01T00:00:00+00:00", 100.0)];
assert!(no_regression_against(&candidate, &history, 10.0).is_ok());
}
#[test]
fn drop_beyond_threshold_rejected() {
let candidate = run("ci", "2026-01-02T00:00:00+00:00", 80.0);
let history = vec![run("ci", "2026-01-01T00:00:00+00:00", 100.0)];
let err = no_regression_against(&candidate, &history, 10.0).unwrap_err();
assert!(err.to_string().contains("regression"), "got: {err}");
}
#[test]
fn baseline_is_newest_by_timestamp_not_slice_order() {
let candidate = run("ci", "2026-03-01T00:00:00+00:00", 90.0);
let history = vec![
run("ci", "2026-02-01T00:00:00+00:00", 100.0), run("ci", "2026-01-01T00:00:00+00:00", 1000.0), ];
assert!(no_regression_against(&candidate, &history, 10.0).is_ok());
}
#[test]
fn baseline_picks_newest_even_when_older_run_is_last_in_slice_and_regressed() {
let candidate = run("ci", "2026-03-01T00:00:00+00:00", 50.0);
let history = vec![
run("ci", "2026-02-01T00:00:00+00:00", 100.0),
run("ci", "2026-01-01T00:00:00+00:00", 40.0),
];
assert!(no_regression_against(&candidate, &history, 10.0).is_err());
}
fn hist(machine: &str, day: u32, mbs: f64) -> BenchRun {
run(machine, &format!("2026-02-{day:02}T00:00:00+00:00"), mbs)
}
#[test]
fn metric_stats_mean_and_sample_stddev() {
let s = MetricStats::from_samples(&[100.0, 90.0, 110.0, 95.0, 105.0]);
assert_eq!(s.n, 5);
assert!((s.mean - 100.0).abs() < 1e-9);
assert!((s.stddev - 7.9057).abs() < 1e-3, "got {}", s.stddev);
assert_eq!(MetricStats::from_samples(&[42.0]).stddev, 0.0);
}
#[test]
fn windowed_within_noise_does_not_flip_gate() {
let candidate = run("ci", "2026-03-01T00:00:00+00:00", 85.0);
let history = vec![
hist("ci", 1, 100.0),
hist("ci", 2, 90.0),
hist("ci", 3, 110.0),
hist("ci", 4, 95.0),
hist("ci", 5, 105.0),
];
assert!(no_regression_against(&candidate, &history, 10.0).is_err());
assert!(
no_regression_windowed(&candidate, &history, 10.0, DEFAULT_REGRESSION_WINDOW, DEFAULT_REGRESSION_SIGMA).is_ok(),
"a 15% dip inside the noise band must not flip the gate"
);
}
#[test]
fn windowed_significant_drop_is_flagged() {
let candidate = run("ci", "2026-03-01T00:00:00+00:00", 85.0);
let history = vec![
hist("ci", 1, 100.0),
hist("ci", 2, 101.0),
hist("ci", 3, 99.0),
hist("ci", 4, 100.0),
hist("ci", 5, 100.0),
];
let err = no_regression_windowed(&candidate, &history, 10.0, DEFAULT_REGRESSION_WINDOW, DEFAULT_REGRESSION_SIGMA)
.unwrap_err()
.to_string();
assert!(err.contains("regression"), "got: {err}");
assert!(err.contains("σ"), "names the sigma band: {err}");
}
#[test]
fn windowed_small_drop_within_tolerance_passes() {
let candidate = run("ci", "2026-03-01T00:00:00+00:00", 96.0);
let history = vec![hist("ci", 1, 100.0), hist("ci", 2, 100.0), hist("ci", 3, 100.0)];
assert!(no_regression_windowed(&candidate, &history, 10.0, DEFAULT_REGRESSION_WINDOW, DEFAULT_REGRESSION_SIGMA).is_ok());
}
#[test]
fn windowed_no_baseline_for_machine_passes() {
let candidate = run("ci", "2026-03-01T00:00:00+00:00", 1.0);
let history = vec![hist("other-box", 1, 100.0)];
assert!(no_regression_windowed(&candidate, &history, 10.0, DEFAULT_REGRESSION_WINDOW, DEFAULT_REGRESSION_SIGMA).is_ok());
}
#[test]
fn windowed_single_sample_falls_back_to_percent() {
let history = vec![hist("ci", 1, 100.0)];
let big = run("ci", "2026-03-01T00:00:00+00:00", 80.0);
assert!(no_regression_windowed(&big, &history, 10.0, DEFAULT_REGRESSION_WINDOW, DEFAULT_REGRESSION_SIGMA).is_err());
let small = run("ci", "2026-03-01T00:00:00+00:00", 95.0);
assert!(no_regression_windowed(&small, &history, 10.0, DEFAULT_REGRESSION_WINDOW, DEFAULT_REGRESSION_SIGMA).is_ok());
}
#[test]
fn windowed_window_caps_the_baseline_sample_count() {
let history: Vec<BenchRun> = {
let mut v: Vec<BenchRun> = (1..=10)
.map(|d| run("ci", &format!("2026-02-{d:02}T00:00:00+00:00"), 100.0))
.collect();
v.push(run("ci", "2026-01-01T00:00:00+00:00", 1.0));
v.push(run("ci", "2026-01-02T00:00:00+00:00", 1.0));
v
};
let baseline = baseline_window("ci", &history, 10);
assert_eq!(baseline.len(), 10, "window caps the sample count");
assert!(
baseline.iter().all(|b| b.find("codec").unwrap().metrics["throughput_mbs"].as_f64().unwrap() > 50.0),
"the two oldest slow runs are excluded by the window"
);
}
}
#[cfg(test)]
mod nexus_floor_tests {
use super::*;
use crate::bench::{BenchResult, BenchRun};
fn result(name: &str, kv: &[(&str, f64)]) -> BenchResult {
let mut r = BenchResult { name: name.into(), metrics: Default::default() };
for (k, v) in kv {
r.metrics.insert((*k).to_string(), serde_json::Value::from(*v));
}
r
}
fn run_with(results: Vec<BenchResult>) -> BenchRun {
BenchRun {
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(),
}
}
#[test]
fn holger_above_nexus_passes() {
let run = run_with(vec![result(
"decode",
&[("holger_ops_sec", 5000.0), ("nexus_ops_sec", 1000.0)],
)]);
assert!(nexus_floor(&run).is_ok());
}
#[test]
fn holger_equal_to_nexus_passes() {
let run = run_with(vec![result(
"decode",
&[("holger_ops_sec", 1000.0), ("nexus_ops_sec", 1000.0)],
)]);
assert!(nexus_floor(&run).is_ok());
}
#[test]
fn holger_below_nexus_rejected() {
let run = run_with(vec![result(
"decode",
&[("holger_ops_sec", 900.0), ("nexus_ops_sec", 1000.0)],
)]);
let err = nexus_floor(&run).unwrap_err().to_string();
assert!(err.contains("nexus floor"), "got: {err}");
assert!(err.contains("decode"), "error should name the result: {err}");
}
#[test]
fn one_result_below_floor_fails_the_whole_run() {
let run = run_with(vec![
result("warm", &[("holger_ops_sec", 5000.0), ("nexus_ops_sec", 1000.0)]),
result("cold", &[("holger_ops_sec", 50.0), ("nexus_ops_sec", 1000.0)]),
]);
let err = nexus_floor(&run).unwrap_err().to_string();
assert!(err.contains("cold"), "should blame the breaching result: {err}");
}
#[test]
fn results_missing_either_key_are_skipped() {
let run = run_with(vec![
result("compress", &[("compress_mbs", 800.0)]),
result("only_holger", &[("holger_ops_sec", 10.0)]),
result("only_nexus", &[("nexus_ops_sec", 9999.0)]),
]);
assert!(nexus_floor(&run).is_ok());
}
}
#[cfg(test)]
mod roundtrip_tests {
use super::*;
use std::cell::RefCell;
#[test]
fn all_kinds_succeed_passes() {
let seen = RefCell::new(Vec::new());
let res = integration_roundtrip(&["blob", "symbol"], |k| {
seen.borrow_mut().push(k.to_string());
Ok(())
});
assert!(res.is_ok());
assert_eq!(seen.into_inner(), vec!["blob", "symbol"]);
}
#[test]
fn empty_kind_list_passes_vacuously() {
let res = integration_roundtrip(&[], |_| -> Result<()> {
panic!("closure must not run for an empty kind list")
});
assert!(res.is_ok());
}
#[test]
fn first_failure_aborts_and_is_contextualised() {
let seen = RefCell::new(Vec::new());
let res = integration_roundtrip(&["blob", "symbol", "edge"], |k| {
seen.borrow_mut().push(k.to_string());
if k == "symbol" {
anyhow::bail!("store rejected the artifact")
}
Ok(())
});
let err = res.unwrap_err();
let chain = format!("{err:#}");
assert!(chain.contains("roundtrip failed for symbol"), "got: {chain}");
assert!(chain.contains("store rejected the artifact"), "got: {chain}");
assert_eq!(seen.into_inner(), vec!["blob", "symbol"]);
}
}