use super::*;
#[cfg(target_arch = "x86_64")]
#[test]
fn routing_failure_summary_none_when_zero_else_counts() {
assert!(
routing_failure_summary(0).is_none(),
"no routing failures → no summary"
);
let msg = routing_failure_summary(3).expect("n>0 → summary");
assert!(
msg.contains("3 device-IRQ routing failure"),
"summary names the count: {msg:?}"
);
}
#[test]
fn detect_guest_failure_surfaces_alloc_oom_panic_and_generic() {
let c = KtstrVm::detect_guest_failure(
"[ 0.000000] Booting Linux\n",
"memory allocation of 24 bytes failed\n",
);
assert!(c.contains("failed allocation"), "alloc cause: {c:?}");
assert!(
c.contains("memory allocation of 24 bytes failed"),
"echoes the line: {c:?}"
);
let c =
KtstrVm::detect_guest_failure("Kernel panic - not syncing: Attempted to kill init!\n", "");
assert!(c.contains("Guest kernel panic"), "panic cause: {c:?}");
let c = KtstrVm::detect_guest_failure("nothing here\n", "benign output\n");
assert!(
c.contains("may have panicked or rebooted"),
"generic: {c:?}"
);
let c = KtstrVm::detect_guest_failure(
"Kernel panic - not syncing: Attempted to kill init!\n",
"memory allocation of 8 bytes failed\n",
);
assert!(c.contains("failed allocation"), "alloc-priority: {c:?}");
}
#[test]
fn exec_exit_from_entries_decodes_last_crc_valid_frame() {
use crate::vmm::wire::{MSG_TYPE_EXEC_EXIT, ShmEntry};
let mk = |msg_type, payload: Vec<u8>, crc_ok| ShmEntry {
msg_type,
payload,
crc_ok,
};
assert_eq!(
KtstrVm::exec_exit_from_entries(&[mk(
MSG_TYPE_EXEC_EXIT,
17i32.to_le_bytes().to_vec(),
true
)]),
Some(17),
);
assert_eq!(
KtstrVm::exec_exit_from_entries(&[mk(
MSG_TYPE_EXEC_EXIT,
(-1i32).to_le_bytes().to_vec(),
true
)]),
Some(-1),
);
assert_eq!(
KtstrVm::exec_exit_from_entries(&[mk(
MSG_TYPE_EXEC_EXIT,
17i32.to_le_bytes().to_vec(),
false
)]),
None,
);
assert_eq!(
KtstrVm::exec_exit_from_entries(&[mk(MSG_TYPE_EXEC_EXIT, vec![1, 2, 3], true)]),
None,
);
assert_eq!(
KtstrVm::exec_exit_from_entries(&[mk(0xDEAD_BEEF, 0i32.to_le_bytes().to_vec(), true)]),
None,
);
assert_eq!(
KtstrVm::exec_exit_from_entries(&[
mk(MSG_TYPE_EXEC_EXIT, 1i32.to_le_bytes().to_vec(), true),
mk(MSG_TYPE_EXEC_EXIT, 2i32.to_le_bytes().to_vec(), true),
]),
Some(2),
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn ap_mp_state_set_correctly() {
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let vm = kvm::KtstrKvm::new(topo, 128, false).unwrap();
for vcpu in &vm.vcpus[1..] {
let state = vcpu.get_mp_state().unwrap();
assert_eq!(
state.mp_state,
kvm_bindings::KVM_MP_STATE_UNINITIALIZED,
"AP should default to UNINITIALIZED"
);
}
}
#[test]
fn boot_kernel_produces_output() {
let kernel = crate::test_support::require_kernel();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.topology(Topology::new(1, 1, 1, 1))
.memory_mib(256)
.timeout(Duration::from_secs(10))
.cmdline("loglevel=7")
.build()
);
let result = skip_on_contention!(vm.run());
assert!(
result.stderr.contains("Linux") || result.stderr.contains("Booting"),
"kernel console should contain boot messages"
);
}
#[test]
fn boot_kernel_smp_topology() {
let kernel = crate::test_support::require_kernel();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.topology(Topology::new(1, 2, 2, 1)) .memory_mib(256)
.timeout(Duration::from_secs(10))
.cmdline("loglevel=7")
.build()
);
let result = skip_on_contention!(vm.run());
assert!(!result.stderr.is_empty(), "no console output from SMP boot");
}
#[test]
fn bench_boot_time() {
let kernel = crate::test_support::require_kernel();
for (label, llcs, cores, threads, mem) in [("1cpu", 1, 1, 1, 256), ("4cpu", 2, 2, 1, 512)] {
let start = Instant::now();
let vm = match KtstrVm::builder()
.kernel(&kernel)
.topology(Topology::new(1, llcs, cores, threads))
.memory_mib(mem)
.timeout(Duration::from_secs(10))
.build()
{
Ok(vm) => vm,
Err(e)
if e.downcast_ref::<host_topology::ResourceContention>()
.is_some() =>
{
crate::report::test_skip(format_args!("{label}: resource contention: {e}"));
continue;
}
Err(e) => panic!("{e:#}"),
};
let setup = start.elapsed();
let result = skip_on_contention!(vm.run());
let boot_ms = result
.stderr
.lines()
.rev()
.find(|l| l.contains("Kernel panic") || l.contains("end Kernel panic"))
.and_then(|l| {
l.trim()
.strip_prefix('[')
.and_then(|s| s.split(']').next())
.and_then(|s| s.trim().parse::<f64>().ok())
})
.map(|s| (s * 1000.0) as u64)
.unwrap_or(0);
eprintln!(
"BENCH {label}: setup={:.0}ms kernel_boot={boot_ms}ms wall={:.0}ms timed_out={}",
setup.as_millis(),
result.duration.as_millis(),
result.timed_out,
);
}
}
#[test]
fn kvm_has_immediate_exit_cap() {
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let vm = kvm::KtstrKvm::new(topo, 64, false).unwrap();
assert!(
vm.has_immediate_exit,
"KVM_CAP_IMMEDIATE_EXIT should be available on modern kernels"
);
}
#[test]
fn boot_kernel_with_monitor() {
if !crate::test_support::cargo_ktstr_orchestrated() {
skip!(
"test boots a real KVM VM and depends on cargo-ktstr's VM-test \
concurrency cap to keep KVM page allocation, vCPU thread scheduling, \
and freeze rendezvous timing within budget. Raw `cargo nextest run` \
/ `cargo test` fans 7000+ tests at full host parallelism and \
produces a misleading `kill set by AP` failure ~5 s after VM start \
that masks the real cause (resource starvation, not a real bug). \
Run via `cargo ktstr test --kernel ../linux` instead, which sets \
KTSTR_ORCHESTRATED and constrains the per-VM resource budgets."
);
}
if crate::test_support::current_binary_is_coverage_instrumented() {
skip!(
"coverage-instrumented `current_exe` used as guest /init trips an \
AP-kill exit inside guest boot (failure shape: `kill set by AP` at \
~3.6 s from VM start). Test exercises host-side monitor behaviour \
with no coverage-relevant code paths, so skip-under-coverage loses \
no real coverage; the real fix is a non-instrumented /init binary."
);
}
let kernel = crate::test_support::require_kernel();
let _vmlinux = crate::test_support::require_vmlinux(&kernel);
let exe = crate::resolve_current_exe().unwrap();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.init_binary(&exe)
.topology(Topology::new(1, 1, 2, 1))
.memory_deferred()
.timeout(Duration::from_secs(15))
.build()
);
let result = skip_on_contention!(vm.run());
let Some(ref report) = result.monitor else {
return;
};
if report.boot_wait_outcome != crate::monitor::BootWaitOutcome::Fired {
skip!(
"boot wait did not observe a sys_rdy wake (boot_wait_outcome={:?}) \
— inconclusive (slow guest boot or kill-evt race); not a \
monitor-data regression. Total samples: {}, run duration: {:?}.",
report.boot_wait_outcome,
report.summary.total_samples,
result.duration,
);
}
assert!(
report.summary.total_samples > 0,
"monitor should have collected at least one sample"
);
let populated = report
.samples
.iter()
.rev()
.find(|s| s.cpus.iter().any(|c| c.rq_clock > 1_000_000))
.expect(
"no monitor sample showed populated runqueue data — every sample \
had all CPUs at rq_clock <= 1ms, \
or the monitor is reading the wrong rq offsets",
);
assert_eq!(
populated.cpus.len(),
2,
"topology requested 2 CPUs but monitor saw {}",
populated.cpus.len()
);
for (i, cpu) in populated.cpus.iter().enumerate() {
if cpu.rq_clock <= 1_000_000 {
continue;
}
assert!(
cpu.rq_clock < 300_000_000_000,
"cpu {i}: rq_clock must be < 300s (ns), got {}",
cpu.rq_clock
);
}
if let Some(ref obs) = report.watchdog_observation {
assert_eq!(
obs.expected_jiffies, obs.observed_jiffies,
"watchdog write/read roundtrip mismatch: expected={} observed={}",
obs.expected_jiffies, obs.observed_jiffies
);
}
for (i, cpu) in populated.cpus.iter().enumerate() {
assert!(
cpu.event_counters.is_none(),
"cpu {i}: event_counters must be None when no scheduler is loaded"
);
}
}
#[test]
fn monitor_data_valid_latch_records_live_page_offset() {
if !crate::test_support::cargo_ktstr_orchestrated() {
skip!("{}", crate::test_support::SKIP_NOT_ORCHESTRATED_MSG);
}
if crate::test_support::current_binary_is_coverage_instrumented() {
skip!(
"coverage-instrumented /init AP-kill — see boot_kernel_with_monitor \
for the shared rationale."
);
}
let kernel = crate::test_support::require_kernel();
let _vmlinux = crate::test_support::require_vmlinux(&kernel);
let exe = crate::resolve_current_exe().unwrap();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.init_binary(&exe)
.topology(Topology::new(1, 1, 2, 1))
.memory_deferred()
.timeout(Duration::from_secs(5))
.watchdog_timeout(Duration::from_secs(2))
.build()
);
let result = skip_on_contention!(vm.run());
let Some(ref report) = result.monitor else {
return;
};
assert!(
report.summary.total_samples > 0,
"monitor produced no samples — DATA_VALID latch \
observability cannot be evaluated"
);
assert_ne!(
report.page_offset, 0,
"DATA_VALID latch never fired during the run — \
monitor.page_offset stayed at the initial 0 sentinel. \
page_offset_base was never resolved or \
__per_cpu_offset[0] never became non-zero before the \
run closed",
);
assert!(
report.page_offset & (1u64 << 63) != 0,
"monitor.page_offset {:#x} is not in the canonical \
upper half — page_offset_resolved gate accepted a \
user-space address",
report.page_offset,
);
assert_eq!(
report.page_offset & 0xFFF,
0,
"monitor.page_offset {:#x} is not 4 KiB aligned",
report.page_offset,
);
}
#[test]
fn sys_rdy_releases_monitor_before_5s_timeout() {
if !crate::test_support::cargo_ktstr_orchestrated() {
skip!("{}", crate::test_support::SKIP_NOT_ORCHESTRATED_MSG);
}
if crate::test_support::current_binary_is_coverage_instrumented() {
skip!(
"coverage-instrumented /init AP-kill — see boot_kernel_with_monitor \
for the shared rationale."
);
}
let kernel = crate::test_support::require_kernel();
let _vmlinux = crate::test_support::require_vmlinux(&kernel);
let exe = crate::resolve_current_exe().unwrap();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.init_binary(&exe)
.topology(Topology::new(1, 1, 2, 1))
.memory_deferred()
.timeout(Duration::from_secs(15))
.build()
);
let result = skip_on_contention!(vm.run());
let Some(ref report) = result.monitor else {
return;
};
if report.boot_wait_outcome != crate::monitor::BootWaitOutcome::Fired {
skip!(
"boot wait did not observe a sys_rdy wake before the host's \
5 s ceiling (boot_wait_outcome={:?}) — inconclusive (slow \
guest boot, kill-evt race, or wait not run); not the \
sys_rdy → monitor-wake regression this test pins. Total \
samples: {}, run duration: {:?}. (The kill_evt fall-through \
is covered by \
monitor_exits_cleanly_when_guest_panics_before_sys_rdy.)",
report.boot_wait_outcome,
report.summary.total_samples,
result.duration,
);
}
assert!(
report.summary.total_samples > 0,
"sys_rdy fired but the monitor produced no samples — the wake \
reached the boot epoll but never reached the sample loop. Run \
wall time: {:?}",
result.duration,
);
let first = report
.samples
.first()
.expect("total_samples > 0 but samples list empty");
assert!(
first.elapsed_ms < 8_000,
"sys_rdy fired but the first monitor sample landed at {} ms — \
past the 8 s budget. The post-wake path (phys_base poll / \
page_offset resolve / first iteration) is broken or \
pathologically slow. Total samples: {}, run duration: {:?}",
first.elapsed_ms,
report.summary.total_samples,
result.duration,
);
}
#[test]
fn monitor_exits_cleanly_when_guest_panics_before_sys_rdy() {
let kernel = crate::test_support::require_kernel();
let _vmlinux = crate::test_support::require_vmlinux(&kernel);
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.topology(Topology::new(1, 1, 2, 1))
.memory_mib(256)
.timeout(Duration::from_secs(15))
.cmdline("init=/nonexistent panic=-1")
.build()
);
let result = skip_on_contention!(vm.run());
assert!(
!result.timed_out,
"guest never panicked / rebooted within 15 s — the test's \
premise (panic-before-sys_rdy → kernel reboot → VM exit) \
is not holding. Stderr tail: {:?}",
result.stderr.lines().rev().take(5).collect::<Vec<_>>(),
);
assert!(
result.duration < Duration::from_secs(12),
"VM ran for {:?} — past the 12 s budget. The monitor's \
boot wait did not wake on kill_evt; the loop sat on the \
sys_rdy ceiling instead. timed_out={}, exit_code={}",
result.duration,
result.timed_out,
result.exit_code,
);
}
#[test]
fn first_sample_has_valid_rq_clock_thanks_to_sys_rdy() {
if !crate::test_support::cargo_ktstr_orchestrated() {
skip!("{}", crate::test_support::SKIP_NOT_ORCHESTRATED_MSG);
}
if crate::test_support::current_binary_is_coverage_instrumented() {
skip!(
"coverage-instrumented /init AP-kill — see boot_kernel_with_monitor \
for the shared rationale."
);
}
let kernel = crate::test_support::require_kernel();
let _vmlinux = crate::test_support::require_vmlinux(&kernel);
let exe = crate::resolve_current_exe().unwrap();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.init_binary(&exe)
.topology(Topology::new(1, 1, 2, 1))
.memory_deferred()
.timeout(Duration::from_secs(15))
.build()
);
let result = skip_on_contention!(vm.run());
let Some(ref report) = result.monitor else {
return;
};
if report.boot_wait_outcome != crate::monitor::BootWaitOutcome::Fired {
skip!(
"boot wait did not observe a sys_rdy wake before the host's \
5 s ceiling (boot_wait_outcome={:?}) — inconclusive (slow \
guest boot / kill-evt race), not the FIRST-sample rq_clock \
contract this test pins. total_samples={}",
report.boot_wait_outcome,
report.summary.total_samples,
);
}
assert!(
report.summary.total_samples > 0,
"monitor produced no samples — cannot evaluate \
FIRST-sample semantics"
);
let early_populated = report
.samples
.iter()
.take(5)
.any(|s| s.cpus.iter().any(|c| c.rq_clock > 1_000_000));
assert!(
early_populated,
"none of the first 5 monitor samples had any CPU with \
rq_clock > 1ms — SYS_RDY did not wait for the guest's \
runqueue fields to be populated. \
total_samples: {}, run duration: {:?}",
report.summary.total_samples, result.duration,
);
}
#[test]
fn watchdog_timeout_override_lands_in_guest_memory() {
let kernel = crate::test_support::require_kernel();
let vmlinux = crate::test_support::require_vmlinux(&kernel);
let syms = crate::test_support::require_kernel_symbols(&vmlinux);
if syms.scx_root.is_none() {
skip!("scx_root not present (needs Linux 6.16+ with sched_ext enabled)");
}
let offsets = crate::test_support::require_kernel_offsets(&vmlinux);
if offsets.watchdog_offsets.is_none() {
skip!(
"scx_sched.watchdog_timeout field not in BTF \
(needs Linux 7.1+; pre-7.1 exposes watchdog timeout as a file-scope \
scx_watchdog_timeout symbol handled separately)"
);
}
const TIMEOUT_SECS: u64 = 2;
let hz = crate::monitor::guest_kernel_hz(Some(&kernel));
let expected_jiffies = TIMEOUT_SECS * hz;
let sched_bin = crate::test_support::require_binary("scx-ktstr");
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.topology(Topology::new(1, 1, 1, 1))
.memory_mib(256)
.timeout(Duration::from_secs(5))
.scheduler_binary(&sched_bin)
.watchdog_timeout(Duration::from_secs(TIMEOUT_SECS))
.build()
);
let result = skip_on_contention!(vm.run());
let report = result.monitor.as_ref().expect(
"ktstr: monitor report missing — require_kernel_offsets, scx_root, and \
watchdog_offsets all resolved at setup, so monitor initialization must \
have succeeded. A None report here is a bug in monitor startup",
);
let Some(obs) = &report.watchdog_observation else {
skip!(
"watchdog observation missing — the scheduler did not attach \
(scx_root remained null throughout the run)"
);
};
assert_eq!(
obs.expected_jiffies, expected_jiffies,
"expected_jiffies recorded by monitor ({}) does not match {} * HZ {} = {}",
obs.expected_jiffies, TIMEOUT_SECS, hz, expected_jiffies,
);
assert_eq!(
obs.observed_jiffies, obs.expected_jiffies,
"host wrote {} jiffies to scx_sched.watchdog_timeout but guest memory holds {} — host-write mechanism broken",
obs.expected_jiffies, obs.observed_jiffies,
);
}
#[test]
fn watchdog_override_prevents_stall_exit() {
let kernel = crate::test_support::require_kernel();
let _vmlinux = crate::test_support::require_vmlinux(&kernel);
let sched_bin = crate::test_support::require_binary("scx-ktstr");
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.topology(Topology::new(1, 1, 2, 1))
.memory_mib(256)
.timeout(Duration::from_secs(30))
.scheduler_binary(&sched_bin)
.watchdog_timeout(Duration::from_secs(300))
.build()
);
let result = skip_on_contention!(vm.run());
assert!(
result.crash_message.is_none(),
"no crash expected with 300s watchdog: {:?}",
result.crash_message
);
let output = &result.output;
let stderr = &result.stderr;
let lifecycle_phase_seen = |phase: crate::vmm::wire::LifecyclePhase| -> bool {
let Some(ref drain) = result.guest_messages else {
return false;
};
drain.entries.iter().any(|e| {
e.msg_type == crate::vmm::wire::MSG_TYPE_LIFECYCLE
&& e.crc_ok
&& !e.payload.is_empty()
&& crate::vmm::wire::LifecyclePhase::from_wire(e.payload[0]) == Some(phase)
})
};
assert!(
!lifecycle_phase_seen(crate::vmm::wire::LifecyclePhase::SchedulerDied),
"scheduler no longer running after 15s — either the watchdog fired or the \
scheduler exited for another reason. output: {output:?}, stderr: {stderr:?}",
);
assert!(
!lifecycle_phase_seen(crate::vmm::wire::LifecyclePhase::SchedulerNotAttached),
"scheduler did not attach — no watchdog override to evaluate. \
output: {output:?}, stderr: {stderr:?}",
);
assert!(
!output.contains("sched_ext: disabled") && !stderr.contains("sched_ext: disabled"),
"kernel disabled sched_ext during run — a watchdog stall or ops \
error fired. output: {output:?}, stderr: {stderr:?}",
);
if let Some(ref report) = result.monitor
&& let Some(ref obs) = report.watchdog_observation
{
let hz = crate::monitor::guest_kernel_hz(Some(&kernel));
let expected_jiffies = 300 * hz;
assert_eq!(
obs.expected_jiffies, expected_jiffies,
"watchdog override should be 300s * HZ={hz}"
);
assert_eq!(
obs.observed_jiffies, obs.expected_jiffies,
"write/read roundtrip mismatch"
);
}
}
#[test]
fn sched_domain_data_populated() {
if !crate::test_support::cargo_ktstr_orchestrated() {
skip!("{}", crate::test_support::SKIP_NOT_ORCHESTRATED_MSG);
}
if crate::test_support::current_binary_is_coverage_instrumented() {
skip!(
"coverage-instrumented /init AP-kill — see boot_kernel_with_monitor \
for the shared rationale."
);
}
let kernel = crate::test_support::require_kernel();
let vmlinux = crate::test_support::require_vmlinux(&kernel);
let offsets = crate::test_support::require_kernel_offsets(&vmlinux);
if offsets.sched_domain_offsets.is_none() {
skip!(
"sched_domain BTF fields not found (likely CONFIG_SMP=n; \
struct sched_domain is absent or incomplete in BTF on UP kernels, \
and on pre-6.17 kernels the rq.sd field is also compiled out)"
);
}
let exe = crate::resolve_current_exe().unwrap();
let vm = skip_on_contention!(
KtstrVm::builder()
.kernel(&kernel)
.init_binary(&exe)
.topology(Topology::new(1, 1, 2, 1))
.memory_deferred()
.timeout(Duration::from_secs(15))
.watchdog_timeout(Duration::from_secs(2))
.build()
);
let result = skip_on_contention!(vm.run());
let report = result.monitor.as_ref().expect(
"ktstr: monitor report missing — require_kernel_offsets and \
sched_domain_offsets resolved at setup, so monitor initialization \
must have succeeded. A None report here is a bug in monitor startup",
);
assert!(
report.summary.total_samples > 0,
"monitor should have collected at least one sample"
);
let populated = report
.samples
.iter()
.rev()
.find(|s| {
s.cpus.iter().any(|c| {
c.sched_domains
.as_ref()
.is_some_and(|doms| !doms.is_empty())
})
})
.unwrap_or_else(|| {
panic!(
"no sample had any CPU with non-empty sched_domains across \
{} collected samples — monitor samples may be racing boot-time \
kernel thread that builds the domain tree, or `rq.sd` offsets \
are wrong",
report.samples.len(),
);
});
for cpu in &populated.cpus {
if let Some(ref doms) = cpu.sched_domains {
if doms.is_empty() {
continue;
}
for w in doms.windows(2) {
assert!(
w[1].level > w[0].level,
"domain levels must be strictly increasing: {} -> {}",
w[0].level,
w[1].level
);
}
assert!(
doms[0].span_weight >= 2,
"lowest domain span_weight must be >= 2 for a 2-CPU topology, got {}",
doms[0].span_weight
);
for dom in doms {
assert!(
dom.span_weight > 0,
"domain level {} span_weight must be > 0",
dom.level
);
}
}
}
}
#[test]
fn builder_performance_mode_false_no_validation() {
let exe = crate::resolve_current_exe().unwrap();
let result = KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, 1, 1, 1))
.performance_mode(false)
.build();
match result {
Ok(_) => {}
Err(e)
if e.downcast_ref::<host_topology::ResourceContention>()
.is_some() =>
{
skip!("resource contention: {e}");
}
Err(e) => panic!("performance_mode=false should not validate host topology: {e:#}",),
}
}
#[test]
fn builder_performance_mode_oversubscribed_fails() {
let exe = crate::resolve_current_exe().unwrap();
let host_topo = host_topology::HostTopology::from_sysfs().unwrap();
let too_many = host_topo.total_cpus() as u32 + 1;
let result = KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, 1, too_many, 1))
.performance_mode(true)
.build();
match result {
Ok(_) => panic!("oversubscribed topology should fail"),
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("performance_mode"),
"error should mention performance_mode: {msg}",
);
}
}
}
#[test]
fn builder_performance_mode_too_many_llcs_fails() {
let exe = crate::resolve_current_exe().unwrap();
let host_topo = host_topology::HostTopology::from_sysfs().unwrap();
let too_many_llcs = host_topo.llc_groups.len() as u32 + 1;
if (too_many_llcs as usize + 1) <= host_topo.total_cpus() {
let result = KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, too_many_llcs, 1, 1))
.performance_mode(true)
.build();
assert!(
result.is_err(),
"more virtual LLCs than host LLCs should fail",
);
}
}
#[test]
fn builder_performance_mode_valid_succeeds() {
let exe = crate::resolve_current_exe().unwrap();
let host_topo = host_topology::HostTopology::from_sysfs().unwrap();
if host_topo.total_cpus() < 3 {
skip!("need >= 3 host CPUs for performance_mode test");
}
let result = KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, 1, 2, 1))
.performance_mode(true)
.build();
match result {
Ok(_) => {}
Err(e)
if e.downcast_ref::<host_topology::ResourceContention>()
.is_some() =>
{
skip!("resource contention: {e}");
}
Err(e) => panic!("valid topology with performance_mode should build: {e:#}",),
}
}
#[test]
fn builder_performance_mode_preserves_in_vm() {
let exe = crate::resolve_current_exe().unwrap();
let host_topo = host_topology::HostTopology::from_sysfs().unwrap();
if host_topo.total_cpus() < 3 {
skip!("need >= 3 host CPUs for performance_mode test");
}
let vm = skip_on_contention!(
KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, 1, 2, 1))
.performance_mode(true)
.build()
);
assert!(vm.performance_mode);
}
#[test]
fn builder_performance_mode_false_preserves_in_vm() {
let exe = crate::resolve_current_exe().unwrap();
let vm = skip_on_contention!(
KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, 1, 1, 1))
.performance_mode(false)
.build()
);
assert!(!vm.performance_mode);
}
#[test]
fn builder_performance_mode_mbind_nodes_populated() {
let exe = crate::resolve_current_exe().unwrap();
let host_topo = host_topology::HostTopology::from_sysfs().unwrap();
if host_topo.total_cpus() < 3 {
skip!("need >= 3 host CPUs for performance_mode test");
}
let vm = KtstrVmBuilder::default()
.kernel(&exe)
.topology(Topology::new(1, 1, 2, 1))
.performance_mode(true)
.build();
if let Ok(vm) = vm {
assert!(
!vm.mbind_node_map.is_empty(),
"mbind_node_map should be populated for performance_mode",
);
}
}