use super::super::*;
use super::*;
use crate::vmm::topology::Topology;
#[test]
fn parse_cpu_list_range() {
assert_eq!(parse_cpu_list_lenient("0-3"), vec![0, 1, 2, 3]);
}
#[test]
fn parse_cpu_list_single() {
assert_eq!(parse_cpu_list_lenient("5"), vec![5]);
}
#[test]
fn parse_cpu_list_mixed() {
assert_eq!(
parse_cpu_list_lenient("0-2,5,7-9"),
vec![0, 1, 2, 5, 7, 8, 9]
);
}
#[test]
fn parse_cpu_list_empty() {
assert!(parse_cpu_list_lenient("").is_empty());
}
#[test]
fn parse_cpu_list_whitespace() {
assert_eq!(parse_cpu_list_lenient("0-3\n"), vec![0, 1, 2, 3]);
}
#[test]
fn host_topology_from_sysfs() {
let topo = HostTopology::from_sysfs();
assert!(topo.is_ok(), "should read host topology: {:?}", topo.err());
let topo = topo.unwrap();
assert!(!topo.online_cpus.is_empty());
assert!(!topo.llc_groups.is_empty());
}
#[test]
fn pinning_plan_simple() {
let topo = HostTopology::from_sysfs().unwrap();
if topo.total_cpus() < 2 {
return; }
let plan = topo.compute_pinning(&Topology::new(1, 1, 2, 1), false, 0);
assert!(plan.is_ok(), "pinning should succeed: {:?}", plan.err());
let plan = plan.unwrap();
assert_eq!(plan.assignments.len(), 2);
let cpus: Vec<usize> = plan.assignments.iter().map(|a| a.1).collect();
let unique: std::collections::HashSet<usize> = cpus.iter().copied().collect();
assert_eq!(cpus.len(), unique.len());
}
#[test]
fn pinning_plan_oversubscribed() {
let topo = HostTopology::from_sysfs().unwrap();
let too_many = topo.total_cpus() as u32 + 1;
let plan = topo.compute_pinning(&Topology::new(1, 1, too_many, 1), false, 0);
assert!(plan.is_err());
}
#[test]
fn hugepages_needed_values() {
assert_eq!(hugepages_needed(2), 1);
assert_eq!(hugepages_needed(4), 2);
assert_eq!(hugepages_needed(2048), 1024);
assert_eq!(hugepages_needed(3), 2);
}
#[test]
fn hugepages_free_runs() {
let _ = hugepages_free();
}
#[test]
fn host_load_estimate_runs() {
let result = host_load_estimate();
assert!(result.is_some());
let (running, total) = result.unwrap();
assert!(total > 0);
assert!(running >= 1);
}
#[test]
fn parse_cpu_list_trailing_comma() {
assert_eq!(parse_cpu_list_lenient("0,1,2,"), vec![0, 1, 2]);
}
#[test]
fn parse_cpu_list_leading_comma() {
assert_eq!(parse_cpu_list_lenient(",0,1"), vec![0, 1]);
}
#[test]
fn parse_cpu_list_single_zero() {
assert_eq!(parse_cpu_list_lenient("0"), vec![0]);
}
#[test]
fn parse_cpu_list_large_ids() {
assert_eq!(parse_cpu_list_lenient("127,255"), vec![127, 255]);
}
#[test]
fn parse_cpu_list_reversed_range() {
assert!(parse_cpu_list_lenient("5-3").is_empty());
}
#[test]
fn parse_cpu_list_non_numeric() {
assert!(parse_cpu_list_lenient("abc").is_empty());
}
#[test]
fn compute_pinning_single_llc() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 2);
assert_eq!(plan.assignments[0], (0, 0));
assert_eq!(plan.assignments[1], (1, 1));
}
#[test]
fn compute_pinning_two_llcs() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
assert_eq!(plan.assignments[0], (0, 0));
assert_eq!(plan.assignments[1], (1, 1));
assert_eq!(plan.assignments[2], (2, 4));
assert_eq!(plan.assignments[3], (3, 5));
}
#[test]
fn compute_pinning_with_smt() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3, 4, 5, 6, 7]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 2), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
let cpus: Vec<usize> = plan.assignments.iter().map(|a| a.1).collect();
let unique: std::collections::HashSet<usize> = cpus.iter().copied().collect();
assert_eq!(cpus.len(), unique.len());
}
#[test]
fn compute_pinning_exact_fit() {
let topo = synthetic_topo(vec![vec![0, 1], vec![2, 3]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
let assigned: std::collections::HashSet<usize> = plan.assignments.iter().map(|a| a.1).collect();
let all_cpus: std::collections::HashSet<usize> = topo.online_cpus.iter().copied().collect();
assert_eq!(assigned, all_cpus, "exact fit must consume all host CPUs");
assert_eq!(
assigned.len(),
plan.assignments.len(),
"all assignments must be unique",
);
}
#[test]
fn compute_pinning_error_too_many_vcpus() {
let topo = synthetic_topo(vec![vec![0, 1]]);
let err = topo
.compute_pinning(&Topology::new(1, 1, 4, 1), false, 0)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("4 vCPUs") && msg.contains("2 host CPUs"),
"error should mention CPU counts: {msg}",
);
}
#[test]
fn compute_pinning_error_too_many_llcs() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3]]);
let err = topo
.compute_pinning(&Topology::new(1, 2, 1, 1), false, 0)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("2 LLCs") && msg.contains("1 LLC groups"),
"error should mention LLC count mismatch: {msg}",
);
}
#[test]
fn compute_pinning_error_llc_too_small() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4]]);
let err = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 0)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("LLC group 1") && msg.contains("1 available"),
"error should identify the undersized LLC: {msg}",
);
}
#[test]
fn compute_pinning_no_cross_llc_sharing() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10, 11]]);
let plan = topo
.compute_pinning(&Topology::new(1, 3, 2, 1), false, 0)
.unwrap();
for (vcpu_id, host_cpu) in &plan.assignments {
let llc_idx = vcpu_id / 2; let llc_start = llc_idx as usize * 4;
let llc_end = llc_start + 3;
assert!(
*host_cpu >= llc_start && *host_cpu <= llc_end,
"vCPU {vcpu_id} (LLC {llc_idx}) pinned to CPU {host_cpu}, \
expected range {llc_start}..={llc_end}",
);
}
}
#[test]
fn compute_pinning_all_assignments_unique() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 4, 1), false, 0)
.unwrap();
let cpus: Vec<usize> = plan.assignments.iter().map(|a| a.1).collect();
let unique: std::collections::HashSet<usize> = cpus.iter().copied().collect();
assert_eq!(
cpus.len(),
unique.len(),
"all host CPU assignments must be unique: {:?}",
cpus,
);
}
#[test]
fn compute_pinning_vcpu_ids_sequential() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 4, 1), false, 0)
.unwrap();
let vcpu_ids: Vec<u32> = plan.assignments.iter().map(|a| a.0).collect();
assert_eq!(vcpu_ids, vec![0, 1, 2, 3]);
}
#[test]
fn compute_pinning_single_vcpu() {
let topo = synthetic_topo(vec![vec![42]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 1, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 1);
assert_eq!(plan.assignments[0], (0, 42));
}
#[test]
fn sysfs_llc_groups_cover_all_cpus() {
let topo = HostTopology::from_sysfs().unwrap();
let llc_cpus: Vec<usize> = topo
.llc_groups
.iter()
.flat_map(|g| g.cpus.iter().copied())
.collect();
for cpu in &topo.online_cpus {
assert!(
llc_cpus.contains(cpu),
"CPU {} is online but not in any LLC group",
cpu,
);
}
}
#[test]
fn sysfs_llc_groups_nonempty() {
let topo = HostTopology::from_sysfs().unwrap();
for (i, group) in topo.llc_groups.iter().enumerate() {
assert!(
!group.cpus.is_empty(),
"LLC group {} should have at least one CPU",
i,
);
}
}
#[test]
fn sysfs_pinning_respects_llc_boundaries() {
let topo = HostTopology::from_sysfs().unwrap();
if topo.llc_groups.len() < 2 || topo.total_cpus() < 4 {
return; }
let min_llc_size = topo.llc_groups.iter().map(|g| g.cpus.len()).min().unwrap();
if min_llc_size < 2 {
return;
}
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 0)
.unwrap();
for (vcpu_id, host_cpu) in &plan.assignments {
let llc_idx = vcpu_id / 2;
let group = &topo.llc_groups[llc_idx as usize];
assert!(
group.cpus.contains(host_cpu),
"vCPU {} mapped to CPU {} which is not in LLC group {}",
vcpu_id,
host_cpu,
llc_idx,
);
}
}
#[test]
fn hugepages_needed_boundary() {
assert_eq!(hugepages_needed(1), 1); assert_eq!(hugepages_needed(0), 0);
}
#[test]
fn hugepages_needed_exact_multiple() {
assert_eq!(hugepages_needed(1024), 512);
}
#[test]
fn compute_pinning_service_cpu_picks_unpinned() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), true, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 2);
let service = plan.service_cpu.expect("service_cpu should be set");
let vcpu_cpus: std::collections::HashSet<usize> =
plan.assignments.iter().map(|a| a.1).collect();
assert!(
!vcpu_cpus.contains(&service),
"service CPU {service} must not be assigned to a vCPU",
);
}
#[test]
fn compute_pinning_service_cpu_false_returns_none() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), false, 0)
.unwrap();
assert!(plan.service_cpu.is_none());
}
#[test]
fn compute_pinning_service_cpu_exact_fit() {
let topo = synthetic_topo(vec![vec![0, 1, 2]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), true, 0)
.unwrap();
let service = plan.service_cpu.expect("service_cpu should be set");
assert_eq!(service, 2, "service CPU should be the only remaining CPU");
let vcpu_cpus: std::collections::HashSet<usize> =
plan.assignments.iter().map(|a| a.1).collect();
assert!(
!vcpu_cpus.contains(&service),
"service CPU {service} must not overlap vCPU assignments",
);
}
#[test]
fn compute_pinning_service_cpu_insufficient_fails() {
let topo = synthetic_topo(vec![vec![0, 1]]);
let err = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), true, 0)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("3 CPUs") && msg.contains("2 host CPUs"),
"error should mention CPU shortage: {msg}",
);
}
#[test]
fn compute_pinning_service_cpu_multi_llc() {
let topo = synthetic_topo(vec![vec![0, 1, 2], vec![3, 4, 5]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), true, 0)
.unwrap();
let service = plan.service_cpu.unwrap();
let vcpu_cpus: std::collections::HashSet<usize> =
plan.assignments.iter().map(|a| a.1).collect();
assert!(!vcpu_cpus.contains(&service));
}
#[test]
fn sysfs_cpu_to_node_populated() {
let topo = HostTopology::from_sysfs().unwrap();
if !topo.cpu_to_node.is_empty() {
for (&cpu, &node) in &topo.cpu_to_node {
assert!(
topo.online_cpus.contains(&cpu),
"NUMA mapping for CPU {cpu} but not in online set",
);
assert!(node < 1024, "unexpected NUMA node ID {node} for CPU {cpu}");
}
}
}
#[test]
fn max_cores_per_llc_synthetic() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5]]);
assert_eq!(topo.max_cores_per_llc(), 4);
}
#[test]
fn max_cores_per_llc_uniform() {
let topo = synthetic_topo(vec![vec![0, 1, 2], vec![3, 4, 5]]);
assert_eq!(topo.max_cores_per_llc(), 3);
}
#[test]
fn mbind_to_nodes_empty_is_noop() {
unsafe {
mbind_to_nodes(std::ptr::null_mut(), 0, &[]);
mbind_to_nodes(std::ptr::null_mut(), 4096, &[]);
}
}
#[test]
fn llc_numa_node_synthetic() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1]),
(0, vec![2, 3]),
(1, vec![4, 5]),
(1, vec![6, 7]),
]);
assert_eq!(topo.llc_numa_node(0), 0);
assert_eq!(topo.llc_numa_node(1), 0);
assert_eq!(topo.llc_numa_node(2), 1);
assert_eq!(topo.llc_numa_node(3), 1);
}
#[test]
fn compute_pinning_numa_two_nodes() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1, 2, 3]),
(0, vec![4, 5, 6, 7]),
(1, vec![8, 9, 10, 11]),
(1, vec![12, 13, 14, 15]),
]);
let plan = topo
.compute_pinning(&Topology::new(2, 4, 2, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 8);
let node_0_cpus: Vec<usize> = plan
.assignments
.iter()
.filter(|(vcpu, _)| *vcpu < 4) .map(|(_, cpu)| *cpu)
.collect();
let node_0_host_nodes = numa_nodes_for_cpus(&topo, &node_0_cpus);
assert_eq!(
node_0_host_nodes.len(),
1,
"guest NUMA 0 LLCs should all be on one host NUMA node, got {:?}",
node_0_host_nodes,
);
let node_1_cpus: Vec<usize> = plan
.assignments
.iter()
.filter(|(vcpu, _)| *vcpu >= 4) .map(|(_, cpu)| *cpu)
.collect();
let node_1_host_nodes = numa_nodes_for_cpus(&topo, &node_1_cpus);
assert_eq!(
node_1_host_nodes.len(),
1,
"guest NUMA 1 LLCs should all be on one host NUMA node, got {:?}",
node_1_host_nodes,
);
assert_ne!(
node_0_host_nodes.iter().next(),
node_1_host_nodes.iter().next(),
"guest NUMA nodes should map to different host NUMA nodes",
);
}
#[test]
fn numa_aware_llc_order_uneven_llcs_preserves_remainder() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1]),
(0, vec![2, 3]),
(0, vec![4, 5]),
(1, vec![6, 7]),
(1, vec![8, 9]),
(1, vec![10, 11]),
]);
let order = topo.numa_aware_llc_order(2, 5, 0);
assert_eq!(
order.len(),
5,
"uneven llc distribution must preserve all LLCs, got {order:?}"
);
let first_three_nodes: std::collections::BTreeSet<usize> = order[..3]
.iter()
.map(|&idx| topo.llc_numa_node(idx))
.collect();
let last_two_nodes: std::collections::BTreeSet<usize> = order[3..]
.iter()
.map(|&idx| topo.llc_numa_node(idx))
.collect();
assert_eq!(
first_three_nodes.len(),
1,
"first 3 LLCs must share a host node"
);
assert_eq!(
last_two_nodes.len(),
1,
"last 2 LLCs must share a host node"
);
assert_ne!(
first_three_nodes, last_two_nodes,
"two guest NUMA nodes must map to distinct host nodes"
);
}
#[test]
fn numa_aware_llc_order_zero_numa_nodes_is_safe() {
let topo = synthetic_topo_numa(vec![(0, vec![0, 1]), (0, vec![2, 3])]);
let order = topo.numa_aware_llc_order(0, 2, 0);
assert_eq!(
order.len(),
2,
"zero-numa fallback must still produce an order"
);
}
#[test]
fn numa_aware_llc_order_fewer_llcs_than_nodes_falls_back() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1]),
(0, vec![2, 3]),
(1, vec![4, 5]),
(1, vec![6, 7]),
]);
let order = topo.numa_aware_llc_order(4, 2, 0);
assert_eq!(
order.len(),
2,
"fewer-llcs-than-nodes fallback must still produce 2 entries"
);
}
#[test]
fn compute_pinning_numa_fallback_insufficient_nodes() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1]),
(0, vec![2, 3]),
(0, vec![4, 5]),
(0, vec![6, 7]),
]);
let plan = topo
.compute_pinning(&Topology::new(2, 4, 2, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 8);
let cpus: Vec<usize> = plan.assignments.iter().map(|a| a.1).collect();
let unique: std::collections::HashSet<usize> = cpus.iter().copied().collect();
assert_eq!(cpus.len(), unique.len());
}
#[test]
fn compute_pinning_numa_single_node_unchanged() {
let topo = synthetic_topo_numa(vec![(0, vec![0, 1, 2, 3]), (1, vec![4, 5, 6, 7])]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
assert_eq!(plan.assignments[0], (0, 0));
assert_eq!(plan.assignments[1], (1, 1));
assert_eq!(plan.assignments[2], (2, 4));
assert_eq!(plan.assignments[3], (3, 5));
}
#[test]
fn compute_pinning_numa_three_nodes() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1]),
(0, vec![2, 3]),
(1, vec![4, 5]),
(1, vec![6, 7]),
(2, vec![8, 9]),
(2, vec![10, 11]),
]);
let plan = topo
.compute_pinning(&Topology::new(3, 6, 1, 1), false, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 6);
for guest_node in 0..3u32 {
let start = guest_node * 2;
let end = start + 2;
let cpus: Vec<usize> = plan
.assignments
.iter()
.filter(|(vcpu, _)| *vcpu >= start && *vcpu < end)
.map(|(_, cpu)| *cpu)
.collect();
let nodes = numa_nodes_for_cpus(&topo, &cpus);
assert_eq!(
nodes.len(),
1,
"guest NUMA {} should be on one host NUMA node, got {:?}",
guest_node,
nodes,
);
}
}
#[test]
fn compute_pinning_numa_with_service_cpu() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1, 2, 3]),
(0, vec![4, 5, 6, 7]),
(1, vec![8, 9, 10, 11]),
(1, vec![12, 13, 14, 15]),
]);
let plan = topo
.compute_pinning(&Topology::new(2, 4, 2, 1), true, 0)
.unwrap();
assert_eq!(plan.assignments.len(), 8);
let service = plan.service_cpu.expect("service_cpu should be set");
let vcpu_cpus: std::collections::HashSet<usize> =
plan.assignments.iter().map(|a| a.1).collect();
assert!(
!vcpu_cpus.contains(&service),
"service CPU {service} must not overlap vCPU assignments",
);
}
#[test]
fn llc_numa_node_empty_map() {
let mut topo = HostTopology::new_for_tests(&[(vec![0, 1], 0)]);
topo.cpu_to_node.clear();
topo.host_node_llcs.clear();
assert_eq!(topo.llc_numa_node(0), 0);
}
#[test]
fn compute_pinning_offset_single_llc_wraps() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), false, 1)
.unwrap();
assert_eq!(plan.assignments.len(), 2);
assert_eq!(plan.assignments[0], (0, 0));
assert_eq!(plan.assignments[1], (1, 1));
}
#[test]
fn compute_pinning_offset_two_llcs_shifts() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 1)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
assert_eq!(plan.assignments[0], (0, 4));
assert_eq!(plan.assignments[1], (1, 5));
assert_eq!(plan.assignments[2], (2, 0));
assert_eq!(plan.assignments[3], (3, 1));
}
#[test]
fn compute_pinning_offset_wraps_modulo() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 2)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
assert_eq!(plan.assignments[0], (0, 0));
assert_eq!(plan.assignments[1], (1, 1));
assert_eq!(plan.assignments[2], (2, 4));
assert_eq!(plan.assignments[3], (3, 5));
}
#[test]
fn compute_pinning_offset_three_llcs_partial() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10, 11]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), false, 1)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
assert_eq!(plan.assignments[0], (0, 4));
assert_eq!(plan.assignments[1], (1, 5));
assert_eq!(plan.assignments[2], (2, 8));
assert_eq!(plan.assignments[3], (3, 9));
}
#[test]
fn compute_pinning_offset_large_wraps() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10, 11]]);
let plan = topo
.compute_pinning(&Topology::new(1, 1, 2, 1), false, 5)
.unwrap();
assert_eq!(plan.assignments.len(), 2);
assert_eq!(plan.assignments[0], (0, 8));
assert_eq!(plan.assignments[1], (1, 9));
}
#[test]
fn compute_pinning_offset_numa_within_rotation() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1, 2, 3]),
(0, vec![4, 5, 6, 7]),
(1, vec![8, 9, 10, 11]),
(1, vec![12, 13, 14, 15]),
]);
let plan = topo
.compute_pinning(&Topology::new(2, 4, 2, 1), false, 1)
.unwrap();
assert_eq!(plan.assignments.len(), 8);
assert_eq!(plan.assignments[0], (0, 4));
assert_eq!(plan.assignments[1], (1, 5));
assert_eq!(plan.assignments[2], (2, 0));
assert_eq!(plan.assignments[3], (3, 1));
assert_eq!(plan.assignments[4], (4, 12));
assert_eq!(plan.assignments[5], (5, 13));
assert_eq!(plan.assignments[6], (6, 8));
assert_eq!(plan.assignments[7], (7, 9));
}
#[test]
fn compute_pinning_offset_numa_node_rotation() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1, 2, 3]),
(0, vec![4, 5, 6, 7]),
(1, vec![8, 9, 10, 11]),
(1, vec![12, 13, 14, 15]),
]);
let plan = topo
.compute_pinning(&Topology::new(2, 4, 2, 1), false, 2)
.unwrap();
assert_eq!(plan.assignments.len(), 8);
assert_eq!(plan.assignments[0], (0, 8));
assert_eq!(plan.assignments[1], (1, 9));
assert_eq!(plan.assignments[2], (2, 12));
assert_eq!(plan.assignments[3], (3, 13));
assert_eq!(plan.assignments[4], (4, 0));
assert_eq!(plan.assignments[5], (5, 1));
assert_eq!(plan.assignments[6], (6, 4));
assert_eq!(plan.assignments[7], (7, 5));
}
#[test]
fn compute_pinning_offset_with_service_cpu() {
let topo = synthetic_topo(vec![vec![0, 1, 2, 3], vec![4, 5, 6, 7]]);
let plan = topo
.compute_pinning(&Topology::new(1, 2, 2, 1), true, 1)
.unwrap();
assert_eq!(plan.assignments.len(), 4);
assert_eq!(plan.assignments[0], (0, 4));
assert_eq!(plan.assignments[1], (1, 5));
assert_eq!(plan.assignments[2], (2, 0));
assert_eq!(plan.assignments[3], (3, 1));
let service = plan.service_cpu.expect("service_cpu should be set");
assert_eq!(service, 2);
let vcpu_cpus: std::collections::HashSet<usize> =
plan.assignments.iter().map(|a| a.1).collect();
assert!(!vcpu_cpus.contains(&service));
}
#[test]
fn compute_pinning_offset_numa_combined_rotation() {
let topo = synthetic_topo_numa(vec![
(0, vec![0, 1, 2, 3]),
(0, vec![4, 5, 6, 7]),
(1, vec![8, 9, 10, 11]),
(1, vec![12, 13, 14, 15]),
]);
let plan = topo
.compute_pinning(&Topology::new(2, 4, 2, 1), false, 3)
.unwrap();
assert_eq!(plan.assignments.len(), 8);
assert_eq!(plan.assignments[0], (0, 12));
assert_eq!(plan.assignments[1], (1, 13));
assert_eq!(plan.assignments[2], (2, 8));
assert_eq!(plan.assignments[3], (3, 9));
assert_eq!(plan.assignments[4], (4, 4));
assert_eq!(plan.assignments[5], (5, 5));
assert_eq!(plan.assignments[6], (6, 0));
assert_eq!(plan.assignments[7], (7, 1));
}
#[test]
fn resource_lock_exclusive_acquires() {
let _tempfile_keep_alive = tempfile::Builder::new()
.prefix("ktstr-test-flock-excl-acquires-")
.suffix(".lock")
.tempfile()
.unwrap();
let path = _tempfile_keep_alive.path().to_str().unwrap();
let fd = try_flock(path, FlockMode::Exclusive).expect("open should succeed");
assert!(fd.is_some(), "exclusive lock on fresh file should succeed");
}