use super::*;
#[cfg(target_arch = "aarch64")]
#[test]
fn aarch64_initrd_stays_below_pvtime_carve() {
use crate::vmm::{aarch64::fdt::pvtime_base, kvm::DRAM_START};
for &(mem, cpus) in &[(512u32, 2u32), (512, 8), (2048, 256), (4096, 512)] {
let pvt = pvtime_base(mem, cpus);
let max = pvt - DRAM_START - (1 << 20);
let load = aarch64_initrd_addr(mem, cpus, max).expect("near-max initrd must fit");
assert!(
load >= DRAM_START,
"initrd underflows DRAM_START (mem={mem} cpus={cpus} load={load:#x})"
);
assert!(
load + max <= pvt,
"initrd top {:#x} entered the PVTIME carve at {pvt:#x} (mem={mem} cpus={cpus})",
load + max
);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn aarch64_initrd_oversized_returns_err_not_panic_or_wrap() {
use crate::vmm::{aarch64::fdt::pvtime_base, kvm::DRAM_START};
for &(mem, cpus) in &[(512u32, 2u32), (512, 8), (2048, 256), (4096, 512)] {
let pvt = pvtime_base(mem, cpus);
let oversized = (pvt - DRAM_START) + (1 << 20);
let result = aarch64_initrd_addr(mem, cpus, oversized);
assert!(
result.is_err(),
"oversized initrd must Err (mem={mem} cpus={cpus} \
oversized={oversized:#x} pvt={pvt:#x}), got {result:?}"
);
}
let (mem, cpus) = (512u32, 2u32);
let pvt = pvtime_base(mem, cpus);
let huge = pvt + (1 << 20);
assert!(
aarch64_initrd_addr(mem, cpus, huge).is_err(),
"initrd larger than pvtime_base must Err (huge={huge:#x} pvt={pvt:#x})"
);
}
#[test]
fn disk_auto_mount_cmdline_tokens_raw_emits_nothing() {
let disk = disk_config::DiskConfig::default();
assert_eq!(disk.filesystem, disk_config::Filesystem::Raw);
assert_eq!(disk_auto_mount_cmdline_tokens(&disk), "");
}
#[test]
fn disk_auto_mount_cmdline_tokens_btrfs_default() {
let disk = disk_config::DiskConfig::default().filesystem(disk_config::Filesystem::Btrfs);
assert_eq!(
disk_auto_mount_cmdline_tokens(&disk),
" KTSTR_DISK0_FS=btrfs KTSTR_DISK0_MOUNT=/mnt/disk0",
);
}
#[test]
fn disk_auto_mount_cmdline_tokens_btrfs_named() {
let disk = disk_config::DiskConfig::default()
.filesystem(disk_config::Filesystem::Btrfs)
.with_name("data");
assert_eq!(
disk_auto_mount_cmdline_tokens(&disk),
" KTSTR_DISK0_FS=btrfs KTSTR_DISK0_MOUNT=/mnt/data",
);
}
#[test]
fn disk_auto_mount_cmdline_tokens_btrfs_read_only() {
let disk = disk_config::DiskConfig::default()
.filesystem(disk_config::Filesystem::Btrfs)
.read_only();
assert_eq!(
disk_auto_mount_cmdline_tokens(&disk),
" KTSTR_DISK0_FS=btrfs KTSTR_DISK0_MOUNT=/mnt/disk0 KTSTR_DISK0_RO=1",
);
}
#[test]
fn disk_auto_mount_cmdline_tokens_no_auto_mount_suppresses() {
let disk = disk_config::DiskConfig::default()
.filesystem(disk_config::Filesystem::Btrfs)
.no_auto_mount();
assert_eq!(disk_auto_mount_cmdline_tokens(&disk), "");
let disk = disk_config::DiskConfig::default()
.filesystem(disk_config::Filesystem::Btrfs)
.with_name("data")
.read_only()
.no_auto_mount();
assert_eq!(disk_auto_mount_cmdline_tokens(&disk), "");
}
#[test]
fn disk_auto_mount_cmdline_tokens_raw_with_no_auto_mount() {
let disk = disk_config::DiskConfig::default().no_auto_mount();
assert_eq!(disk.filesystem, disk_config::Filesystem::Raw);
assert_eq!(disk_auto_mount_cmdline_tokens(&disk), "");
}
#[test]
fn disk_auto_mount_cmdline_tokens_starts_with_space() {
let disk = disk_config::DiskConfig::default().filesystem(disk_config::Filesystem::Btrfs);
let s = disk_auto_mount_cmdline_tokens(&disk);
assert!(
s.starts_with(' '),
"non-empty tokens must start with a space for safe \
cmdline concatenation; got {s:?}",
);
}
fn build_synthetic_staged_set(
names: &[&str],
) -> (
tempfile::TempDir,
PathBuf,
Vec<crate::vmm::builder::StagedScheduler>,
) {
let dir = tempfile::Builder::new()
.prefix("ktstr-assemble-test-")
.tempdir()
.unwrap();
let payload = dir.path().join("payload");
std::fs::write(&payload, b"payload-content").unwrap();
let staged: Vec<crate::vmm::builder::StagedScheduler> = names
.iter()
.map(|name| {
let bin = dir.path().join(format!("staged_bin_{name}"));
std::fs::write(&bin, format!("staged-content-{name}").as_bytes()).unwrap();
crate::vmm::builder::StagedScheduler {
name: (*name).to_string(),
binary: bin,
sched_args: vec![format!("--variant={name}")],
}
})
.collect();
(dir, payload, staged)
}
fn staged_extras_names_for(staged: &[crate::vmm::builder::StagedScheduler]) -> Vec<String> {
staged
.iter()
.map(|s| {
format!(
"{}/scheduler",
crate::test_support::staged::staged_scheduler_archive_dir(&s.name),
)
})
.collect()
}
#[test]
fn assemble_extras_and_key_emits_staged_binary_under_correct_archive_path() {
let (_tmp, payload, staged) = build_synthetic_staged_set(&["scx_foo", "scx_bar"]);
let names = staged_extras_names_for(&staged);
let (extras, _key) = assemble_extras_and_key(
payload.as_path(),
None,
None,
None,
&staged,
&names,
&[],
None,
false,
)
.unwrap();
let extras_names: Vec<&str> = extras.iter().map(|(n, _)| *n).collect();
assert!(
extras_names.contains(&"staging/schedulers/scx_foo/scheduler"),
"missing scx_foo at canonical archive path; got {extras_names:?}",
);
assert!(
extras_names.contains(&"staging/schedulers/scx_bar/scheduler"),
"missing scx_bar at canonical archive path; got {extras_names:?}",
);
}
#[test]
fn assemble_extras_and_key_preserves_staged_iteration_order_in_extras() {
let (_tmp, payload, staged) = build_synthetic_staged_set(&["alpha", "beta", "gamma"]);
let names = staged_extras_names_for(&staged);
let (extras, _key) = assemble_extras_and_key(
payload.as_path(),
None,
None,
None,
&staged,
&names,
&[],
None,
false,
)
.unwrap();
for (i, name) in ["alpha", "beta", "gamma"].iter().enumerate() {
let (entry_name, entry_path) = extras[i];
let expected_name = format!("staging/schedulers/{name}/scheduler");
assert_eq!(
entry_name, expected_name,
"extras[{i}] expected name '{expected_name}', got '{entry_name}'",
);
assert!(
entry_path
.to_string_lossy()
.ends_with(&format!("staged_bin_{name}")),
"extras[{i}] binary path '{}' does not match expected staged_bin_{name}",
entry_path.display(),
);
}
}
#[test]
fn assemble_extras_and_key_threads_staged_into_basekey_in_both_modes() {
let (_tmp, payload, staged) = build_synthetic_staged_set(&["mitosis_a"]);
let names = staged_extras_names_for(&staged);
let empty: Vec<crate::vmm::builder::StagedScheduler> = vec![];
let empty_names: Vec<String> = vec![];
let (_, key_with_staged_nonshell) = assemble_extras_and_key(
payload.as_path(),
None,
None,
None,
&staged,
&names,
&[],
None,
false,
)
.unwrap();
let (_, key_empty_nonshell) = assemble_extras_and_key(
payload.as_path(),
None,
None,
None,
&empty,
&empty_names,
&[],
None,
false,
)
.unwrap();
assert_ne!(
key_with_staged_nonshell, key_empty_nonshell,
"non-shell-mode BaseKey must reflect staged contribution",
);
let stub_busybox: &[u8] = b"#!/bin/sh\n";
let (_, key_with_staged_shell) = assemble_extras_and_key(
payload.as_path(),
None,
None,
None,
&staged,
&names,
&[],
Some(stub_busybox),
false,
)
.unwrap();
let (_, key_empty_shell) = assemble_extras_and_key(
payload.as_path(),
None,
None,
None,
&empty,
&empty_names,
&[],
Some(stub_busybox),
false,
)
.unwrap();
assert_ne!(
key_with_staged_shell, key_empty_shell,
"shell-mode BaseKey must reflect staged contribution",
);
assert_ne!(
key_with_staged_nonshell, key_with_staged_shell,
"shell-mode and non-shell-mode keys for same staged set \
must differ — confirms each arm calls its respective \
BaseKey constructor",
);
}