use perfgate_types::{HostInfo, RunMeta, RunReceipt};
#[derive(Debug, Clone)]
pub struct PromoteRequest {
pub receipt: RunReceipt,
pub normalize: bool,
}
#[derive(Debug, Clone)]
pub struct PromoteResult {
pub receipt: RunReceipt,
}
pub struct PromoteUseCase;
impl PromoteUseCase {
pub fn execute(req: PromoteRequest) -> PromoteResult {
let receipt = if req.normalize {
Self::normalize_receipt(req.receipt)
} else {
req.receipt
};
PromoteResult { receipt }
}
fn normalize_receipt(mut receipt: RunReceipt) -> RunReceipt {
receipt.run = RunMeta {
id: "baseline".to_string(),
started_at: "1970-01-01T00:00:00Z".to_string(),
ended_at: "1970-01-01T00:00:00Z".to_string(),
host: HostInfo {
os: receipt.run.host.os,
arch: receipt.run.host.arch,
cpu_count: receipt.run.host.cpu_count,
memory_bytes: receipt.run.host.memory_bytes,
hostname_hash: receipt.run.host.hostname_hash,
},
};
receipt
}
}
#[cfg(test)]
mod tests {
use super::*;
use perfgate_types::{BenchMeta, HostInfo, RunMeta, Sample, Stats, ToolInfo, U64Summary};
fn create_test_receipt() -> RunReceipt {
RunReceipt {
schema: "perfgate.run.v1".to_string(),
tool: ToolInfo {
name: "perfgate".to_string(),
version: "0.1.0".to_string(),
},
run: RunMeta {
id: "unique-run-id-12345".to_string(),
started_at: "2024-01-15T10:00:00Z".to_string(),
ended_at: "2024-01-15T10:00:05Z".to_string(),
host: HostInfo {
os: "linux".to_string(),
arch: "x86_64".to_string(),
cpu_count: Some(4),
memory_bytes: Some(8_000_000_000),
hostname_hash: Some("testhash123".to_string()),
},
},
bench: BenchMeta {
name: "test-benchmark".to_string(),
cwd: None,
command: vec!["echo".to_string(), "hello".to_string()],
repeat: 5,
warmup: 0,
work_units: None,
timeout_ms: None,
},
samples: vec![Sample {
wall_ms: 100,
exit_code: 0,
warmup: false,
timed_out: false,
cpu_ms: None,
page_faults: None,
ctx_switches: None,
max_rss_kb: None,
io_read_bytes: None,
io_write_bytes: None,
network_packets: None,
energy_uj: None,
binary_bytes: None,
stdout: None,
stderr: None,
}],
stats: Stats {
wall_ms: U64Summary::new(100, 100, 100),
cpu_ms: None,
page_faults: None,
ctx_switches: None,
max_rss_kb: None,
io_read_bytes: None,
io_write_bytes: None,
network_packets: None,
energy_uj: None,
binary_bytes: None,
throughput_per_s: None,
},
}
}
#[test]
fn test_promote_without_normalize() {
let receipt = create_test_receipt();
let original_run_id = receipt.run.id.clone();
let original_started_at = receipt.run.started_at.clone();
let result = PromoteUseCase::execute(PromoteRequest {
receipt,
normalize: false,
});
assert_eq!(result.receipt.run.id, original_run_id);
assert_eq!(result.receipt.run.started_at, original_started_at);
}
#[test]
fn test_promote_with_normalize() {
let receipt = create_test_receipt();
let result = PromoteUseCase::execute(PromoteRequest {
receipt,
normalize: true,
});
assert_eq!(result.receipt.run.id, "baseline");
assert_eq!(result.receipt.run.started_at, "1970-01-01T00:00:00Z");
assert_eq!(result.receipt.run.ended_at, "1970-01-01T00:00:00Z");
assert_eq!(result.receipt.run.host.os, "linux");
assert_eq!(result.receipt.run.host.arch, "x86_64");
assert_eq!(result.receipt.bench.name, "test-benchmark");
assert_eq!(result.receipt.stats.wall_ms.median, 100);
}
#[test]
fn test_normalize_preserves_bench_data() {
let receipt = create_test_receipt();
let result = PromoteUseCase::execute(PromoteRequest {
receipt: receipt.clone(),
normalize: true,
});
assert_eq!(result.receipt.bench.name, receipt.bench.name);
assert_eq!(result.receipt.bench.command, receipt.bench.command);
assert_eq!(result.receipt.bench.repeat, receipt.bench.repeat);
assert_eq!(result.receipt.bench.warmup, receipt.bench.warmup);
assert_eq!(result.receipt.samples.len(), receipt.samples.len());
assert_eq!(
result.receipt.samples[0].wall_ms,
receipt.samples[0].wall_ms
);
assert_eq!(
result.receipt.stats.wall_ms.median,
receipt.stats.wall_ms.median
);
}
#[test]
fn test_normalize_preserves_schema_and_tool() {
let receipt = create_test_receipt();
let result = PromoteUseCase::execute(PromoteRequest {
receipt: receipt.clone(),
normalize: true,
});
assert_eq!(result.receipt.schema, receipt.schema);
assert_eq!(result.receipt.tool.name, receipt.tool.name);
assert_eq!(result.receipt.tool.version, receipt.tool.version);
}
#[test]
fn test_promote_preserves_optional_none_fields() {
let mut receipt = create_test_receipt();
receipt.run.host.cpu_count = None;
receipt.run.host.memory_bytes = None;
receipt.run.host.hostname_hash = None;
receipt.bench.cwd = None;
receipt.bench.work_units = None;
receipt.bench.timeout_ms = None;
let result = PromoteUseCase::execute(PromoteRequest {
receipt,
normalize: true,
});
assert!(result.receipt.run.host.cpu_count.is_none());
assert!(result.receipt.run.host.memory_bytes.is_none());
assert!(result.receipt.run.host.hostname_hash.is_none());
assert!(result.receipt.bench.cwd.is_none());
assert!(result.receipt.bench.work_units.is_none());
}
#[test]
fn test_promote_normalize_idempotent() {
let receipt = create_test_receipt();
let first = PromoteUseCase::execute(PromoteRequest {
receipt,
normalize: true,
});
let second = PromoteUseCase::execute(PromoteRequest {
receipt: first.receipt.clone(),
normalize: true,
});
assert_eq!(first.receipt.run.id, second.receipt.run.id);
assert_eq!(first.receipt.run.started_at, second.receipt.run.started_at);
assert_eq!(first.receipt.run.ended_at, second.receipt.run.ended_at);
}
}