use super::super::test_helpers::{EnvVarGuard, eevdf_entry, lock_env, make_vm_result};
use super::*;
use crate::sync::MutexExt;
use tempfile::TempDir;
#[test]
fn combine_post_vm_errs_both_fail_surfaces_both_signals() {
let c = anyhow::anyhow!("snapshot-bridge captured nothing");
let u = anyhow::anyhow!("wprof .pb missing at <path>");
let combined = super::post_vm::combine_post_vm_errs(Some(c), Some(u))
.expect("both Some inputs produce Some output");
let rendered = format!("{combined:#}");
assert!(
rendered.contains("post_vm:"),
"combined message must label the conditional fail: {rendered}",
);
assert!(
rendered.contains("snapshot-bridge captured nothing"),
"conditional fail message must be preserved: {rendered}",
);
assert!(
rendered.contains("post_vm_unconditional:"),
"combined message must label the unconditional fail: {rendered}",
);
assert!(
rendered.contains("wprof .pb missing"),
"unconditional fail message must be preserved: {rendered}",
);
}
#[test]
fn combine_post_vm_errs_only_conditional_passes_through() {
let c = anyhow::anyhow!("snapshot-bridge captured nothing");
let combined = super::post_vm::combine_post_vm_errs(Some(c), None)
.expect("conditional Some produces Some output");
let rendered = format!("{combined:#}");
assert_eq!(rendered, "snapshot-bridge captured nothing");
}
#[test]
fn combine_post_vm_errs_only_unconditional_passes_through() {
let u = anyhow::anyhow!("wprof .pb missing at <path>");
let combined = super::post_vm::combine_post_vm_errs(None, Some(u))
.expect("unconditional Some produces Some output");
let rendered = format!("{combined:#}");
assert_eq!(rendered, "wprof .pb missing at <path>");
}
#[test]
fn combine_post_vm_errs_both_none_returns_none() {
let combined = super::post_vm::combine_post_vm_errs(None, None);
assert!(combined.is_none());
}
fn post_vm_ok(_result: &crate::vmm::VmResult) -> anyhow::Result<()> {
Ok(())
}
fn post_vm_err_conditional(_result: &crate::vmm::VmResult) -> anyhow::Result<()> {
Err(anyhow::anyhow!("snapshot-bridge captured nothing"))
}
fn post_vm_err_unconditional(_result: &crate::vmm::VmResult) -> anyhow::Result<()> {
Err(anyhow::anyhow!("wprof .pb missing at <path>"))
}
fn post_vm_panic(_result: &crate::vmm::VmResult) -> anyhow::Result<()> {
panic!("simulated callback panic");
}
#[test]
fn run_post_vm_callbacks_unconditional_fires_on_guest_fail() {
let mut entry = eevdf_entry("test_unconditional_on_guest_fail");
entry.post_vm = Some(post_vm_err_conditional);
entry.post_vm_unconditional = Some(post_vm_err_unconditional);
let result = make_vm_result("", "", 0, false);
let combined =
super::run_post_vm_callbacks(&entry, &result, true)
.expect("post_vm_unconditional must produce Some(err) when guest failed");
let rendered = format!("{combined:#}");
assert!(
!rendered.contains("snapshot-bridge captured nothing"),
"post_vm must be suppressed on guest-fail: {rendered}",
);
assert!(
rendered.contains("wprof .pb missing"),
"post_vm_unconditional must run on guest-fail: {rendered}",
);
}
#[test]
fn run_post_vm_callbacks_conditional_suppressed_on_guest_fail() {
let mut entry = eevdf_entry("test_conditional_suppressed_on_guest_fail");
entry.post_vm = Some(post_vm_err_conditional);
entry.post_vm_unconditional = None;
let result = make_vm_result("", "", 0, false);
let combined =
super::run_post_vm_callbacks(&entry, &result, true);
assert!(
combined.is_none(),
"post_vm must be suppressed AND no unconditional → None: {combined:?}",
);
}
#[test]
fn run_post_vm_callbacks_both_ok_returns_none() {
let mut entry = eevdf_entry("test_both_ok");
entry.post_vm = Some(post_vm_ok);
entry.post_vm_unconditional = Some(post_vm_ok);
let result = make_vm_result("", "", 0, false);
let combined =
super::run_post_vm_callbacks(&entry, &result, false);
assert!(combined.is_none(), "both Ok → None: {combined:?}");
}
#[test]
#[cfg(panic = "unwind")]
fn run_post_vm_callbacks_unconditional_panic_caught() {
let mut entry = eevdf_entry("test_unconditional_panic");
entry.post_vm = None;
entry.post_vm_unconditional = Some(post_vm_panic);
let result = make_vm_result("", "", 0, false);
let combined = super::run_post_vm_callbacks(&entry, &result, false)
.expect("panicking callback must produce Some(err)");
let rendered = format!("{combined:#}");
assert!(
rendered.contains("post_vm_unconditional callback panicked:"),
"panic must carry the slot label: {rendered}",
);
assert!(
rendered.contains("simulated callback panic"),
"panic message must be preserved: {rendered}",
);
}
#[test]
fn dedupe_include_files_empty_input() {
let out = dedupe_include_files(&[]).unwrap();
assert!(out.is_empty(), "empty in → empty out, got {out:?}");
}
#[test]
fn dedupe_include_files_identical_pair_collapses() {
let input = vec![
(
"include-files/helper".to_string(),
std::path::PathBuf::from("/usr/bin/helper"),
"declarative",
),
(
"include-files/helper".to_string(),
std::path::PathBuf::from("/usr/bin/helper"),
"scheduler config_file",
),
];
let out = dedupe_include_files(&input).unwrap();
assert_eq!(out.len(), 1, "identical pair must dedupe, got {out:?}");
assert_eq!(out[0].0, "include-files/helper");
assert_eq!(out[0].1, std::path::PathBuf::from("/usr/bin/helper"));
}
#[test]
fn dedupe_include_files_archive_collision_errors() {
let input = vec![
(
"include-files/config.json".to_string(),
std::path::PathBuf::from("/tmp/sched/config.json"),
"scheduler config_file",
),
(
"include-files/config.json".to_string(),
std::path::PathBuf::from("/tmp/payload/config.json"),
"declarative",
),
];
let err = dedupe_include_files(&input).unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("include_files conflict"),
"diagnostic must mention 'include_files conflict': {msg}",
);
assert!(
msg.contains("/tmp/sched/config.json") && msg.contains("/tmp/payload/config.json"),
"diagnostic must name both host paths: {msg}",
);
assert!(
msg.contains("origin: scheduler config_file") && msg.contains("origin: declarative"),
"diagnostic must name both origin labels: {msg}",
);
}
#[test]
fn dedupe_include_files_preserves_distinct_entries() {
let input = vec![
(
"include-files/a".to_string(),
std::path::PathBuf::from("/usr/bin/a"),
"declarative",
),
(
"include-files/b".to_string(),
std::path::PathBuf::from("/usr/bin/b"),
"declarative",
),
(
"include-files/c".to_string(),
std::path::PathBuf::from("/usr/bin/c"),
"scheduler config_file",
),
];
let out = dedupe_include_files(&input).unwrap();
assert_eq!(out.len(), 3, "three distinct entries must survive");
let archives: Vec<&str> = out.iter().map(|(a, _)| a.as_str()).collect();
assert!(archives.contains(&"include-files/a"));
assert!(archives.contains(&"include-files/b"));
assert!(archives.contains(&"include-files/c"));
}
#[test]
fn resolve_test_kernel_with_env_var() {
let _lock = lock_env();
let exe = crate::resolve_current_exe().unwrap();
let _env = EnvVarGuard::set(crate::KTSTR_TEST_KERNEL_ENV, &exe);
let result = resolve_test_kernel();
assert!(result.is_ok());
assert_eq!(result.unwrap(), exe);
}
#[test]
fn resolve_test_kernel_with_nonexistent_env_path() {
let _lock = lock_env();
let _env = EnvVarGuard::set(crate::KTSTR_TEST_KERNEL_ENV, "/nonexistent/kernel/path");
let result = resolve_test_kernel();
let err = match result {
Err(e) => e,
Ok(p) => panic!("expected nonexistent env path to fail, got {p:?}"),
};
assert!(
!crate::test_support::is_kernel_unavailable(&err),
"KTSTR_TEST_KERNEL pointing at a missing path must NOT downcast \
to KernelUnavailable (operator typo, not harness-misconfigured); \
got: {err:#}",
);
}
#[test]
fn resolve_test_kernel_no_sources_returns_kernel_unavailable() {
let _lock = lock_env();
let _e1 = EnvVarGuard::remove(crate::KTSTR_TEST_KERNEL_ENV);
let _e2 = EnvVarGuard::remove(crate::KTSTR_KERNEL_ENV);
let _e3 = EnvVarGuard::remove(crate::KTSTR_KERNEL_LIST_ENV);
match resolve_test_kernel() {
Ok(_) => {
}
Err(e) => {
assert!(
crate::test_support::is_kernel_unavailable(&e),
"every Err from resolve_test_kernel after env-clearing must \
downcast to KernelUnavailable; got: {e:#}",
);
}
}
}
#[test]
fn kernel_unavailable_display_renders_diagnostic() {
let err = KernelUnavailable {
diagnostic: "test fixture diagnostic".to_string(),
};
assert_eq!(format!("{err}"), "test fixture diagnostic");
}
#[test]
fn kvm_accessible_on_test_host() {
ensure_kvm().expect("/dev/kvm not accessible");
}
extern "C" fn sigrtmin_handler_probe(_sig: libc::c_int) {}
static SIGRTMIN_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn sigrtmin_save_install_restore_roundtrip() {
let _serial = SIGRTMIN_TEST_LOCK.lock_unpoisoned();
let mut saved: libc::sigaction = unsafe { std::mem::zeroed() };
let rc = unsafe { libc::sigaction(libc::SIGRTMIN(), std::ptr::null(), &mut saved as *mut _) };
assert_eq!(
rc,
0,
"sigaction(SIGRTMIN, NULL, &mut saved) must succeed; \
got rc={rc}, errno={}",
std::io::Error::last_os_error()
);
let mut probe: libc::sigaction = unsafe { std::mem::zeroed() };
probe.sa_sigaction = sigrtmin_handler_probe as *const () as usize;
unsafe {
libc::sigemptyset(&mut probe.sa_mask);
}
let rc = unsafe { libc::sigaction(libc::SIGRTMIN(), &probe, std::ptr::null_mut()) };
assert_eq!(rc, 0, "install probe handler for SIGRTMIN must succeed");
let mut current: libc::sigaction = unsafe { std::mem::zeroed() };
unsafe {
libc::sigaction(libc::SIGRTMIN(), std::ptr::null(), &mut current as *mut _);
}
let probe_addr = sigrtmin_handler_probe as *const () as usize;
assert_eq!(
current.sa_sigaction, probe_addr,
"after install, sa_sigaction must point at \
sigrtmin_handler_probe (0x{:x}); got 0x{:x} — the \
install path is broken",
probe_addr, current.sa_sigaction
);
let rc = unsafe { libc::sigaction(libc::SIGRTMIN(), &saved, std::ptr::null_mut()) };
assert_eq!(rc, 0, "restore from saved sigaction must succeed");
let mut after: libc::sigaction = unsafe { std::mem::zeroed() };
unsafe {
libc::sigaction(libc::SIGRTMIN(), std::ptr::null(), &mut after as *mut _);
}
assert_eq!(
after.sa_sigaction, saved.sa_sigaction,
"after restore, sa_sigaction must match the saved \
value — restore is broken or `saved` got clobbered \
during install. saved=0x{:x}, after=0x{:x}",
saved.sa_sigaction, after.sa_sigaction
);
let mask = !0x04000000i32;
assert_eq!(
after.sa_flags & mask,
saved.sa_flags & mask,
"after restore, sa_flags must match the saved value \
(ignoring SA_RESTORER)"
);
}
#[test]
fn resolve_scheduler_eevdf() {
let (path, source) = resolve_scheduler(&SchedulerSpec::Eevdf).unwrap();
assert!(path.is_none());
assert_eq!(
source,
ResolveSource::NotFound,
"Eevdf has no user-space binary — source must be NotFound",
);
}
#[test]
fn resolve_scheduler_kernel_builtin_is_not_found() {
let (path, source) = resolve_scheduler(&SchedulerSpec::KernelBuiltin {
enable: &[],
disable: &[],
})
.unwrap();
assert!(path.is_none());
assert_eq!(
source,
ResolveSource::NotFound,
"KernelBuiltin has no user-space binary — source must be NotFound",
);
}
#[test]
fn resolve_scheduler_path_exists() {
let exe = crate::resolve_current_exe().unwrap();
let (path, source) = resolve_scheduler(&SchedulerSpec::Path(Box::leak(
exe.to_str().unwrap().to_string().into_boxed_str(),
)))
.unwrap();
assert!(path.is_some());
assert_eq!(
source,
ResolveSource::Path,
"explicit SchedulerSpec::Path(_) is tagged Path",
);
}
#[test]
fn resolve_scheduler_path_missing() {
let result = resolve_scheduler(&SchedulerSpec::Path("/nonexistent/scheduler"));
assert!(result.is_err());
}
#[test]
fn resolve_scheduler_discover_missing() {
let _lock = lock_env();
let _env = EnvVarGuard::remove(crate::KTSTR_SCHEDULER_ENV);
let result = resolve_scheduler(&SchedulerSpec::Discover("__nonexistent_scheduler_xyz__"));
assert!(result.is_err());
}
#[test]
fn resolve_scheduler_discover_via_env() {
let _lock = lock_env();
let exe = crate::resolve_current_exe().unwrap();
let _env = EnvVarGuard::set(crate::KTSTR_SCHEDULER_ENV, &exe);
let (path, source) = resolve_scheduler(&SchedulerSpec::Discover("anything")).unwrap();
assert_eq!(path.unwrap(), exe);
assert_eq!(
source,
ResolveSource::EnvVar,
"KTSTR_SCHEDULER hit must tag the result EnvVar",
);
}
#[test]
fn resolve_scheduler_discover_path_lookup_under_cargo_test_mode() {
use std::os::unix::fs::PermissionsExt;
let _lock = lock_env();
let _no_env = EnvVarGuard::remove(crate::KTSTR_SCHEDULER_ENV);
let _cargo = EnvVarGuard::set(crate::KTSTR_CARGO_TEST_MODE_ENV, "1");
let dir = TempDir::new().expect("tempdir");
let bin_path = dir.path().join("__test_path_scheduler__");
std::fs::write(&bin_path, b"#!/bin/sh\nexit 0\n").expect("write stub");
let mut perms = std::fs::metadata(&bin_path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&bin_path, perms).expect("chmod 0755");
let _path_env = EnvVarGuard::set("PATH", dir.path());
let (path, source) =
resolve_scheduler(&SchedulerSpec::Discover("__test_path_scheduler__")).unwrap();
assert_eq!(path.expect("found on PATH"), bin_path);
assert_eq!(
source,
ResolveSource::PathLookup,
"PATH-lookup hit must tag the result PathLookup",
);
}
#[test]
fn resolve_scheduler_discover_path_lookup_inert_without_cargo_test_mode() {
use std::os::unix::fs::PermissionsExt;
let _lock = lock_env();
let _no_env = EnvVarGuard::remove(crate::KTSTR_SCHEDULER_ENV);
let _cargo = EnvVarGuard::remove(crate::KTSTR_CARGO_TEST_MODE_ENV);
let dir = TempDir::new().expect("tempdir");
let bin_path = dir.path().join("__test_inert_path_scheduler__");
std::fs::write(&bin_path, b"#!/bin/sh\nexit 0\n").expect("write stub");
let mut perms = std::fs::metadata(&bin_path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&bin_path, perms).expect("chmod 0755");
let _path_env = EnvVarGuard::set("PATH", dir.path());
let result = resolve_scheduler(&SchedulerSpec::Discover("__test_inert_path_scheduler__"));
match result {
Ok((_, source)) => {
panic!("PATH lookup must be inert without KTSTR_CARGO_TEST_MODE; got source {source:?}",)
}
Err(_) => {
}
}
}
#[test]
fn scheduler_label_eevdf_empty() {
assert_eq!(reporting::scheduler_label(&SchedulerSpec::Eevdf), "");
}
#[test]
fn scheduler_label_discover() {
assert_eq!(
reporting::scheduler_label(&SchedulerSpec::Discover("scx_mitosis")),
" [sched=scx_mitosis]"
);
}
#[test]
fn scheduler_label_path() {
assert_eq!(
reporting::scheduler_label(&SchedulerSpec::Path("/usr/bin/sched")),
" [sched=/usr/bin/sched]"
);
}