use super::tests_common::rpt;
use super::*;
#[test]
fn merge_cgroups() {
let r1 = assert_not_starved(&[
rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0, 1], 50),
rpt(2, 1000, 5e9 as u64, 6e8 as u64, &[0, 1], 60),
]);
let r2 = assert_not_starved(&[
rpt(3, 1000, 5e9 as u64, 25e8 as u64, &[2, 3], 50),
rpt(4, 1000, 5e9 as u64, 26e8 as u64, &[2, 3], 50),
]);
let mut m = r1;
m.merge(r2);
assert_eq!(m.stats.cgroups.len(), 2);
assert_eq!(m.stats.total_workers, 4);
assert!(m.is_pass(), "diff cgroups diff off_cpu should pass");
}
#[test]
fn merge_takes_worst_gap() {
let r1 = assert_not_starved(&[rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 100)]);
let r2 = assert_not_starved(&[rpt(2, 1000, 5e9 as u64, 5e8 as u64, &[1], 500)]);
let mut m = r1;
m.merge(r2);
assert_eq!(m.stats.worst_gap_ms, 500);
assert_eq!(m.stats.worst_gap_cpu, 1);
}
#[test]
fn merge_takes_worst_gap_reverse_self_retains() {
let r1 = assert_not_starved(&[rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 700)]);
let r2 = assert_not_starved(&[rpt(2, 1000, 5e9 as u64, 5e8 as u64, &[1], 200)]);
let mut m = r1;
m.merge(r2);
assert_eq!(
m.stats.worst_gap_ms, 700,
"self's larger gap must be retained",
);
assert_eq!(
m.stats.worst_gap_cpu, 0,
"worst_gap_cpu must stay coupled to self's worst_gap_ms — \
a regression overwriting cpu from other would set this to 1",
);
}
#[test]
fn merge_takes_worst_spread() {
let r1 = assert_not_starved(&[
rpt(1, 1000, 5e9 as u64, 1e9 as u64, &[0], 50),
rpt(2, 1000, 5e9 as u64, 12e8 as u64, &[0], 50),
]); let r2 = assert_not_starved(&[
rpt(3, 1000, 5e9 as u64, 1e9 as u64, &[1], 50),
rpt(4, 1000, 5e9 as u64, 15e8 as u64, &[1], 50),
]); let mut m = r1;
m.merge(r2);
assert!((m.stats.worst_spread - 10.0).abs() < 0.1);
}
#[test]
fn merge_skip_plus_explicit_pass_demotes_skip() {
let mut a = AssertResult::skip("optional");
let mut b = AssertResult::pass();
b.record_pass();
a.merge(b);
assert!(
!a.is_skip(),
"explicit Pass in the merged stream means not all-Skip"
);
assert!(a.is_pass(), "explicit Pass + no Fail → is_pass=true");
}
#[test]
fn merge_skip_plus_empty_pass_stays_skip() {
let mut a = AssertResult::skip("optional");
let b = AssertResult::pass();
a.merge(b);
assert!(
a.is_skip(),
"empty pass() merges to a no-op; stream stays all-Skip"
);
assert!(!a.is_pass(), "all-Skip is not pass");
}
#[test]
fn merge_skip_plus_fail_is_fail_not_skip() {
let mut a = AssertResult::skip("topo missing");
let mut b = AssertResult::pass();
b.record_fail(AssertDetail::new(DetailKind::Other, "synthetic fail"));
a.merge(b);
assert!(a.is_fail());
assert!(!a.is_skip());
}
#[test]
fn merge_accumulates_totals() {
let r1 = assert_not_starved(&[rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 50)]);
let r2 = assert_not_starved(&[rpt(2, 1000, 5e9 as u64, 5e8 as u64, &[1], 50)]);
let mut m = r1;
m.merge(r2);
assert_eq!(m.stats.total_workers, 2);
assert_eq!(m.stats.total_cpus, 2);
}
#[test]
fn merge_three_cgroups_worst_wins_and_iterations_sum() {
fn mk(
worst_spread: f64,
worst_mig: f64,
worst_p99_us: f64,
total_iters: u64,
page_locality: f64,
iters_per_worker: f64,
cg_total_iters: u64,
) -> AssertResult {
let cg = CgroupStats {
total_iterations: cg_total_iters,
page_locality,
..CgroupStats::default()
};
AssertResult {
outcomes: vec![],
passes: vec![],
stats: ScenarioStats {
total_iterations: total_iters,
worst_spread,
worst_migration_ratio: worst_mig,
worst_p99_wake_latency_us: worst_p99_us,
worst_page_locality: page_locality,
worst_iterations_per_worker: iters_per_worker,
cgroups: vec![cg],
..ScenarioStats::default()
},
measurements: std::collections::BTreeMap::new(),
info_notes: vec![],
}
}
let mut acc = mk(10.0, 0.1, 50.0, 100, 0.8, 300.0, 100);
acc.merge(mk(5.0, 0.3, 20.0, 200, 0.5, 150.0, 200));
acc.merge(mk(20.0, 0.2, 70.0, 400, 0.9, 500.0, 400));
let s = &acc.stats;
assert_eq!(
s.cgroups.len(),
3,
"3 cgroups must accumulate; a missing entry means stats.cgroups.extend dropped a merge",
);
assert_eq!(s.cgroups[0].total_iterations, 100);
assert_eq!(s.cgroups[1].total_iterations, 200);
assert_eq!(s.cgroups[2].total_iterations, 400);
assert_eq!(s.worst_spread, 20.0, "third cgroup's 20.0 is worst");
assert_eq!(s.worst_migration_ratio, 0.3, "second cgroup's 0.3 is worst");
assert_eq!(
s.worst_p99_wake_latency_us, 70.0,
"third cgroup's 70.0us p99 is worst",
);
assert_eq!(
s.worst_page_locality, 0.5,
"second cgroup's 0.5 is the lowest-non-zero — 0 sentinel never wins",
);
assert_eq!(
s.worst_iterations_per_worker, 150.0,
"second cgroup's 150 is the lowest-non-zero per-worker throughput",
);
assert_eq!(
s.total_iterations,
100 + 200 + 400,
"total_iterations must sum (not max) across all merged cgroups",
);
}
#[test]
fn merge_scenario_stats_worst_wins_and_iterations_sum() {
let mut a = AssertResult::pass();
a.stats.total_iterations = 100;
a.stats.worst_spread = 5.0;
a.stats.worst_migration_ratio = 0.1;
a.stats.worst_p99_wake_latency_us = 20.0;
a.stats.worst_median_wake_latency_us = 10.0;
a.stats.worst_wake_latency_cv = 0.2;
a.stats.worst_run_delay_us = 50.0;
a.stats.worst_mean_run_delay_us = 30.0;
a.stats.worst_cross_node_migration_ratio = 0.05;
let mut b = AssertResult::pass();
b.stats.total_iterations = 400;
b.stats.worst_spread = 15.0;
b.stats.worst_migration_ratio = 0.4;
b.stats.worst_p99_wake_latency_us = 80.0;
b.stats.worst_median_wake_latency_us = 40.0;
b.stats.worst_wake_latency_cv = 0.5;
b.stats.worst_run_delay_us = 120.0;
b.stats.worst_mean_run_delay_us = 90.0;
b.stats.worst_cross_node_migration_ratio = 0.25;
a.merge(b);
assert_eq!(a.stats.total_iterations, 500);
assert_eq!(a.stats.worst_spread, 15.0);
assert_eq!(a.stats.worst_migration_ratio, 0.4);
assert_eq!(a.stats.worst_p99_wake_latency_us, 80.0);
assert_eq!(a.stats.worst_median_wake_latency_us, 40.0);
assert_eq!(a.stats.worst_wake_latency_cv, 0.5);
assert_eq!(a.stats.worst_run_delay_us, 120.0);
assert_eq!(a.stats.worst_mean_run_delay_us, 90.0);
assert_eq!(a.stats.worst_cross_node_migration_ratio, 0.25);
}
#[test]
fn merge_derived_ratios_use_correct_polarities() {
let mut a = AssertResult::pass();
a.stats.worst_wake_latency_tail_ratio = 2.0;
a.stats.worst_iterations_per_worker = 500.0;
let mut b = AssertResult::pass();
b.stats.worst_wake_latency_tail_ratio = 8.0;
b.stats.worst_iterations_per_worker = 100.0;
a.merge(b);
assert_eq!(
a.stats.worst_wake_latency_tail_ratio, 8.0,
"tail ratio uses max — 8.0 is worse than 2.0 (more \
amplification); got {}",
a.stats.worst_wake_latency_tail_ratio,
);
assert_eq!(
a.stats.worst_iterations_per_worker, 100.0,
"iterations_per_worker uses lowest-non-zero — 100.0 is \
worse than 500.0 (less throughput per worker); got {}",
a.stats.worst_iterations_per_worker,
);
let mut c = AssertResult::pass();
c.stats.worst_iterations_per_worker = 300.0;
let mut empty = AssertResult::pass();
empty.stats.worst_iterations_per_worker = 0.0;
c.merge(empty);
assert_eq!(
c.stats.worst_iterations_per_worker, 300.0,
"self=300 must be retained when other=0 (unreported \
sentinel) — a plain min would let the sentinel \
clobber the real reading; got {}",
c.stats.worst_iterations_per_worker,
);
let mut d = AssertResult::pass();
d.stats.worst_iterations_per_worker = 0.0;
let mut real = AssertResult::pass();
real.stats.worst_iterations_per_worker = 300.0;
d.merge(real);
assert_eq!(
d.stats.worst_iterations_per_worker, 300.0,
"self=0 (accumulator sentinel) must adopt other=300 \
— the `AssertResult::pass().merge(real)` pattern \
depends on this; got {}",
d.stats.worst_iterations_per_worker,
);
let mut e = AssertResult::pass();
e.stats.worst_iterations_per_worker = 0.0;
let mut f = AssertResult::pass();
f.stats.worst_iterations_per_worker = 0.0;
e.merge(f);
assert_eq!(
e.stats.worst_iterations_per_worker, 0.0,
"both-zero must stay zero; got {}",
e.stats.worst_iterations_per_worker,
);
let mut g = AssertResult::pass();
g.stats.worst_wake_latency_tail_ratio = 8.0;
let mut h = AssertResult::pass();
h.stats.worst_wake_latency_tail_ratio = 2.0;
g.merge(h);
assert_eq!(
g.stats.worst_wake_latency_tail_ratio, 8.0,
"tail_ratio uses max: self=8.0, other=2.0 → self \
retains 8.0 (higher is worse); got {}",
g.stats.worst_wake_latency_tail_ratio,
);
}
#[test]
fn merge_scenario_stats_worst_wins_when_other_is_smaller() {
let mut a = AssertResult::pass();
a.stats.worst_spread = 30.0;
a.stats.worst_gap_ms = 500;
a.stats.worst_gap_cpu = 7;
a.stats.worst_migration_ratio = 0.9;
a.stats.worst_p99_wake_latency_us = 100.0;
a.stats.worst_median_wake_latency_us = 60.0;
a.stats.worst_wake_latency_cv = 0.7;
a.stats.worst_run_delay_us = 300.0;
a.stats.worst_mean_run_delay_us = 200.0;
a.stats.worst_cross_node_migration_ratio = 0.35;
a.stats.total_iterations = 500;
let mut b = AssertResult::pass();
b.stats.worst_spread = 5.0;
b.stats.worst_gap_ms = 100;
b.stats.worst_gap_cpu = 3;
b.stats.worst_migration_ratio = 0.1;
b.stats.worst_p99_wake_latency_us = 10.0;
b.stats.worst_median_wake_latency_us = 5.0;
b.stats.worst_wake_latency_cv = 0.1;
b.stats.worst_run_delay_us = 40.0;
b.stats.worst_mean_run_delay_us = 20.0;
b.stats.worst_cross_node_migration_ratio = 0.05;
b.stats.total_iterations = 50;
a.merge(b);
assert_eq!(a.stats.worst_spread, 30.0);
assert_eq!(a.stats.worst_gap_ms, 500);
assert_eq!(a.stats.worst_gap_cpu, 7);
assert_eq!(a.stats.worst_migration_ratio, 0.9);
assert_eq!(a.stats.worst_p99_wake_latency_us, 100.0);
assert_eq!(a.stats.worst_median_wake_latency_us, 60.0);
assert_eq!(a.stats.worst_wake_latency_cv, 0.7);
assert_eq!(a.stats.worst_run_delay_us, 300.0);
assert_eq!(a.stats.worst_mean_run_delay_us, 200.0);
assert_eq!(a.stats.worst_cross_node_migration_ratio, 0.35);
assert_eq!(a.stats.total_iterations, 550);
}
#[test]
fn merge_worst_page_locality_lowest_non_zero() {
let mut a = AssertResult::pass();
a.stats.worst_page_locality = 0.0;
let mut b = AssertResult::pass();
b.stats.worst_page_locality = 0.8;
a.merge(b);
assert_eq!(
a.stats.worst_page_locality, 0.8,
"unreported self must adopt other's reading"
);
let mut a = AssertResult::pass();
a.stats.worst_page_locality = 0.6;
let mut b = AssertResult::pass();
b.stats.worst_page_locality = 0.8;
a.merge(b);
assert_eq!(
a.stats.worst_page_locality, 0.6,
"lower non-zero reading wins across cgroups"
);
let mut a = AssertResult::pass();
a.stats.worst_page_locality = 0.8;
let mut b = AssertResult::pass();
b.stats.worst_page_locality = 0.0;
a.merge(b);
assert_eq!(
a.stats.worst_page_locality, 0.8,
"unreported other must not clobber self's reading"
);
}
#[test]
fn merge_ext_metrics_higher_is_worse_takes_max() {
let mut a = AssertResult::pass();
a.stats.ext_metrics.insert("worst_spread".into(), 10.0);
let mut b = AssertResult::pass();
b.stats.ext_metrics.insert("worst_spread".into(), 42.0);
a.merge(b);
assert_eq!(a.stats.ext_metrics["worst_spread"], 42.0);
}
#[test]
fn merge_ext_metrics_higher_is_better_takes_min() {
let mut a = AssertResult::pass();
a.stats.ext_metrics.insert("total_iterations".into(), 10.0);
let mut b = AssertResult::pass();
b.stats.ext_metrics.insert("total_iterations".into(), 42.0);
a.merge(b);
assert_eq!(
a.stats.ext_metrics["total_iterations"], 10.0,
"higher_is_worse=false must take min on merge"
);
}
#[test]
fn merge_ext_metrics_unknown_metric_defaults_to_max() {
let mut a = AssertResult::pass();
a.stats.ext_metrics.insert("unknown_metric".into(), 10.0);
let mut b = AssertResult::pass();
b.stats.ext_metrics.insert("unknown_metric".into(), 42.0);
a.merge(b);
assert_eq!(a.stats.ext_metrics["unknown_metric"], 42.0);
}
#[test]
fn merge_ext_metrics_first_insert_uses_other_value() {
let mut a = AssertResult::pass();
let mut b = AssertResult::pass();
b.stats.ext_metrics.insert("total_iterations".into(), 77.0);
a.merge(b);
assert_eq!(a.stats.ext_metrics["total_iterations"], 77.0);
}
#[test]
fn merge_pass_and_fail() {
let pass = AssertResult::pass();
let mut fail = AssertResult::pass();
fail.record_fail(AssertDetail::new(DetailKind::Other, "something failed"));
let mut merged = pass;
merged.merge(fail);
assert!(merged.is_fail(), "merging pass+fail must produce fail");
assert!(
merged
.failure_details()
.any(|d| d.message.contains("something failed"))
);
}
#[test]
fn merge_fail_and_pass() {
let mut fail = AssertResult::pass();
fail.record_fail(AssertDetail::new(DetailKind::Other, "first failed"));
let pass = AssertResult::pass();
let mut merged = fail;
merged.merge(pass);
assert!(merged.is_fail(), "merging fail+pass must produce fail");
}
#[test]
fn merge_outcomes_extend_and_stats_sum_coexist() {
let mut a = AssertResult::pass();
a.record_fail(AssertDetail::new(DetailKind::Other, "fail_a"));
a.stats.total_iterations = 100;
a.stats.total_workers = 2;
let mut b = AssertResult::pass();
b.record_skip("skip_b");
b.stats.total_iterations = 50;
b.stats.total_workers = 3;
a.merge(b);
assert_eq!(a.outcomes.len(), 2, "Fail + Skip both extend");
assert!(a.is_fail(), "Fail dominates the verdict");
assert_eq!(a.stats.total_iterations, 150, "stats SUM (not max)");
assert_eq!(a.stats.total_workers, 5);
assert_eq!(a.failure_details().count(), 1);
assert_eq!(a.skip_details().count(), 1);
}
#[test]
fn merge_inconclusive_precedence() {
fn merged(lhs: AssertResult, rhs: AssertResult) -> AssertResult {
let mut a = lhs;
a.merge(rhs);
a
}
fn mk_pass() -> AssertResult {
AssertResult::pass()
}
fn mk_skip() -> AssertResult {
let mut r = AssertResult::pass();
r.record_skip("s");
r
}
fn mk_inconc() -> AssertResult {
let mut r = AssertResult::pass();
r.record_inconclusive(AssertDetail::new(DetailKind::Other, "i"));
r
}
fn mk_fail() -> AssertResult {
let mut r = AssertResult::pass();
r.record_fail(AssertDetail::new(DetailKind::Other, "f"));
r
}
let pi = merged(mk_pass(), mk_inconc());
assert!(pi.is_inconclusive() && !pi.is_fail() && !pi.is_pass());
let ip = merged(mk_inconc(), mk_pass());
assert!(ip.is_inconclusive() && !ip.is_fail() && !ip.is_pass());
let si = merged(mk_skip(), mk_inconc());
assert!(si.is_inconclusive() && !si.is_skip() && !si.is_fail());
let is_ = merged(mk_inconc(), mk_skip());
assert!(is_.is_inconclusive() && !is_.is_skip() && !is_.is_fail());
let fi = merged(mk_fail(), mk_inconc());
assert!(fi.is_fail() && !fi.is_inconclusive() && !fi.is_pass());
let if_ = merged(mk_inconc(), mk_fail());
assert!(if_.is_fail() && !if_.is_inconclusive() && !if_.is_pass());
let ii = merged(mk_inconc(), mk_inconc());
assert!(ii.is_inconclusive() && !ii.is_fail() && !ii.is_pass());
assert_eq!(
ii.outcomes.len(),
2,
"both Inconclusive outcomes extend the merged vec"
);
}
#[test]
fn assert_result_merge_combines_stats() {
let mut a = AssertResult {
outcomes: vec![Outcome::Fail(AssertDetail::new(DetailKind::Other, "a"))],
passes: vec![],
stats: ScenarioStats {
cgroups: vec![],
total_workers: 2,
total_cpus: 4,
total_migrations: 10,
worst_spread: 5.0,
worst_gap_ms: 100,
worst_gap_cpu: 0,
..Default::default()
},
measurements: std::collections::BTreeMap::new(),
info_notes: vec![],
};
let b = AssertResult {
outcomes: vec![Outcome::Fail(AssertDetail::new(DetailKind::Other, "b"))],
passes: vec![],
stats: ScenarioStats {
cgroups: vec![],
total_workers: 3,
total_cpus: 6,
total_migrations: 20,
worst_spread: 15.0,
worst_gap_ms: 500,
worst_gap_cpu: 2,
..Default::default()
},
measurements: std::collections::BTreeMap::new(),
info_notes: vec![],
};
a.merge(b);
assert!(a.is_fail());
assert_eq!(
a.failure_details()
.map(|d| d.message.as_str())
.collect::<Vec<_>>(),
vec!["a", "b"]
);
assert_eq!(a.stats.total_workers, 5);
assert_eq!(a.stats.total_cpus, 10);
assert_eq!(a.stats.total_migrations, 30);
assert_eq!(a.stats.worst_spread, 15.0);
assert_eq!(a.stats.worst_gap_ms, 500);
assert_eq!(a.stats.worst_gap_cpu, 2);
}
#[test]
fn assert_result_merge_ext_metrics_max_value() {
let mut a = AssertResult::pass();
a.stats.ext_metrics.insert("latency".into(), 10.0);
a.stats.ext_metrics.insert("throughput".into(), 100.0);
let mut b = AssertResult::pass();
b.stats.ext_metrics.insert("latency".into(), 20.0);
b.stats.ext_metrics.insert("jitter".into(), 5.0);
a.merge(b);
assert_eq!(a.stats.ext_metrics["latency"], 20.0);
assert_eq!(a.stats.ext_metrics["throughput"], 100.0);
assert_eq!(a.stats.ext_metrics["jitter"], 5.0);
}
#[test]
fn assert_result_merge_ext_metrics_keeps_larger() {
let mut a = AssertResult::pass();
a.stats.ext_metrics.insert("x".into(), 50.0);
let mut b = AssertResult::pass();
b.stats.ext_metrics.insert("x".into(), 30.0);
a.merge(b);
assert_eq!(a.stats.ext_metrics["x"], 50.0);
}
fn phase_bucket(
step_index: u16,
label: &str,
start_ms: u64,
end_ms: u64,
sample_count: usize,
metrics: &[(&str, f64)],
) -> PhaseBucket {
PhaseBucket {
step_index,
label: label.to_string(),
start_ms,
end_ms,
sample_count,
metrics: metrics
.iter()
.map(|(k, v)| ((*k).to_string(), *v))
.collect(),
}
}
#[test]
fn assert_result_merge_per_phase_counter_sums() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
5,
&[("total_migrations", 25.0)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
5,
&[("total_migrations", 75.0)],
)];
a.merge(b);
assert_eq!(a.stats.phases.len(), 1);
assert_eq!(a.stats.phases[0].metrics["total_migrations"], 100.0);
}
#[test]
fn assert_result_merge_per_phase_peak_takes_max() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
2,
"Step[1]",
0,
100,
5,
&[("worst_gap_ms", 12.0)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
2,
"Step[1]",
0,
100,
5,
&[("worst_gap_ms", 7.0)],
)];
a.merge(b);
assert_eq!(a.stats.phases[0].metrics["worst_gap_ms"], 12.0);
}
#[test]
fn assert_result_merge_per_phase_gauge_last_takes_later_end_ms() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
200,
5,
&[("worst_spread", 0.42)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
5,
&[("worst_spread", 0.11)],
)];
a.merge(b);
assert_eq!(a.stats.phases[0].metrics["worst_spread"], 0.42);
assert_eq!(a.stats.phases[0].start_ms, 0);
assert_eq!(a.stats.phases[0].end_ms, 200);
}
#[test]
fn assert_result_merge_per_phase_gauge_last_reverse_picks_later_end_ms() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
5,
&[("worst_spread", 0.42)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
200,
5,
&[("worst_spread", 0.11)],
)];
a.merge(b);
assert_eq!(a.stats.phases[0].metrics["worst_spread"], 0.11);
assert_eq!(a.stats.phases[0].end_ms, 200);
}
#[test]
fn assert_result_merge_per_phase_unpaired_step_indices_keep_both() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
3,
&[("total_migrations", 5.0)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
2,
"Step[1]",
100,
200,
3,
&[("total_migrations", 8.0)],
)];
a.merge(b);
assert_eq!(a.stats.phases.len(), 2);
assert_eq!(a.stats.phases[0].step_index, 1);
assert_eq!(a.stats.phases[1].step_index, 2);
assert_eq!(a.stats.phases[0].metrics["total_migrations"], 5.0);
assert_eq!(a.stats.phases[1].metrics["total_migrations"], 8.0);
}
#[test]
fn assert_result_merge_per_phase_unknown_metric_takes_mean() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
0,
"BASELINE",
0,
100,
5,
&[("custom.metric", 10.0)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
0,
"BASELINE",
0,
100,
5,
&[("custom.metric", 30.0)],
)];
a.merge(b);
assert_eq!(a.stats.phases[0].metrics["custom.metric"], 20.0);
}
#[test]
fn assert_result_merge_per_phase_one_side_only_keeps_value() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
5,
&[("total_migrations", 7.0), ("worst_gap_ms", 12.0)],
)];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(
1,
"Step[0]",
0,
100,
5,
&[("total_migrations", 3.0)],
)];
a.merge(b);
assert_eq!(a.stats.phases[0].metrics["total_migrations"], 10.0);
assert_eq!(a.stats.phases[0].metrics["worst_gap_ms"], 12.0);
}
#[test]
fn assert_result_merge_per_phase_window_invariants() {
let mut a = AssertResult::pass();
a.stats.phases = vec![phase_bucket(1, "Step[0]", 50, 150, 4, &[])];
let mut b = AssertResult::pass();
b.stats.phases = vec![phase_bucket(1, "Step[0]", 10, 200, 6, &[])];
a.merge(b);
assert_eq!(a.stats.phases[0].start_ms, 10);
assert_eq!(a.stats.phases[0].end_ms, 200);
assert_eq!(a.stats.phases[0].sample_count, 10);
}
#[test]
fn merge_kind_enum_exhaustively_covers_metric_kind_variants() {
use crate::stats::{GaugeAgg, MergeKind, MetricKind};
assert_eq!(MetricKind::Counter.merge_kind(), MergeKind::Commutative);
assert_eq!(MetricKind::Peak.merge_kind(), MergeKind::Commutative);
assert_eq!(
MetricKind::Gauge(GaugeAgg::Avg).merge_kind(),
MergeKind::Commutative,
);
assert_eq!(
MetricKind::Gauge(GaugeAgg::Max).merge_kind(),
MergeKind::Commutative,
);
assert_eq!(
MetricKind::Gauge(GaugeAgg::Last).merge_kind(),
MergeKind::NonCommutative,
);
assert_eq!(
MetricKind::Timestamp.merge_kind(),
MergeKind::NonCommutative,
);
}