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('[') {
in_patch = l.starts_with("[patch.crates-io")
|| l.starts_with("[patch.\"crates-io\"");
continue;
}
if in_patch && l.contains("znippy") && !l.starts_with('#') {
return Err(anyhow!(
"[patch.crates-io] znippy entry at {}:{} — strip before release",
cargo.display(),
i + 1
));
}
}
Ok(())
}
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)?;
let Some(last) = history::last_for_machine(&history, &run.machine) 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(())
}
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(())
})
}