use super::super::*;
use super::*;
#[test]
fn cpu_cap_new_accepts_one() {
let cap = CpuCap::new(1).expect("cap of 1 must succeed");
assert_eq!(cap.effective_count(4).unwrap(), 1);
}
#[test]
fn cpu_cap_new_accepts_usize_max() {
let cap = CpuCap::new(usize::MAX).expect("MAX accepted at construction");
assert!(cap.effective_count(usize::MAX).is_ok());
}
#[test]
fn cpu_cap_effective_count_fits() {
let cap = CpuCap::new(3).unwrap();
assert_eq!(cap.effective_count(4).unwrap(), 3);
assert_eq!(cap.effective_count(3).unwrap(), 3);
}
#[test]
fn cpu_cap_effective_count_exceeds_host() {
let cap = CpuCap::new(8).unwrap();
let err = cap.effective_count(4).expect_err("8 > 4 must error");
let msg = format!("{err:#}");
assert!(msg.contains("8"), "msg must name requested cap: {msg}");
assert!(msg.contains("4"), "msg must name allowed-CPU count: {msg}");
assert!(
err.downcast_ref::<ResourceContention>().is_some(),
"must be a ResourceContention for retry routing: {msg}",
);
}
#[test]
fn cpu_cap_effective_count_at_host_boundary() {
let cap = CpuCap::new(4).unwrap();
assert_eq!(cap.effective_count(4).unwrap(), 4);
}
#[test]
fn cpu_cap_resolve_cli_wins_over_env() {
let _lock = env_lock();
let _env = EnvGuard::set(crate::KTSTR_CPU_CAP_ENV, "99");
let cap = CpuCap::resolve(Some(3)).unwrap().expect("CLI flag set");
assert_eq!(cap.effective_count(4).unwrap(), 3, "CLI wins");
}
#[test]
fn cpu_cap_resolve_no_cli_no_env_returns_none() {
let _lock = env_lock();
let _env = EnvGuard::remove(crate::KTSTR_CPU_CAP_ENV);
assert!(CpuCap::resolve(None).unwrap().is_none());
}
#[test]
fn cpu_cap_resolve_env_set() {
let _lock = env_lock();
let _env = EnvGuard::set(crate::KTSTR_CPU_CAP_ENV, "2");
let cap = CpuCap::resolve(None)
.expect("resolve must succeed")
.expect("env-set cap must yield Some");
assert_eq!(cap.effective_count(8).unwrap(), 2);
}
#[test]
fn cpu_cap_resolve_empty_env_is_absent() {
let _lock = env_lock();
let _env = EnvGuard::set(crate::KTSTR_CPU_CAP_ENV, "");
assert!(CpuCap::resolve(None).unwrap().is_none());
}
#[test]
fn cpu_cap_resolve_non_numeric_env_errors() {
let _lock = env_lock();
let _env = EnvGuard::set(crate::KTSTR_CPU_CAP_ENV, "not-a-number");
let err = CpuCap::resolve(None).expect_err("non-numeric must error");
let msg = format!("{err:#}");
assert!(msg.contains(crate::KTSTR_CPU_CAP_ENV), "msg={msg}");
}
#[test]
fn cpu_cap_resolve_zero_env_rejected() {
let _lock = env_lock();
let _env = EnvGuard::set(crate::KTSTR_CPU_CAP_ENV, "0");
let err = CpuCap::resolve(None).expect_err("zero must error");
let msg = format!("{err:#}");
assert!(msg.contains("≥ 1"), "msg={msg}");
assert!(msg.contains("got 0"), "msg={msg}");
}
#[test]
fn cpu_cap_resolve_zero_cli_rejected_even_with_valid_env() {
let _lock = env_lock();
let _env = EnvGuard::set(crate::KTSTR_CPU_CAP_ENV, "2");
let err = CpuCap::resolve(Some(0)).expect_err("cli=0 must error");
let msg = format!("{err:#}");
assert!(msg.contains("≥ 1"), "msg={msg}");
}
#[test]
fn env_guard_set_and_drop_removes_variable() {
let _lock = env_lock();
let probe = "KTSTR_CPU_CAP_ENV_GUARD_TEST";
{
let _env = EnvGuard::set(probe, "abc");
assert_eq!(
std::env::var(probe).ok().as_deref(),
Some("abc"),
"set must apply immediately",
);
}
assert!(
std::env::var(probe).is_err(),
"EnvGuard::drop must remove the variable",
);
}
#[test]
fn host_llcs_by_numa_node_single_node() {
let topo = synth_host_topo(&[(vec![0, 1], 0), (vec![2, 3], 0), (vec![4, 5], 0)]);
let map = topo.host_llcs_by_numa_node();
assert_eq!(map.len(), 1, "single-node host has one entry");
assert_eq!(map.get(&0), Some(&vec![0, 1, 2]));
}
#[test]
fn host_llcs_by_numa_node_dual_node() {
let topo = synth_host_topo(&[
(vec![0, 1], 0),
(vec![2, 3], 1),
(vec![4, 5], 0),
(vec![6, 7], 1),
]);
let map = topo.host_llcs_by_numa_node();
assert_eq!(map.len(), 2);
assert_eq!(map.get(&0), Some(&vec![0, 2]));
assert_eq!(map.get(&1), Some(&vec![1, 3]));
}
#[test]
fn numa_nodes_with_capacity_asymmetric() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0), (vec![2], 0), (vec![3], 1)]);
let cap2: Vec<usize> = topo
.numa_nodes_with_capacity(2)
.into_iter()
.map(|(node, _)| node)
.collect();
assert_eq!(cap2, vec![0], "only node 0 has ≥ 2 LLCs");
let cap1: Vec<usize> = topo
.numa_nodes_with_capacity(1)
.into_iter()
.map(|(node, _)| node)
.collect();
assert_eq!(cap1, vec![0, 1], "both nodes have ≥ 1 LLC");
}
#[test]
fn numa_nodes_with_capacity_over_max_returns_empty() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 1)]);
assert!(topo.numa_nodes_with_capacity(99).is_empty());
}
#[test]
fn numa_nodes_sorted_by_distance_identity_closure() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 1), (vec![2], 2)]);
let order = topo.numa_nodes_sorted_by_distance(1, |from, to| if from == to { 10 } else { 20 });
assert_eq!(order[0], 1, "anchor node first");
assert_eq!(
&order[1..],
&[0, 2],
"tied-distance nodes in ascending order"
);
}
#[test]
fn numa_nodes_sorted_by_distance_unreachable_demoted() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 1), (vec![2], 2)]);
let order = topo.numa_nodes_sorted_by_distance(0, |from, to| match (from, to) {
(0, 0) => 10,
(0, 1) => 20,
(0, 2) => 255,
_ => 20,
});
assert_eq!(order, vec![0, 1, 2]);
assert_eq!(*order.last().unwrap(), 2, "unreachable node is last");
}
#[test]
fn numa_nodes_sorted_by_distance_skips_empty_nodes() {
let topo = synth_host_topo(&[(vec![0], 0)]);
let order = topo.numa_nodes_sorted_by_distance(99, |_, _| 20);
assert_eq!(order, vec![0], "only node 0 is in host_node_llcs");
}
#[test]
fn acquire_llc_plan_rejects_cap_over_allowed_cpus() {
let _allowed = AllowedCpusGuard::new(vec![0, 1]);
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0)]);
let test_topo = crate::topology::TestTopology::synthetic(4, 1);
let cap = CpuCap::new(3).unwrap();
let err =
acquire_llc_plan(&topo, &test_topo, Some(cap)).expect_err("cap > allowed_cpus must error");
assert!(
err.downcast_ref::<ResourceContention>().is_some(),
"must be ResourceContention: {err:#}"
);
}
#[test]
fn plan_from_snapshots_returns_ascending_indices() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0), (vec![2], 0), (vec![3], 0)]);
let snapshots: Vec<LlcSnapshot> = (0..4)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: if idx >= 2 { 5 } else { 0 },
})
.collect();
let allowed: std::collections::BTreeSet<usize> = (0..4).collect();
let selected = plan_from_snapshots(
&snapshots,
3,
&topo,
&allowed,
|_, _| 10, );
assert_eq!(selected, vec![0, 2, 3], "step e sorts ascending");
}
#[test]
fn plan_from_snapshots_target_ge_all_selects_every_llc() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 1), (vec![2], 2)]);
let snapshots: Vec<LlcSnapshot> = (0..3)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: 0,
})
.collect();
let allowed: std::collections::BTreeSet<usize> = (0..3).collect();
let selected = plan_from_snapshots(&snapshots, 3, &topo, &allowed, |_, _| 10);
assert_eq!(selected, vec![0, 1, 2]);
let selected_over = plan_from_snapshots(&snapshots, 999, &topo, &allowed, |_, _| 10);
assert_eq!(selected_over, vec![0, 1, 2], "target > len clamps");
}
#[test]
fn plan_from_snapshots_target_zero_returns_empty() {
let topo = synth_host_topo(&[(vec![0], 0)]);
let snapshots: Vec<LlcSnapshot> = vec![LlcSnapshot {
llc_idx: 0,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-0.lock"),
holders: Vec::new(),
holder_count: 0,
}];
let allowed: std::collections::BTreeSet<usize> = [0].into_iter().collect();
let selected = plan_from_snapshots(&snapshots, 0, &topo, &allowed, |_, _| 10);
assert!(selected.is_empty());
}
#[test]
fn plan_from_snapshots_prefers_higher_holder_count() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0)]);
let snapshots: Vec<LlcSnapshot> = vec![
LlcSnapshot {
llc_idx: 0,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-0.lock"),
holders: Vec::new(),
holder_count: 0,
},
LlcSnapshot {
llc_idx: 1,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-1.lock"),
holders: Vec::new(),
holder_count: 5,
},
];
let allowed: std::collections::BTreeSet<usize> = (0..2).collect();
let selected = plan_from_snapshots(&snapshots, 1, &topo, &allowed, |_, _| 10);
assert_eq!(
selected,
vec![1],
"target=1 with holders [0,5] must pick LLC 1 \
(consolidation preference), not LLC 0 (fresh)"
);
}
#[test]
fn plan_from_snapshots_always_ascending_across_target_range() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 1), (vec![2], 0), (vec![3], 1)]);
let snapshots: Vec<LlcSnapshot> = vec![
LlcSnapshot {
llc_idx: 0,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-0.lock"),
holders: Vec::new(),
holder_count: 3,
},
LlcSnapshot {
llc_idx: 1,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-1.lock"),
holders: Vec::new(),
holder_count: 0,
},
LlcSnapshot {
llc_idx: 2,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-2.lock"),
holders: Vec::new(),
holder_count: 7,
},
LlcSnapshot {
llc_idx: 3,
lockfile_path: std::path::PathBuf::from("/tmp/ktstr-llc-3.lock"),
holders: Vec::new(),
holder_count: 1,
},
];
let allowed: std::collections::BTreeSet<usize> = (0..4).collect();
for target_cpus in 1..=snapshots.len() {
let selected = plan_from_snapshots(&snapshots, target_cpus, &topo, &allowed, |_, _| 10);
assert_eq!(
selected.len(),
target_cpus,
"target_cpus={target_cpus} must produce {target_cpus} selections, got {selected:?}"
);
assert!(
selected.windows(2).all(|w| w[0] < w[1]),
"target_cpus={target_cpus}: selection {selected:?} is not strictly ascending",
);
}
}
#[test]
fn make_jobs_for_plan_matches_cpu_count() {
let plan = LlcPlan {
locked_llcs: vec![0, 1],
cpus: vec![0, 1, 2, 3],
mems: std::collections::BTreeSet::new(),
snapshot: Vec::new(),
locks: Vec::new(),
};
assert_eq!(make_jobs_for_plan(&plan), 4);
}
#[test]
fn make_jobs_for_plan_empty_cpus_floors_to_one() {
let plan = LlcPlan {
locked_llcs: Vec::new(),
cpus: Vec::new(),
mems: std::collections::BTreeSet::new(),
snapshot: Vec::new(),
locks: Vec::new(),
};
assert_eq!(
make_jobs_for_plan(&plan),
1,
"empty-cpus must floor to 1, not 0 — -j0 is unbounded",
);
}
#[test]
fn format_llc_list_with_numa_info() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0), (vec![2], 1), (vec![3], 1)]);
let rendered = format_llc_list(&[0, 2], &topo);
assert!(
rendered.contains("0 (node 0)"),
"must annotate LLC 0 with its node: {rendered}",
);
assert!(
rendered.contains("2 (node 1)"),
"must annotate LLC 2 with its node: {rendered}",
);
assert_eq!(rendered, "[0 (node 0), 2 (node 1)]");
}
#[test]
fn format_llc_list_single_llc() {
let topo = synth_host_topo(&[(vec![0], 0)]);
let rendered = format_llc_list(&[0], &topo);
assert_eq!(rendered, "[0 (node 0)]");
}
#[test]
fn format_llc_list_without_numa_info() {
let mut topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0)]);
topo.cpu_to_node.clear();
let rendered = format_llc_list(&[0, 1], &topo);
assert_eq!(
rendered, "[0, 1]",
"degraded-host form drops node annotation"
);
}
#[test]
fn should_warn_cross_node_polarity() {
use std::collections::BTreeSet;
let empty: BTreeSet<usize> = BTreeSet::new();
assert!(
!should_warn_cross_node(&empty),
"empty mems must NOT warn (degenerate plan with no NUMA info)",
);
let single: BTreeSet<usize> = [0].into_iter().collect();
assert!(
!should_warn_cross_node(&single),
"single-node plan must NOT warn — the whole point of the cap \
is to fit on one node when possible",
);
let dual: BTreeSet<usize> = [0, 1].into_iter().collect();
assert!(
should_warn_cross_node(&dual),
"two-node plan MUST warn — operator picked a cap that \
couldn't fit on one node and deserves to hear about it",
);
let triple: BTreeSet<usize> = [0, 1, 2].into_iter().collect();
assert!(
should_warn_cross_node(&triple),
"three-node plan MUST warn — same rationale as dual",
);
}
#[test]
fn warn_if_cross_node_spill_predicate_gates_stderr() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 1)]);
let multi_plan = LlcPlan {
locked_llcs: vec![0, 1],
cpus: vec![0, 1],
mems: [0usize, 1].into_iter().collect(),
snapshot: Vec::new(),
locks: Vec::new(),
};
assert!(should_warn_cross_node(&multi_plan.mems));
warn_if_cross_node_spill(&multi_plan, &topo);
let single_plan = LlcPlan {
locked_llcs: vec![0],
cpus: vec![0],
mems: [0usize].into_iter().collect(),
snapshot: Vec::new(),
locks: Vec::new(),
};
assert!(!should_warn_cross_node(&single_plan.mems));
warn_if_cross_node_spill(&single_plan, &topo);
}
#[test]
fn cpu_cap_effective_count_on_zero_llc_host() {
let cap = CpuCap::new(1).unwrap();
let err = cap.effective_count(0).expect_err("1 > 0 must error");
assert!(
err.downcast_ref::<ResourceContention>().is_some(),
"must be ResourceContention for retry routing",
);
}
#[test]
fn acquire_llc_plan_consolidates_on_peer_held_llc() {
let _llc_prefix = LlcLockPrefixGuard::new();
let topo = HostTopology::new_for_tests(&[(vec![0], 0), (vec![1], 0)]);
let target_lock = llc_lock_path(1);
crate::flock::materialize(&target_lock).expect("materialize lockfile");
let child = std::process::Command::new("flock")
.args(["-s", "-n", &target_lock, "sleep", "2"])
.spawn();
let mut child = match child {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
eprintln!(
"acquire_llc_plan_consolidates_on_peer_held_llc: \
flock(1) not available, skipping ({e})"
);
return;
}
Err(e) => panic!("spawn flock(1): {e}"),
};
std::thread::sleep(std::time::Duration::from_millis(50));
let test_topo = crate::topology::TestTopology::synthetic(2, 1);
let cap = CpuCap::new(1).expect("cap=1 valid");
let plan = acquire_llc_plan(&topo, &test_topo, Some(cap))
.expect("SH is reentrant — parent SH must coexist with child SH");
assert_eq!(
plan.locked_llcs,
vec![1],
"cap=1 with child holding SH on LLC 1 must pick LLC 1 \
(consolidation over fresh LLC 0); got {:?}",
plan.locked_llcs,
);
drop(plan);
let _ = child.wait();
}
#[test]
fn acquire_max_toctou_retries_pinned() {
assert_eq!(
ACQUIRE_MAX_TOCTOU_RETRIES, 3,
"retry budget must be 3 — micro-sleeps absorb mid-sized races",
);
assert_eq!(
TOCTOU_RETRY_DELAYS.len(),
ACQUIRE_MAX_TOCTOU_RETRIES as usize,
"one sleep per retry — TOCTOU_RETRY_DELAYS length must \
match ACQUIRE_MAX_TOCTOU_RETRIES exactly",
);
}
#[test]
fn acquire_llc_plan_retry_succeeds_on_attempt_one() {
let _llc_prefix = LlcLockPrefixGuard::new();
let _allowed = AllowedCpusGuard::new(vec![93500, 93501]);
let topo = synth_host_topo(&[(vec![93500], 0), (vec![93501], 0)]);
let test_topo = crate::topology::TestTopology::synthetic(2, 1);
let counter = std::cell::Cell::new(0u32);
let plan =
acquire_llc_plan_with_acquire_fn(&topo, &test_topo, None, |_selected, _snapshots| {
let n = counter.get();
counter.set(n + 1);
if n == 0 {
Ok(None)
} else {
Ok(Some(Vec::new()))
}
})
.expect("retry on attempt 1 must succeed");
assert_eq!(counter.get(), 2, "acquire_fn called exactly twice");
assert_eq!(plan.locked_llcs, vec![0]);
}
#[test]
fn acquire_llc_plan_retry_exhausted_bails_with_resource_contention() {
let _llc_prefix = LlcLockPrefixGuard::new();
let _allowed = AllowedCpusGuard::new(vec![93600]);
let topo = synth_host_topo(&[(vec![93600], 0)]);
let test_topo = crate::topology::TestTopology::synthetic(1, 1);
let counter = std::cell::Cell::new(0u32);
let err = acquire_llc_plan_with_acquire_fn(&topo, &test_topo, None, |_selected, _snapshots| {
counter.set(counter.get() + 1);
Ok(None)
})
.expect_err("every attempt returns None — must bail after retries");
assert_eq!(
counter.get(),
ACQUIRE_MAX_TOCTOU_RETRIES + 1,
"acquire_fn called exactly ACQUIRE_MAX_TOCTOU_RETRIES + 1 times",
);
assert!(
err.downcast_ref::<ResourceContention>().is_some(),
"must downcast to ResourceContention for retry routing: {err:#}",
);
let msg = format!("{err:#}");
assert!(
msg.contains("attempts"),
"message must name the attempt count: {msg}",
);
}
#[test]
fn plan_from_snapshots_consolidation_overrides_fresh_ordering() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0), (vec![2], 0), (vec![3], 0)]);
let snapshots: Vec<LlcSnapshot> = (0..4)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: if idx == 3 { 5 } else { 0 },
})
.collect();
let allowed: std::collections::BTreeSet<usize> = (0..4).collect();
let selected = plan_from_snapshots(&snapshots, 1, &topo, &allowed, |_, _| 10);
assert_eq!(
selected,
vec![3],
"target=1 with peer-held LLC 3 must pick LLC 3, not the \
lowest-index fresh LLC 0 — consolidation overrides fresh",
);
}
#[test]
fn plan_from_snapshots_single_node_fit_no_spill() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0), (vec![2], 1), (vec![3], 1)]);
let snapshots: Vec<LlcSnapshot> = (0..4)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: 0,
})
.collect();
let allowed: std::collections::BTreeSet<usize> = (0..4).collect();
let selected = plan_from_snapshots(&snapshots, 2, &topo, &allowed, |from, to| {
if from == to { 10 } else { 20 }
});
assert_eq!(
selected,
vec![0, 1],
"target=2 must stay on seed node 0 (LLCs 0,1); seed-node \
capacity (2) covers the request, no spill to node 1 allowed",
);
}
#[test]
fn plan_from_snapshots_equal_scores_tiebreak_ascending() {
let topo = synth_host_topo(&[(vec![0], 0), (vec![1], 0), (vec![2], 0), (vec![3], 0)]);
let snapshots: Vec<LlcSnapshot> = (0..4)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: 5,
})
.collect();
let allowed: std::collections::BTreeSet<usize> = (0..4).collect();
let selected = plan_from_snapshots(&snapshots, 2, &topo, &allowed, |_, _| 10);
assert_eq!(
selected,
vec![0, 1],
"equal consolidation scores must tiebreak on llc_idx ASC \
— selected={selected:?}",
);
}
#[test]
fn default_cpu_budget_30_percent_rounded_up_min_one() {
assert_eq!(default_cpu_budget(0), 1, "min-1 floor");
assert_eq!(default_cpu_budget(1), 1, "ceil(0.3) = 1");
assert_eq!(default_cpu_budget(3), 1, "ceil(0.9) = 1");
assert_eq!(default_cpu_budget(4), 2, "ceil(1.2) = 2");
assert_eq!(default_cpu_budget(10), 3, "ceil(3.0) = 3");
assert_eq!(default_cpu_budget(100), 30, "exact 30%");
}
#[test]
fn acquire_llc_plan_bails_when_no_llc_overlaps_allowed() {
let _llc_prefix = LlcLockPrefixGuard::new();
let _allowed = AllowedCpusGuard::new(vec![100, 101]);
let topo = HostTopology::new_for_tests(&[(vec![0], 0), (vec![1], 0)]);
let test_topo = crate::topology::TestTopology::synthetic(4, 1);
let err = acquire_llc_plan(&topo, &test_topo, None)
.expect_err("no LLC overlap must bail, not silently run");
let msg = format!("{err:#}");
assert!(
msg.contains("no host LLC overlaps"),
"err must name the no-overlap condition: {msg}"
);
}
#[test]
fn plan_from_snapshots_filters_llcs_outside_allowed_set() {
let topo = synth_host_topo(&[
(vec![0, 1], 0),
(vec![2, 3], 0),
(vec![4, 5], 0),
(vec![6, 7], 0),
]);
let snapshots: Vec<LlcSnapshot> = (0..4)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: 0,
})
.collect();
let allowed: std::collections::BTreeSet<usize> = [0, 1, 4, 5].into_iter().collect();
let selected = plan_from_snapshots(&snapshots, 3, &topo, &allowed, |_, _| 10);
assert_eq!(
selected,
vec![0, 2],
"planner must skip LLCs 1 and 3 (no allowed-CPU overlap) \
and pick LLCs 0 and 2 whose CPUs are fully in allowed; \
got {selected:?}"
);
}
#[test]
fn acquire_llc_plan_partial_take_last_llc_matches_exact_budget() {
let _llc_prefix = LlcLockPrefixGuard::new();
let _allowed = AllowedCpusGuard::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let topo = HostTopology::new_for_tests(&[(vec![0, 1, 2, 3], 0), (vec![4, 5, 6, 7], 0)]);
let test_topo = crate::topology::TestTopology::synthetic(4, 1);
let cap = CpuCap::new(5).expect("cap=5 valid");
let plan = acquire_llc_plan(&topo, &test_topo, Some(cap))
.expect("clean pool must allow SH on both LLCs");
assert_eq!(
plan.locked_llcs,
vec![0, 1],
"budget of 5 CPUs crosses LLC boundary — both must be flocked"
);
assert_eq!(
plan.cpus.len(),
5,
"plan.cpus is EXACTLY the budget, not rounded up: {:?}",
plan.cpus,
);
assert_eq!(plan.cpus, vec![0, 1, 2, 3, 4]);
}
#[test]
fn plan_from_snapshots_partial_llc_overlap_counted_correctly() {
let topo = synth_host_topo(&[(vec![0, 1], 0), (vec![2, 3], 0)]);
let snapshots: Vec<LlcSnapshot> = (0..2)
.map(|idx| LlcSnapshot {
llc_idx: idx,
lockfile_path: std::path::PathBuf::from(format!("/tmp/ktstr-llc-{idx}.lock")),
holders: Vec::new(),
holder_count: 0,
})
.collect();
let allowed: std::collections::BTreeSet<usize> = [0, 2].into_iter().collect();
let selected = plan_from_snapshots(&snapshots, 2, &topo, &allowed, |_, _| 10);
assert_eq!(
selected,
vec![0, 1],
"target_cpus=2 with 1 allowed CPU per LLC must pick \
BOTH LLCs — each contributes 1, total 2 meets budget"
);
}
#[test]
fn acquire_llc_plan_cross_node_spill_mems_union() {
let _llc_prefix = LlcLockPrefixGuard::new();
let _allowed = AllowedCpusGuard::new(vec![0, 1, 2, 3]);
let topo =
HostTopology::new_for_tests(&[(vec![0], 0), (vec![1], 0), (vec![2], 1), (vec![3], 1)]);
let test_topo = crate::topology::TestTopology::synthetic(4, 2);
let cap = CpuCap::new(3).expect("cap=3 valid");
let plan = acquire_llc_plan(&topo, &test_topo, Some(cap))
.expect("clean pool must allow 3-CPU acquisition");
assert_eq!(
plan.locked_llcs.len(),
3,
"cap=3 CPUs with 1-CPU LLCs must reserve exactly 3 LLCs, got {:?}",
plan.locked_llcs,
);
assert_eq!(
plan.mems.len(),
2,
"3 LLCs split across 2 nodes → mems must span BOTH nodes; \
got {:?} (locked_llcs={:?})",
plan.mems,
plan.locked_llcs,
);
assert!(
plan.mems.contains(&0) && plan.mems.contains(&1),
"mems must contain BOTH node 0 and node 1 after cross-node \
spill; got {:?}",
plan.mems,
);
}
#[test]
fn acquire_resource_locks_cargo_test_mode_bypasses_flock() {
use crate::test_support::test_helpers::{EnvVarGuard, lock_env};
let _lock = lock_env();
let _env = EnvVarGuard::set(crate::KTSTR_CARGO_TEST_MODE_ENV, "1");
let plan = PinningPlan {
assignments: vec![(0, 95100)],
service_cpu: None,
llc_indices: vec![95100],
locks: Vec::new(),
};
let outcome = acquire_resource_locks(&plan, &[95100usize], LlcLockMode::Exclusive).unwrap();
let (llc_offset, locks) = unwrap_acquired(outcome, Some("in cargo-test mode"));
assert_eq!(llc_offset, 95100);
assert!(
locks.is_empty(),
"cargo-test-mode bypass must NOT take any flocks; \
got {} held fds",
locks.len(),
);
}
#[test]
fn acquire_resource_locks_cargo_test_mode_empty_string_inert() {
use crate::test_support::test_helpers::{EnvVarGuard, lock_env};
let _lock = lock_env();
let _env = EnvVarGuard::set(crate::KTSTR_CARGO_TEST_MODE_ENV, "");
let _llc_prefix = LlcLockPrefixGuard::new();
let plan = PinningPlan {
assignments: vec![(0, 95200)],
service_cpu: None,
llc_indices: vec![95200],
locks: Vec::new(),
};
let outcome = acquire_resource_locks(&plan, &[95200usize], LlcLockMode::Exclusive).unwrap();
let (_, locks) = unwrap_acquired(outcome, Some("with empty-string bypass inert"));
assert_eq!(
locks.len(),
1,
"empty-string cargo-test-mode is inert — expected the \
standard `Exclusive` path to take exactly one LLC fd, \
got {}",
locks.len(),
);
}