mod common;
use common::perfgate_cmd;
use predicates::prelude::*;
use serde_json::{Value, json};
use std::fs;
use std::path::Path;
use tempfile::tempdir;
#[test]
fn test_probe_compare_writes_probe_compare_receipt() {
let temp_dir = tempdir().expect("failed to create temp dir");
let baseline_path = temp_dir.path().join("baseline-probes.json");
let current_path = temp_dir.path().join("current-probes.json");
let output_path = temp_dir.path().join("probe-compare.json");
write_probe_receipt(
&baseline_path,
"baseline-run",
&[
("parser.tokenize", "local", 12.0),
("parser.ast_build", "dominant", 40.0),
],
);
write_probe_receipt(
¤t_path,
"current-run",
&[
("parser.tokenize", "local", 13.0),
("parser.ast_build", "dominant", 36.0),
],
);
perfgate_cmd()
.arg("probe")
.arg("compare")
.arg("--baseline")
.arg(&baseline_path)
.arg("--current")
.arg(¤t_path)
.arg("--out")
.arg(&output_path)
.assert()
.success()
.stderr(predicate::str::contains("Probe compare receipt written"));
let receipt: Value = serde_json::from_str(
&fs::read_to_string(&output_path).expect("failed to read probe compare receipt"),
)
.expect("probe compare receipt should be JSON");
let typed: perfgate_types::ProbeCompareReceipt =
serde_json::from_value(receipt.clone()).expect("probe compare receipt should deserialize");
assert_eq!(typed.schema, "perfgate.probe_compare.v1");
assert_eq!(typed.scenario.as_deref(), Some("large_file_parse"));
assert_eq!(typed.probes.len(), 2);
assert_eq!(typed.verdict.status, perfgate_types::VerdictStatus::Warn);
assert_eq!(receipt["probes"][0]["deltas"]["wall_ms"]["baseline"], 40.0);
assert_eq!(receipt["probes"][0]["deltas"]["wall_ms"]["current"], 36.0);
assert_eq!(receipt["probes"][0]["status"], "pass");
assert_eq!(receipt["probes"][1]["name"], "parser.tokenize");
assert_eq!(receipt["probes"][1]["status"], "warn");
}
#[test]
fn test_probe_compare_warns_on_missing_probe() {
let temp_dir = tempdir().expect("failed to create temp dir");
let baseline_path = temp_dir.path().join("baseline-probes.json");
let current_path = temp_dir.path().join("current-probes.json");
let output_path = temp_dir.path().join("probe-compare.json");
write_probe_receipt(
&baseline_path,
"baseline-run",
&[("parser.tokenize", "local", 12.0)],
);
write_probe_receipt(
¤t_path,
"current-run",
&[("parser.ast_build", "dominant", 36.0)],
);
perfgate_cmd()
.arg("probe")
.arg("compare")
.arg("--baseline")
.arg(&baseline_path)
.arg("--current")
.arg(¤t_path)
.arg("--out")
.arg(&output_path)
.assert()
.success();
let typed: perfgate_types::ProbeCompareReceipt = serde_json::from_str(
&fs::read_to_string(&output_path).expect("failed to read probe compare receipt"),
)
.expect("probe compare receipt should deserialize");
assert_eq!(typed.verdict.status, perfgate_types::VerdictStatus::Warn);
assert!(
typed
.warnings
.iter()
.any(|warning| warning.contains("missing from current"))
);
}
fn write_probe_receipt(path: &Path, run_id: &str, probes: &[(&str, &str, f64)]) {
let observations: Vec<_> = probes
.iter()
.map(|(name, scope, wall_ms)| {
json!({
"name": name,
"parent": "parser.total",
"scope": scope,
"metrics": {
"wall_ms": {
"value": wall_ms,
"unit": "ms"
}
}
})
})
.collect();
let receipt = json!({
"schema": "perfgate.probe.v1",
"tool": {"name": "perfgate-ingest", "version": "0.16.0"},
"run": {
"id": run_id,
"started_at": "2026-05-08T00:00:00Z",
"ended_at": "2026-05-08T00:00:01Z",
"host": {"os": "linux", "arch": "x86_64"}
},
"bench": {
"name": "parser",
"command": ["cargo", "bench"],
"repeat": probes.len(),
"warmup": 0
},
"scenario": "large_file_parse",
"probes": observations
});
fs::write(
path,
serde_json::to_string_pretty(&receipt).expect("serialize probe receipt"),
)
.expect("failed to write probe receipt");
}