use predicates::prelude::*;
use std::fs;
use std::path::PathBuf;
use tempfile::tempdir;
mod common;
use common::{fixtures_dir, generate_compare_receipt, perfgate_cmd};
fn tradeoff_fixture_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("fixtures")
.join("schema")
.join("v0.16")
.join("perfgate.tradeoff.v1.json")
}
#[test]
fn test_md_pass_verdict_stdout() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
assert!(
compare_receipt_path.exists(),
"compare receipt should exist"
);
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--compare").arg(&compare_receipt_path);
cmd.assert()
.success()
.stdout(predicate::str::contains("✅"))
.stdout(predicate::str::contains("test-benchmark"))
.stdout(predicate::str::contains("| metric |"))
.stdout(predicate::str::contains("baseline"))
.stdout(predicate::str::contains("current"))
.stdout(predicate::str::contains("delta"))
.stdout(predicate::str::contains("budget"))
.stdout(predicate::str::contains("status"))
.stdout(predicate::str::contains("wall_ms"));
}
#[test]
fn test_md_warn_verdict_stdout() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_warn.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
assert!(
compare_receipt_path.exists(),
"compare receipt should exist"
);
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--compare").arg(&compare_receipt_path);
cmd.assert()
.success()
.stdout(predicate::str::contains("⚠️"))
.stdout(predicate::str::contains("test-benchmark"))
.stdout(predicate::str::contains("| metric |"));
}
#[test]
fn test_md_fail_verdict_stdout() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_fail.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
assert!(
compare_receipt_path.exists(),
"compare receipt should exist"
);
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--compare").arg(&compare_receipt_path);
cmd.assert()
.success()
.stdout(predicate::str::contains("❌"))
.stdout(predicate::str::contains("test-benchmark"))
.stdout(predicate::str::contains("| metric |"));
}
#[test]
fn test_md_output_to_file() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let md_output_path = temp_dir.path().join("output.md");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
assert!(
compare_receipt_path.exists(),
"compare receipt should exist"
);
let mut cmd = perfgate_cmd();
cmd.arg("md")
.arg("--compare")
.arg(&compare_receipt_path)
.arg("--out")
.arg(&md_output_path);
cmd.assert().success();
assert!(md_output_path.exists(), "markdown output file should exist");
let content = fs::read_to_string(&md_output_path).expect("failed to read markdown file");
assert!(content.contains("✅"), "markdown should contain pass emoji");
assert!(
content.contains("test-benchmark"),
"markdown should contain benchmark name"
);
assert!(
content.contains("| metric |"),
"markdown should contain table header"
);
assert!(
content.contains("baseline"),
"markdown should contain baseline column"
);
assert!(
content.contains("current"),
"markdown should contain current column"
);
assert!(
content.contains("delta"),
"markdown should contain delta column"
);
assert!(
content.contains("budget"),
"markdown should contain budget column"
);
assert!(
content.contains("status"),
"markdown should contain status column"
);
assert!(
content.contains("wall_ms"),
"markdown should contain wall_ms metric"
);
}
#[test]
fn test_md_missing_compare_file() {
let temp_dir = tempdir().expect("failed to create temp dir");
let nonexistent_path = temp_dir.path().join("nonexistent.json");
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--compare").arg(&nonexistent_path);
cmd.assert()
.failure()
.stderr(predicate::str::contains("read"));
}
#[test]
fn test_md_missing_compare_argument() {
let mut cmd = perfgate_cmd();
cmd.arg("md");
cmd.assert().failure().stderr(predicate::str::contains(
"Either --compare or --tradeoff is required",
));
}
#[test]
fn test_md_tradeoff_receipt_stdout() {
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--tradeoff").arg(tradeoff_fixture_path());
cmd.assert()
.success()
.stdout(predicate::str::contains("perfgate tradeoff"))
.stdout(predicate::str::contains("large_file_parse"))
.stdout(predicate::str::contains("accepted"))
.stdout(predicate::str::contains(
"tokenizer-slower-if-parser-faster",
))
.stdout(predicate::str::contains("Summary"))
.stdout(predicate::str::contains("Weighted Workload"))
.stdout(predicate::str::contains("Probe Evidence"))
.stdout(predicate::str::contains("Accepted / Rejected Tradeoffs"))
.stdout(predicate::str::contains("Local Reproduction"));
}
#[test]
fn test_md_contains_verdict_reasons() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_fail.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
assert!(
compare_receipt_path.exists(),
"compare receipt should exist"
);
let content =
fs::read_to_string(&compare_receipt_path).expect("failed to read compare receipt");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("compare receipt should be valid JSON");
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--compare").arg(&compare_receipt_path);
let output = cmd.assert().success();
if receipt["verdict"]["reasons"]
.as_array()
.is_some_and(|reasons| !reasons.is_empty())
{
output.stdout(predicate::str::contains("Notes:"));
}
}
#[test]
fn test_md_template_renders_custom_output() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let template_path = temp_dir.path().join("comment.hbs");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
fs::write(
&template_path,
r#"{{header}}
bench={{bench.name}}
{{#each rows}}
- {{metric}} {{status}} {{delta_pct}}
{{/each}}
"#,
)
.expect("write template");
let mut cmd = perfgate_cmd();
cmd.arg("md")
.arg("--compare")
.arg(&compare_receipt_path)
.arg("--template")
.arg(&template_path);
cmd.assert()
.success()
.stdout(predicate::str::contains("bench=test-benchmark"))
.stdout(predicate::str::contains("- wall_ms"));
}
#[test]
fn test_md_contains_all_metrics() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
let mut cmd = perfgate_cmd();
cmd.arg("md").arg("--compare").arg(&compare_receipt_path);
cmd.assert()
.success()
.stdout(predicate::str::contains("wall_ms"))
.stdout(predicate::str::contains("max_rss_kb"));
}
#[test]
fn test_md_stdout_deterministic() {
let temp_dir = tempdir().expect("failed to create temp dir");
let compare_receipt_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_warn.json");
generate_compare_receipt(&baseline, ¤t, &compare_receipt_path)
.expect("failed to generate compare receipt");
let output1 = perfgate_cmd()
.arg("md")
.arg("--compare")
.arg(&compare_receipt_path)
.output()
.expect("first md run");
assert!(output1.status.success(), "first md run should succeed");
let output2 = perfgate_cmd()
.arg("md")
.arg("--compare")
.arg(&compare_receipt_path)
.output()
.expect("second md run");
assert!(output2.status.success(), "second md run should succeed");
assert_eq!(
output1.stdout, output2.stdout,
"markdown stdout should be byte-for-byte deterministic"
);
}