use predicates::prelude::*;
use std::fs;
use tempfile::tempdir;
mod common;
use common::{fixtures_dir, perfgate_cmd};
#[test]
fn test_compare_pass_scenario() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
cmd.assert().success();
assert!(output_path.exists(), "output file should exist");
let content = fs::read_to_string(&output_path).expect("failed to read output file");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert_eq!(
receipt["schema"].as_str(),
Some("perfgate.compare.v1"),
"schema should be 'perfgate.compare.v1'"
);
assert_eq!(
receipt["verdict"]["status"].as_str(),
Some("pass"),
"verdict should be pass for improved performance"
);
}
#[test]
fn test_compare_warn_scenario_without_fail_on_warn() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_warn.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
cmd.assert().success();
assert!(output_path.exists(), "output file should exist");
let content = fs::read_to_string(&output_path).expect("failed to read output file");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert_eq!(
receipt["verdict"]["status"].as_str(),
Some("warn"),
"verdict should be warn for slight regression"
);
}
#[test]
fn test_compare_warn_scenario_with_fail_on_warn() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_warn.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--fail-on-warn")
.arg("--out")
.arg(&output_path);
cmd.assert().code(3);
assert!(output_path.exists(), "output file should exist");
}
#[test]
fn test_compare_fail_scenario() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_fail.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
cmd.assert().code(2);
assert!(output_path.exists(), "output file should exist");
let content = fs::read_to_string(&output_path).expect("failed to read output file");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert_eq!(
receipt["verdict"]["status"].as_str(),
Some("fail"),
"verdict should be fail for significant regression"
);
}
#[test]
fn test_compare_missing_baseline_file() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = temp_dir.path().join("nonexistent.json");
let current = fixtures_dir().join("current_pass.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
cmd.assert()
.failure()
.stderr(predicate::str::contains("read"));
}
#[test]
fn test_compare_missing_current_file() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = temp_dir.path().join("nonexistent.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
cmd.assert()
.failure()
.stderr(predicate::str::contains("read"));
}
#[test]
fn test_compare_receipt_contains_required_fields() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
cmd.assert().success();
let content = fs::read_to_string(&output_path).expect("failed to read output file");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert!(receipt["schema"].is_string(), "schema should exist");
assert!(receipt["tool"].is_object(), "tool should exist");
assert!(receipt["bench"].is_object(), "bench should exist");
assert!(
receipt["baseline_ref"].is_object(),
"baseline_ref should exist"
);
assert!(
receipt["current_ref"].is_object(),
"current_ref should exist"
);
assert!(receipt["budgets"].is_object(), "budgets should exist");
assert!(receipt["deltas"].is_object(), "deltas should exist");
assert!(receipt["verdict"].is_object(), "verdict should exist");
assert!(
receipt["verdict"]["status"].is_string(),
"verdict.status should exist"
);
assert!(
receipt["verdict"]["counts"].is_object(),
"verdict.counts should exist"
);
}
#[test]
fn test_compare_with_custom_threshold() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_fail.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--threshold")
.arg("0.60")
.arg("--out")
.arg(&output_path);
cmd.assert().success();
let content = fs::read_to_string(&output_path).expect("failed to read output file");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert_eq!(
receipt["verdict"]["status"].as_str(),
Some("pass"),
"verdict should be pass with higher threshold"
);
}
#[test]
fn test_compare_pretty_flag() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_pass.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--pretty")
.arg("--out")
.arg(&output_path);
cmd.assert().success();
let content = fs::read_to_string(&output_path).expect("failed to read output file");
assert!(
content.contains('\n'),
"pretty-printed JSON should contain newlines"
);
assert!(
content.contains(" "),
"pretty-printed JSON should have indentation"
);
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert_eq!(receipt["schema"].as_str(), Some("perfgate.compare.v1"));
}
#[test]
fn test_compare_mismatched_metrics() {
let temp_dir = tempdir().expect("failed to create temp dir");
let output_path = temp_dir.path().join("compare.json");
let baseline = fixtures_dir().join("baseline.json");
let current = fixtures_dir().join("current_extra_metric.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--out")
.arg(&output_path);
let output = cmd.output().expect("failed to execute perfgate compare");
assert!(
output.status.code().is_some(),
"process should exit cleanly, not crash"
);
assert!(output_path.exists(), "output file should exist");
let content = fs::read_to_string(&output_path).expect("failed to read output file");
let receipt: serde_json::Value =
serde_json::from_str(&content).expect("output should be valid JSON");
assert_eq!(receipt["schema"].as_str(), Some("perfgate.compare.v1"));
assert!(
receipt["deltas"]["wall_ms"].is_object(),
"deltas should contain the common metric wall_ms"
);
}