use serde::{Deserialize, Serialize};
use crate::baseline::fingerprint::Fingerprint;
use crate::model::Measurement;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BaselineComparison {
pub matched: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub stale_reasons: Vec<String>,
pub baseline_units: u64,
pub current_units: u64,
pub delta_units: i64,
pub delta_pct: f64,
}
impl BaselineComparison {
#[must_use]
pub fn compute(
baseline_units: u64,
baseline_fp: &Fingerprint,
current: &Measurement,
current_fp: &Fingerprint,
) -> Self {
let stale_reasons = baseline_fp.staleness_reasons(current_fp);
let current_units = current.total_cu;
let delta_units = current_units as i64 - baseline_units as i64;
let delta_pct = if baseline_units == 0 {
0.0
} else {
(delta_units as f64 / baseline_units as f64) * 100.0
};
Self {
matched: stale_reasons.is_empty(),
stale_reasons,
baseline_units,
current_units,
delta_units,
delta_pct,
}
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"{:+.2}% ({:+} CU) vs baseline {}",
self.delta_pct, self.delta_units, self.baseline_units
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fp(fixture: &str) -> Fingerprint {
Fingerprint::new("s", fixture, "cfg", None)
}
fn measurement(cu: u64) -> Measurement {
Measurement {
total_cu: cu,
..Measurement::empty()
}
}
#[test]
fn matched_comparison_reports_delta() {
let c = BaselineComparison::compute(91_204, &fp("a"), &measurement(96_812), &fp("a"));
assert!(c.matched);
assert_eq!(c.delta_units, 5_608);
assert!((c.delta_pct - 6.15).abs() < 0.01);
}
#[test]
fn changed_fixture_is_stale() {
let c = BaselineComparison::compute(91_204, &fp("a"), &measurement(96_812), &fp("b"));
assert!(!c.matched);
assert!(c.stale_reasons.iter().any(|r| r.contains("fixture hash")));
}
}