use super::tests_common::rpt;
use super::*;
#[test]
fn parse_numa_maps_basic() {
let content = "\
00400000 default file=/bin/cat mapped=10 N0=8 N1=2
00600000 default anon=5 N0=3 N1=2";
let entries = parse_numa_maps(content);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].addr, 0x00400000);
assert_eq!(entries[0].node_pages[&0], 8);
assert_eq!(entries[0].node_pages[&1], 2);
assert_eq!(entries[1].addr, 0x00600000);
assert_eq!(entries[1].node_pages[&0], 3);
assert_eq!(entries[1].node_pages[&1], 2);
}
#[test]
fn parse_numa_maps_empty() {
assert!(parse_numa_maps("").is_empty());
}
#[test]
fn parse_numa_maps_no_node_fields() {
let content = "00400000 default file=/bin/cat mapped=10";
let entries = parse_numa_maps(content);
assert!(entries.is_empty());
}
#[test]
fn parse_numa_maps_single_node() {
let content = "7f000000 default anon=100 N0=100";
let entries = parse_numa_maps(content);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].node_pages[&0], 100);
assert_eq!(entries[0].node_pages.len(), 1);
}
#[test]
fn parse_numa_maps_high_node_ids() {
let content = "7f000000 default N0=10 N3=20 N7=5";
let entries = parse_numa_maps(content);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].node_pages[&0], 10);
assert_eq!(entries[0].node_pages[&3], 20);
assert_eq!(entries[0].node_pages[&7], 5);
}
#[test]
fn parse_numa_maps_malformed_lines() {
let content = "\
not_hex default N0=10
00400000 default N0=10
default N0=5";
let entries = parse_numa_maps(content);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].addr, 0x00400000);
}
#[test]
fn page_locality_all_local() {
let entries = vec![NumaMapsEntry {
addr: 0x1000,
node_pages: [(0, 100)].into_iter().collect(),
}];
let expected: BTreeSet<usize> = [0].into_iter().collect();
let loc = page_locality(&entries, &expected);
assert!((loc - 1.0).abs() < f64::EPSILON);
}
#[test]
fn page_locality_mixed_nodes() {
let entries = vec![NumaMapsEntry {
addr: 0x1000,
node_pages: [(0, 80), (1, 20)].into_iter().collect(),
}];
let expected: BTreeSet<usize> = [0].into_iter().collect();
let loc = page_locality(&entries, &expected);
assert!((loc - 0.8).abs() < f64::EPSILON);
}
#[test]
fn page_locality_multi_expected_nodes() {
let entries = vec![NumaMapsEntry {
addr: 0x1000,
node_pages: [(0, 40), (1, 40), (2, 20)].into_iter().collect(),
}];
let expected: BTreeSet<usize> = [0, 1].into_iter().collect();
let loc = page_locality(&entries, &expected);
assert!((loc - 0.8).abs() < f64::EPSILON);
}
#[test]
fn page_locality_empty_entries() {
let expected: BTreeSet<usize> = [0].into_iter().collect();
let loc = page_locality(&[], &expected);
assert!((loc - 0.0).abs() < f64::EPSILON);
}
#[test]
fn page_locality_no_local_pages() {
let entries = vec![NumaMapsEntry {
addr: 0x1000,
node_pages: [(1, 50)].into_iter().collect(),
}];
let expected: BTreeSet<usize> = [0].into_iter().collect();
let loc = page_locality(&entries, &expected);
assert!((loc - 0.0).abs() < f64::EPSILON);
}
#[test]
fn page_locality_empty_expected_set() {
let entries = vec![NumaMapsEntry {
addr: 0x1000,
node_pages: [(0, 50)].into_iter().collect(),
}];
let loc = page_locality(&entries, &BTreeSet::new());
assert!((loc - 0.0).abs() < f64::EPSILON);
}
#[test]
fn assert_page_locality_pass() {
let r = assert_page_locality(0.9, Some(0.8), 100, 90);
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_page_locality_fail() {
let r = assert_page_locality(0.5, Some(0.8), 100, 50);
assert!(r.is_fail());
let detail = r
.failure_details()
.find(|d| matches!(d.kind, DetailKind::PageLocality))
.unwrap();
assert!(
detail.message.contains("50.00%"),
"must include observed %: {detail}"
);
assert!(
detail.message.contains("80.00%"),
"must include threshold %: {detail}"
);
}
#[test]
fn assert_page_locality_no_threshold() {
let r = assert_page_locality(0.1, None, 100, 10);
assert!(r.is_pass());
}
#[test]
fn assert_page_locality_exact_threshold() {
let r = assert_page_locality(0.8, Some(0.8), 100, 80);
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_page_locality_zero_observed_stays_fail_not_inconclusive() {
let r = assert_page_locality(0.0, Some(0.8), 0, 0);
assert!(
r.is_fail(),
"zero-page workload must Fail, not Pass/Inconclusive: {:?}",
r.outcomes
);
assert!(
!r.is_inconclusive(),
"zero-page workload must NOT be Inconclusive (page_locality is the deliberate stays-Fail counter-example): {:?}",
r.outcomes
);
let detail = r
.failure_details()
.find(|d| matches!(d.kind, DetailKind::PageLocality))
.expect("PageLocality detail required");
assert!(
detail.message.contains("0.00%"),
"must surface observed 0% so operator sees the zero-signal cause: {detail}"
);
}
#[test]
fn assert_slow_tier_ratio_pass() {
let mut pages = BTreeMap::new();
pages.insert(0, 90);
pages.insert(1, 10);
let nodes: BTreeSet<usize> = [0, 1].into_iter().collect();
let r = assert_slow_tier_ratio(&pages, 0.5, 100, Some(&nodes));
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_slow_tier_ratio_fail() {
let mut pages = BTreeMap::new();
pages.insert(0, 40);
pages.insert(2, 60);
let nodes: BTreeSet<usize> = [0].into_iter().collect();
let r = assert_slow_tier_ratio(&pages, 0.5, 100, Some(&nodes));
assert!(r.is_fail());
let detail = r
.failure_details()
.find(|d| matches!(d.kind, DetailKind::SlowTier))
.unwrap();
assert!(
detail.message.contains("60.00%"),
"must include observed %: {detail}"
);
assert!(
detail.message.contains("50.00%"),
"must include threshold %: {detail}"
);
}
#[test]
fn assert_slow_tier_ratio_none_numa_nodes() {
let mut pages = BTreeMap::new();
pages.insert(0, 100);
let r = assert_slow_tier_ratio(&pages, 0.1, 100, None);
assert!(r.is_pass());
}
#[test]
fn assert_slow_tier_ratio_zero_pages() {
let pages = BTreeMap::new();
let nodes: BTreeSet<usize> = [0].into_iter().collect();
let r = assert_slow_tier_ratio(&pages, 0.5, 0, Some(&nodes));
assert!(r.is_pass());
}
#[test]
fn assert_slow_tier_ratio_all_local() {
let mut pages = BTreeMap::new();
pages.insert(0, 100);
let nodes: BTreeSet<usize> = [0].into_iter().collect();
let r = assert_slow_tier_ratio(&pages, 0.0, 100, Some(&nodes));
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn plan_slow_tier_ratio_no_numa_signal_is_inconclusive() {
use super::AssertPlan;
use std::collections::BTreeSet;
let a = rpt(1, 1000, 1_000_000_000, 0, &[0], 0);
let b = rpt(2, 1000, 1_000_000_000, 0, &[0], 0);
let plan = AssertPlan {
not_starved: false,
isolation: false,
max_gap_ms: None,
max_spread_pct: None,
max_throughput_cv: None,
min_work_rate: None,
max_p99_wake_latency_ns: None,
max_wake_latency_cv: None,
min_iteration_rate: None,
max_migration_ratio: None,
min_page_locality: None,
max_cross_node_migration_ratio: None,
max_slow_tier_ratio: Some(0.1),
};
let nodes: BTreeSet<usize> = [0].into_iter().collect();
let r = plan.assert_cgroup(&[a, b], None, Some(&nodes));
assert!(
r.is_inconclusive(),
"no-NUMA-signal cgroup must be Inconclusive, not Pass: {:?}",
r.outcomes,
);
assert!(!r.is_pass(), "must not silently pass on zero denominator");
assert!(!r.is_fail(), "no actual ratio violation to report");
let reason = r
.inconclusive_details()
.find(|d| d.kind == DetailKind::SlowTier)
.unwrap_or_else(|| panic!("expected SlowTier Inconclusive, got {:?}", r.outcomes));
assert!(
reason.message.contains("no worker reported any NUMA pages"),
"diagnostic must name the root cause: {reason}"
);
assert!(
reason.message.contains("denominator is zero"),
"diagnostic must surface the operator-actionable hint: {reason}"
);
}
#[test]
fn assert_min_page_locality_setter() {
let v = Assert::NO_OVERRIDES.min_page_locality(0.9);
assert_eq!(v.min_page_locality, Some(0.9));
}
#[test]
fn assert_merge_numa_fields() {
let base = Assert::NO_OVERRIDES.min_page_locality(0.9);
let merged = base.merge(&Assert::NO_OVERRIDES);
assert_eq!(merged.min_page_locality, Some(0.9));
}
#[test]
fn assert_merge_numa_override() {
let base = Assert::NO_OVERRIDES.min_page_locality(0.9);
let other = Assert::NO_OVERRIDES.min_page_locality(0.5);
assert_eq!(base.merge(&other).min_page_locality, Some(0.5));
}
#[test]
fn assert_numa_has_worker_checks() {
assert!(
Assert::NO_OVERRIDES
.min_page_locality(0.8)
.has_worker_checks()
);
}
#[test]
fn assert_page_locality_method_pass() {
let a = Assert::NO_OVERRIDES.min_page_locality(0.8);
let r = a.assert_page_locality(0.9, 100, 90);
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_page_locality_method_fail() {
let a = Assert::NO_OVERRIDES.min_page_locality(0.95);
let r = a.assert_page_locality(0.8, 100, 80);
assert!(r.is_fail());
}
#[test]
fn assert_result_merge_numa_worst_page_locality() {
let mut a = AssertResult::pass();
a.stats.worst_page_locality = 0.9;
let mut b = AssertResult::pass();
b.stats.worst_page_locality = 0.7;
a.merge(b);
assert!((a.stats.worst_page_locality - 0.7).abs() < f64::EPSILON);
}
#[test]
fn assert_result_merge_numa_zero_locality_ignored() {
let mut a = AssertResult::pass();
a.stats.worst_page_locality = 0.9;
let b = AssertResult::pass();
a.merge(b);
assert!((a.stats.worst_page_locality - 0.9).abs() < f64::EPSILON);
}
#[test]
fn cgroup_stats_numa_defaults() {
let c = CgroupStats::default();
assert_eq!(c.page_locality, 0.0);
assert_eq!(c.cross_node_migration_ratio, 0.0);
}
#[test]
fn scenario_stats_numa_defaults() {
let s = ScenarioStats::default();
assert_eq!(s.worst_page_locality, 0.0);
assert_eq!(s.worst_cross_node_migration_ratio, 0.0);
}
#[test]
fn parse_vmstat_present() {
let content = "\
nr_free_pages 12345
numa_hit 100
numa_pages_migrated 42
numa_miss 5";
assert_eq!(parse_vmstat_numa_pages_migrated(content), Some(42));
}
#[test]
fn parse_vmstat_absent() {
let content = "nr_free_pages 12345\nnuma_hit 100";
assert_eq!(parse_vmstat_numa_pages_migrated(content), None);
}
#[test]
fn parse_vmstat_zero() {
let content = "numa_pages_migrated 0";
assert_eq!(parse_vmstat_numa_pages_migrated(content), Some(0));
}
#[test]
fn parse_vmstat_large_value() {
let content = "numa_pages_migrated 9999999999";
assert_eq!(parse_vmstat_numa_pages_migrated(content), Some(9999999999));
}
#[test]
fn parse_vmstat_empty() {
assert_eq!(parse_vmstat_numa_pages_migrated(""), None);
}
#[test]
fn parse_vmstat_malformed_value() {
let content = "numa_pages_migrated abc";
assert_eq!(parse_vmstat_numa_pages_migrated(content), None);
}
#[test]
fn assert_cross_node_migration_pass() {
let r = assert_cross_node_migration(5, 100, Some(0.1));
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_cross_node_migration_fail() {
let r = assert_cross_node_migration(20, 100, Some(0.1));
assert!(r.is_fail());
let detail = r
.failure_details()
.find(|d| matches!(d.kind, DetailKind::CrossNodeMigration))
.unwrap();
assert!(
detail.message.contains("20.00%"),
"must include observed %: {detail}"
);
assert!(
detail.message.contains("10.00%"),
"must include threshold %: {detail}"
);
}
#[test]
fn assert_cross_node_migration_no_threshold() {
let r = assert_cross_node_migration(50, 100, None);
assert!(r.is_pass());
}
#[test]
fn assert_cross_node_migration_exact_threshold() {
let r = assert_cross_node_migration(10, 100, Some(0.1));
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_cross_node_migration_zero_pages_is_inconclusive() {
let r = assert_cross_node_migration(0, 0, Some(0.1));
assert!(
r.is_inconclusive(),
"zero total + zero migrated must be Inconclusive, not Pass or Fail: {:?}",
r.outcomes,
);
assert!(!r.is_pass(), "must not silently pass on zero denominator");
assert!(!r.is_fail(), "no ratio violation to report");
let reason = r
.inconclusive_details()
.find(|d| d.kind == DetailKind::CrossNodeMigration)
.unwrap_or_else(|| panic!("expected Inconclusive reason, got {:?}", r.outcomes));
assert!(
reason.message.contains("denominator is zero"),
"diagnostic must name the root cause: {reason}"
);
assert!(
reason.message.contains("allocate any memory"),
"diagnostic must surface the operator-actionable hint: {reason}"
);
}
#[test]
fn assert_cross_node_migration_inconsistent_zero_total_nonzero_migrated() {
let r = assert_cross_node_migration(5, 0, Some(0.1));
assert!(!r.is_pass(), "inconsistent input must fail loudly");
let detail = r
.failure_details()
.find(|d| d.message.contains("inconsistent"))
.unwrap_or_else(|| panic!("expected inconsistent diagnostic, got {:?}", r.outcomes));
assert!(
detail.message.contains("5 pages migrated"),
"must surface migrated count: {detail}"
);
assert!(
detail.message.contains("0 pages observed"),
"must surface total=0: {detail}"
);
}
#[test]
fn assert_max_cross_node_migration_ratio_setter() {
let v = Assert::NO_OVERRIDES.max_cross_node_migration_ratio(0.05);
assert_eq!(v.max_cross_node_migration_ratio, Some(0.05));
}
#[test]
fn assert_merge_cross_node_migration() {
let base = Assert::NO_OVERRIDES.max_cross_node_migration_ratio(0.1);
let other = Assert::NO_OVERRIDES.max_cross_node_migration_ratio(0.05);
assert_eq!(
base.merge(&other).max_cross_node_migration_ratio,
Some(0.05)
);
}
#[test]
fn assert_merge_cross_node_migration_preserves() {
let base = Assert::NO_OVERRIDES.max_cross_node_migration_ratio(0.1);
assert_eq!(
base.merge(&Assert::NO_OVERRIDES)
.max_cross_node_migration_ratio,
Some(0.1)
);
}
#[test]
fn assert_cross_node_migration_has_worker_checks() {
assert!(
Assert::NO_OVERRIDES
.max_cross_node_migration_ratio(0.1)
.has_worker_checks()
);
}
#[test]
fn assert_cross_node_migration_method_pass() {
let a = Assert::NO_OVERRIDES.max_cross_node_migration_ratio(0.1);
let r = a.assert_cross_node_migration(5, 100);
assert!(r.is_pass(), "{:?}", r.outcomes);
}
#[test]
fn assert_cross_node_migration_method_fail() {
let a = Assert::NO_OVERRIDES.max_cross_node_migration_ratio(0.05);
let r = a.assert_cross_node_migration(20, 100);
assert!(r.is_fail());
}
#[test]
fn assert_result_merge_worst_cross_node_migration() {
let mut a = AssertResult::pass();
a.stats.worst_cross_node_migration_ratio = 0.05;
let mut b = AssertResult::pass();
b.stats.worst_cross_node_migration_ratio = 0.15;
a.merge(b);
assert!((a.stats.worst_cross_node_migration_ratio - 0.15).abs() < f64::EPSILON);
}
#[test]
fn plan_cross_node_migration_aggregates_cgroup_total() {
let mut a = rpt(1, 1000, 1_000_000_000, 0, &[0], 0);
let mut b = rpt(2, 1000, 1_000_000_000, 0, &[1], 0);
a.numa_pages = [(0, 50), (1, 50)].into_iter().collect();
b.numa_pages = [(0, 50), (1, 50)].into_iter().collect();
a.vmstat_numa_pages_migrated = 5;
b.vmstat_numa_pages_migrated = 5;
let plan = AssertPlan {
not_starved: false,
isolation: false,
max_gap_ms: None,
max_spread_pct: None,
max_throughput_cv: None,
min_work_rate: None,
max_p99_wake_latency_ns: None,
max_wake_latency_cv: None,
min_iteration_rate: None,
max_migration_ratio: None,
min_page_locality: None,
max_cross_node_migration_ratio: Some(0.03),
max_slow_tier_ratio: None,
};
let r = plan.assert_cgroup(&[a, b], None, None);
assert!(
r.is_pass(),
"0.025 < 0.03 must pass under aggregated calc; per-worker would have failed at 0.05: {:?}",
r.outcomes
);
}
#[test]
fn plan_cross_node_migration_emits_one_failure_not_per_worker() {
let mut a = rpt(1, 1000, 1_000_000_000, 0, &[0], 0);
let mut b = rpt(2, 1000, 1_000_000_000, 0, &[1], 0);
a.numa_pages = [(0, 50)].into_iter().collect();
b.numa_pages = [(0, 50)].into_iter().collect();
a.vmstat_numa_pages_migrated = 50;
b.vmstat_numa_pages_migrated = 50;
let plan = AssertPlan {
not_starved: false,
isolation: false,
max_gap_ms: None,
max_spread_pct: None,
max_throughput_cv: None,
min_work_rate: None,
max_p99_wake_latency_ns: None,
max_wake_latency_cv: None,
min_iteration_rate: None,
max_migration_ratio: None,
min_page_locality: None,
max_cross_node_migration_ratio: Some(0.1),
max_slow_tier_ratio: None,
};
let r = plan.assert_cgroup(&[a, b], None, None);
assert!(r.is_fail());
let cross_node_failures = r
.failure_details()
.filter(|d| matches!(d.kind, DetailKind::CrossNodeMigration))
.count();
assert_eq!(
cross_node_failures, 1,
"exactly one cross-node migration failure for the cgroup (not per-worker): {:?}",
r.outcomes
);
}
#[test]
fn plan_min_page_locality_fails_on_zero_allocation_cgroup() {
let a = rpt(1, 1000, 1_000_000_000, 0, &[0], 0);
let b = rpt(2, 1000, 1_000_000_000, 0, &[0], 0);
let plan = AssertPlan {
not_starved: false,
isolation: false,
max_gap_ms: None,
max_spread_pct: None,
max_throughput_cv: None,
min_work_rate: None,
max_p99_wake_latency_ns: None,
max_wake_latency_cv: None,
min_iteration_rate: None,
max_migration_ratio: None,
min_page_locality: Some(0.8),
max_cross_node_migration_ratio: None,
max_slow_tier_ratio: None,
};
let nodes: BTreeSet<usize> = [0].into_iter().collect();
let r = plan.assert_cgroup(&[a, b], None, Some(&nodes));
assert!(
!r.is_pass(),
"zero-allocation cgroup must fail min_page_locality, not silently pass: {:?}",
r.outcomes
);
assert!(
r.failure_details()
.any(|d| matches!(d.kind, DetailKind::PageLocality)),
"must surface a PageLocality detail: {:?}",
r.outcomes
);
}
#[test]
fn plan_min_page_locality_aggregates_across_cgroup() {
let mut a = rpt(1, 1000, 1_000_000_000, 0, &[0], 0);
let mut b = rpt(2, 1000, 1_000_000_000, 0, &[1], 0);
a.numa_pages = [(0, 100)].into_iter().collect();
b.numa_pages = [(1, 100)].into_iter().collect();
let plan = AssertPlan {
not_starved: false,
isolation: false,
max_gap_ms: None,
max_spread_pct: None,
max_throughput_cv: None,
min_work_rate: None,
max_p99_wake_latency_ns: None,
max_wake_latency_cv: None,
min_iteration_rate: None,
max_migration_ratio: None,
min_page_locality: Some(0.8),
max_cross_node_migration_ratio: None,
max_slow_tier_ratio: None,
};
let nodes: BTreeSet<usize> = [0].into_iter().collect();
let r = plan.assert_cgroup(&[a, b], None, Some(&nodes));
assert!(
!r.is_pass(),
"cgroup-aggregate locality 0.5 < 0.8 must fail"
);
}