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.passed, "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_pass_demotes_skip() {
let mut a = AssertResult::skip("optional");
let b = AssertResult::pass();
a.merge(b);
assert!(!a.skipped);
assert!(a.passed);
}
#[test]
fn merge_skip_plus_fail_is_fail_not_skip() {
let mut a = AssertResult::skip("topo missing");
let mut b = AssertResult::pass();
b.passed = false;
a.merge(b);
assert!(!a.passed);
assert!(!a.skipped);
}
#[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 {
passed: true,
skipped: false,
details: 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(),
}
}
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.passed = false;
fail.details.push("something failed".into());
let mut merged = pass;
merged.merge(fail);
assert!(!merged.passed, "merging pass+fail must produce fail");
assert!(
merged
.details
.iter()
.any(|d| d.contains("something failed"))
);
}
#[test]
fn merge_fail_and_pass() {
let mut fail = AssertResult::pass();
fail.passed = false;
fail.details.push("first failed".into());
let pass = AssertResult::pass();
let mut merged = fail;
merged.merge(pass);
assert!(!merged.passed, "merging fail+pass must produce fail");
}
#[test]
fn assert_result_merge_combines_stats() {
let mut a = AssertResult {
passed: true,
skipped: false,
details: vec!["a".into()],
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(),
};
let b = AssertResult {
passed: false,
skipped: false,
details: vec!["b".into()],
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(),
};
a.merge(b);
assert!(!a.passed);
assert_eq!(a.details, 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);
}