use super::tests_common::rpt;
use super::*;
#[test]
fn plan_default_empty() {
let plan = AssertPlan::new();
assert!(!plan.not_starved);
assert!(!plan.isolation);
assert!(plan.max_gap_ms.is_none());
assert!(plan.max_spread_pct.is_none());
}
#[test]
fn plan_check_not_starved() {
let plan = AssertPlan::new().check_not_starved();
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 50)];
let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_pass());
assert_eq!(r.stats.total_workers, 1);
}
#[test]
fn plan_check_isolation_with_cpuset() {
let plan = AssertPlan::new().check_not_starved().check_isolation();
let expected: BTreeSet<usize> = [0, 1].into_iter().collect();
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0, 1, 4], 50)];
let r = plan.assert_cgroup(&reports, Some(&expected), None);
assert!(r.is_fail());
assert!(
r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Isolation))
);
}
#[test]
fn plan_isolation_skipped_without_cpuset() {
let plan = AssertPlan::new().check_isolation();
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0, 1, 4], 50)];
let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_pass());
}
#[test]
fn plan_custom_gap_threshold_pass() {
let plan = AssertPlan::new().check_not_starved().max_gap_ms(3000);
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 2500)];
let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_pass(), "2500ms < 3000ms threshold: {:?}", r.outcomes);
}
#[test]
fn plan_custom_gap_threshold_fail() {
let plan = AssertPlan::new().check_not_starved().max_gap_ms(1500);
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 2000)];
let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_fail());
assert!(
r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Stuck))
);
assert!(
r.failure_details()
.any(|d| d.message.contains("threshold 1500ms"))
);
}
#[test]
fn plan_custom_gap_threshold_produces_stuck_kind() {
let plan = AssertPlan::new().check_not_starved().max_gap_ms(1500);
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 2000)];
let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_fail());
assert!(
r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Stuck)),
"custom gap override must produce a Stuck-kind detail: {:?}",
r.outcomes
);
}
#[test]
fn plan_permissive_overrides_clear_unfair_and_stuck_preserve_starved() {
let reports = [
rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 500),
rpt(2, 0, 5e9 as u64, 0, &[0], 500),
rpt(3, 500, 5e9 as u64, 4e9 as u64, &[0], 500),
rpt(4, 1000, 5e9 as u64, 5e8 as u64, &[0], 4000),
];
let mut plan = AssertPlan::new();
plan.not_starved = true;
plan.max_spread_pct = Some(100.0);
plan.max_gap_ms = Some(5000);
let r = plan.assert_cgroup(&reports, None, None);
assert!(
r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Starved)),
"starved detail must survive permissive overrides: {:?}",
r.outcomes
);
assert!(
!r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Unfair)),
"unfair detail must be cleared by permissive spread: {:?}",
r.outcomes
);
assert!(
!r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Stuck)),
"stuck detail must be cleared by permissive gap: {:?}",
r.outcomes
);
assert!(!r.is_pass(), "starved alone is still a failure");
}
#[test]
fn plan_no_checks_always_passes() {
let plan = AssertPlan::new();
let reports = [rpt(1, 0, 0, 0, &[], 5000)]; let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_pass(), "no checks enabled should pass");
}
#[test]
fn plan_default_all_checks_disabled() {
let plan = AssertPlan::default();
assert!(!plan.not_starved, "default must not enable not_starved");
assert!(!plan.isolation, "default must not enable isolation");
assert!(
plan.max_gap_ms.is_none(),
"default must not set gap override"
);
assert!(
plan.max_spread_pct.is_none(),
"default must not set spread override"
);
let reports = [rpt(1, 0, 0, 0, &[], 99999)];
let r = plan.assert_cgroup(&reports, None, None);
assert!(r.is_pass(), "all-disabled plan must pass any input");
}
#[test]
fn assert_plan_default_equals_new() {
let d = AssertPlan::default();
let n = AssertPlan::new();
assert_eq!(d.not_starved, n.not_starved);
assert_eq!(d.isolation, n.isolation);
assert_eq!(d.max_gap_ms, n.max_gap_ms);
assert_eq!(d.max_spread_pct, n.max_spread_pct);
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 50)];
let rd = d.assert_cgroup(&reports, None, None);
let rn = n.assert_cgroup(&reports, None, None);
assert_eq!(rd.is_pass(), rn.is_pass());
}
#[test]
fn plan_starved_still_fails_with_custom_gap() {
let plan = AssertPlan::new().check_not_starved().max_gap_ms(5000);
let reports = [
rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 100), rpt(2, 0, 5e9 as u64, 0, &[1], 1500), ];
let r = plan.assert_cgroup(&reports, None, None);
assert!(
!r.is_pass(),
"starved worker must fail even with relaxed gap threshold"
);
assert!(
r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Starved))
);
assert!(
!r.failure_details()
.any(|d| matches!(d.kind, DetailKind::Stuck))
);
}