mod common;
use common::perfgate_cmd;
use predicates::prelude::*;
use std::fs;
use tempfile::tempdir;
use common::fixtures_dir;
#[test]
fn test_compare_host_mismatch_warn_policy_different_hostname() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_hostname.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("warn")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("warning: host mismatch"))
.stderr(predicate::str::contains("hostname mismatch"));
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'"
);
}
#[test]
fn test_compare_host_mismatch_warn_policy_different_os() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_os.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("warn")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("warning: host mismatch"))
.stderr(predicate::str::contains("OS mismatch"));
}
#[test]
fn test_compare_host_mismatch_warn_policy_different_arch() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_arch.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("warn")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("warning: host mismatch"))
.stderr(predicate::str::contains("architecture mismatch"));
}
#[test]
fn test_compare_host_mismatch_warn_policy_different_cpu_count() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_cpu_count.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("warn")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("warning: host mismatch"))
.stderr(predicate::str::contains("CPU count differs"));
}
#[test]
fn test_compare_host_mismatch_error_policy_fails_on_mismatch() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_hostname.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("error")
.arg("--out")
.arg(&output_path);
cmd.assert()
.code(1)
.stderr(predicate::str::contains("host mismatch detected"))
.stderr(predicate::str::contains("hostname mismatch"));
}
#[test]
fn test_compare_host_mismatch_error_policy_os_mismatch() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_os.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("error")
.arg("--out")
.arg(&output_path);
cmd.assert()
.code(1)
.stderr(predicate::str::contains("host mismatch detected"))
.stderr(predicate::str::contains("OS mismatch"));
}
#[test]
fn test_compare_host_mismatch_error_policy_arch_mismatch() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_arch.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("error")
.arg("--out")
.arg(&output_path);
cmd.assert()
.code(1)
.stderr(predicate::str::contains("host mismatch detected"))
.stderr(predicate::str::contains("architecture mismatch"));
}
#[test]
fn test_compare_host_mismatch_error_policy_cpu_count_mismatch() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_cpu_count.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("error")
.arg("--out")
.arg(&output_path);
cmd.assert()
.code(1)
.stderr(predicate::str::contains("host mismatch detected"))
.stderr(predicate::str::contains("CPU count differs"));
}
#[test]
fn test_compare_host_mismatch_ignore_policy_no_warnings() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_hostname.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("ignore")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("host mismatch").not());
assert!(output_path.exists(), "output file should exist");
}
#[test]
fn test_compare_host_mismatch_ignore_policy_multiple_mismatches() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_multiple_mismatches.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("ignore")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("host mismatch").not())
.stderr(predicate::str::contains("OS mismatch").not())
.stderr(predicate::str::contains("architecture mismatch").not())
.stderr(predicate::str::contains("CPU count differs").not())
.stderr(predicate::str::contains("hostname mismatch").not());
}
#[test]
fn test_compare_host_mismatch_warn_policy_multiple_mismatches() {
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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_multiple_mismatches.json");
let mut cmd = perfgate_cmd();
cmd.arg("compare")
.arg("--baseline")
.arg(&baseline)
.arg("--current")
.arg(¤t)
.arg("--host-mismatch")
.arg("warn")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("OS mismatch"))
.stderr(predicate::str::contains("architecture mismatch"))
.stderr(predicate::str::contains("CPU count differs"))
.stderr(predicate::str::contains("hostname mismatch"));
}
#[test]
fn test_compare_host_mismatch_no_warning_when_hosts_match() {
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("--host-mismatch")
.arg("warn")
.arg("--out")
.arg(&output_path);
cmd.assert()
.success()
.stderr(predicate::str::contains("host mismatch").not());
}
#[test]
fn test_compare_host_mismatch_default_policy_is_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_host_linux_x86.json");
let current = fixtures_dir().join("current_host_different_hostname.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()
.stderr(predicate::str::contains("warning: host mismatch"));
}
#[cfg(unix)]
fn success_command() -> Vec<&'static str> {
vec!["true"]
}
#[cfg(windows)]
fn success_command() -> Vec<&'static str> {
vec!["cmd", "/c", "exit", "0"]
}
fn create_config_file(temp_dir: &std::path::Path, bench_name: &str) -> std::path::PathBuf {
let config_path = temp_dir.join("perfgate.toml");
let success_cmd = success_command();
let cmd_str = success_cmd
.iter()
.map(|s| format!("\"{}\"", s))
.collect::<Vec<_>>()
.join(", ");
let config_content = format!(
r#"
[defaults]
repeat = 2
warmup = 0
threshold = 1000.0
[[bench]]
name = "{}"
command = [{}]
"#,
bench_name, cmd_str
);
fs::write(&config_path, config_content).expect("Failed to write config file");
config_path
}
fn create_baseline_receipt_with_host(
temp_dir: &std::path::Path,
bench_name: &str,
os: &str,
arch: &str,
cpu_count: u32,
hostname_hash: &str,
) -> std::path::PathBuf {
let baselines_dir = temp_dir.join("baselines");
fs::create_dir_all(&baselines_dir).expect("Failed to create baselines dir");
let baseline_path = baselines_dir.join(format!("{}.json", bench_name));
let receipt = serde_json::json!({
"schema": "perfgate.run.v1",
"tool": {
"name": "perfgate",
"version": "0.1.0"
},
"run": {
"id": "baseline-run-id",
"started_at": "2024-01-01T00:00:00Z",
"ended_at": "2024-01-01T00:01:00Z",
"host": {
"os": os,
"arch": arch,
"cpu_count": cpu_count,
"memory_bytes": 17179869184_u64,
"hostname_hash": hostname_hash
}
},
"bench": {
"name": bench_name,
"command": ["echo", "hello"],
"repeat": 2,
"warmup": 0
},
"samples": [
{"wall_ms": 10000, "exit_code": 0, "warmup": false, "timed_out": false},
{"wall_ms": 10200, "exit_code": 0, "warmup": false, "timed_out": false}
],
"stats": {
"wall_ms": {
"median": 10100,
"min": 10000,
"max": 10200
}
}
});
fs::write(
&baseline_path,
serde_json::to_string_pretty(&receipt).unwrap(),
)
.expect("Failed to write baseline");
baseline_path
}
#[test]
fn test_check_host_mismatch_warn_policy() {
let temp_dir = tempdir().expect("failed to create temp dir");
let out_dir = temp_dir.path().join("artifacts");
let config_path = create_config_file(temp_dir.path(), "host-warn-test");
create_baseline_receipt_with_host(
temp_dir.path(),
"host-warn-test",
"some-other-os", "x86_64",
8,
"baseline-hostname-hash",
);
let mut cmd = perfgate_cmd();
cmd.current_dir(temp_dir.path())
.arg("check")
.arg("--config")
.arg(&config_path)
.arg("--bench")
.arg("host-warn-test")
.arg("--host-mismatch")
.arg("warn")
.arg("--out-dir")
.arg(&out_dir);
let output = cmd.output().expect("failed to execute check");
assert!(
output.status.success(),
"check with --host-mismatch=warn should succeed: exit code {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("host mismatch") || stderr.contains("OS mismatch"),
"stderr should mention host mismatch: {}",
stderr
);
assert!(out_dir.join("run.json").exists(), "run.json should exist");
assert!(
out_dir.join("compare.json").exists(),
"compare.json should exist when baseline is present"
);
}
#[test]
fn test_check_host_mismatch_error_policy() {
let temp_dir = tempdir().expect("failed to create temp dir");
let out_dir = temp_dir.path().join("artifacts");
let config_path = create_config_file(temp_dir.path(), "host-error-test");
create_baseline_receipt_with_host(
temp_dir.path(),
"host-error-test",
"some-other-os", "x86_64",
8,
"baseline-hostname-hash",
);
let mut cmd = perfgate_cmd();
cmd.current_dir(temp_dir.path())
.arg("check")
.arg("--config")
.arg(&config_path)
.arg("--bench")
.arg("host-error-test")
.arg("--host-mismatch")
.arg("error")
.arg("--out-dir")
.arg(&out_dir);
let output = cmd.output().expect("failed to execute check");
assert!(
!output.status.success(),
"check with --host-mismatch=error should fail when hosts differ"
);
assert_eq!(
output.status.code(),
Some(1),
"exit code should be 1 for tool error"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("host mismatch"),
"stderr should mention host mismatch: {}",
stderr
);
}
#[test]
fn test_check_host_mismatch_ignore_policy() {
let temp_dir = tempdir().expect("failed to create temp dir");
let out_dir = temp_dir.path().join("artifacts");
let config_path = create_config_file(temp_dir.path(), "host-ignore-test");
create_baseline_receipt_with_host(
temp_dir.path(),
"host-ignore-test",
"some-other-os", "x86_64",
8,
"baseline-hostname-hash",
);
let mut cmd = perfgate_cmd();
cmd.current_dir(temp_dir.path())
.arg("check")
.arg("--config")
.arg(&config_path)
.arg("--bench")
.arg("host-ignore-test")
.arg("--host-mismatch")
.arg("ignore")
.arg("--out-dir")
.arg(&out_dir);
let output = cmd.output().expect("failed to execute check");
assert!(
output.status.success(),
"check with --host-mismatch=ignore should succeed: exit code {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("host mismatch"),
"stderr should NOT mention host mismatch with ignore policy: {}",
stderr
);
assert!(out_dir.join("run.json").exists(), "run.json should exist");
assert!(
out_dir.join("compare.json").exists(),
"compare.json should exist"
);
}
#[test]
fn test_check_explicit_baseline_host_mismatch_error() {
let temp_dir = tempdir().expect("failed to create temp dir");
let out_dir = temp_dir.path().join("artifacts");
let config_path = create_config_file(temp_dir.path(), "explicit-baseline-test");
let baseline = fixtures_dir().join("baseline_host_linux_x86.json");
let mut cmd = perfgate_cmd();
cmd.arg("check")
.arg("--config")
.arg(&config_path)
.arg("--bench")
.arg("explicit-baseline-test")
.arg("--baseline")
.arg(&baseline)
.arg("--host-mismatch")
.arg("error")
.arg("--out-dir")
.arg(&out_dir);
let output = cmd.output().expect("failed to execute check");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("host mismatch"),
"if failing, stderr should mention host mismatch: {}",
stderr
);
}
}
#[test]
fn test_check_explicit_baseline_host_mismatch_warn() {
let temp_dir = tempdir().expect("failed to create temp dir");
let out_dir = temp_dir.path().join("artifacts");
let config_path = create_config_file(temp_dir.path(), "explicit-warn-test");
let baseline = fixtures_dir().join("baseline_host_linux_x86.json");
let mut cmd = perfgate_cmd();
cmd.arg("check")
.arg("--config")
.arg(&config_path)
.arg("--bench")
.arg("explicit-warn-test")
.arg("--baseline")
.arg(&baseline)
.arg("--host-mismatch")
.arg("warn")
.arg("--out-dir")
.arg(&out_dir);
let output = cmd.output().expect("failed to execute check");
assert!(
output.status.success(),
"check with --host-mismatch=warn should succeed: exit code {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
assert!(out_dir.join("run.json").exists(), "run.json should exist");
}
#[test]
fn test_compare_host_mismatch_invalid_policy() {
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("--host-mismatch")
.arg("invalid-policy")
.arg("--out")
.arg(&output_path);
cmd.assert()
.failure()
.stderr(predicate::str::contains("invalid host mismatch policy"));
}
#[test]
fn test_check_host_mismatch_invalid_policy() {
let temp_dir = tempdir().expect("failed to create temp dir");
let out_dir = temp_dir.path().join("artifacts");
let config_path = create_config_file(temp_dir.path(), "invalid-policy-test");
let mut cmd = perfgate_cmd();
cmd.arg("check")
.arg("--config")
.arg(&config_path)
.arg("--bench")
.arg("invalid-policy-test")
.arg("--host-mismatch")
.arg("invalid-policy")
.arg("--out-dir")
.arg(&out_dir);
let output = cmd.output().expect("failed to execute check");
assert!(
!output.status.success(),
"check with invalid policy should fail"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("invalid host mismatch policy"),
"stderr should mention invalid policy: {}",
stderr
);
}