use std::path::{Path, PathBuf};
use std::time::Duration;
use super::entry::KtstrTestEntry;
pub(crate) fn verbose() -> bool {
std::env::var("RUST_BACKTRACE")
.map(|v| v == "1" || v == "full")
.unwrap_or(false)
}
pub(crate) const KTSTR_TEST_SHM_SIZE: u64 = 16 * 1024 * 1024;
pub(crate) fn config_file_parts(entry: &KtstrTestEntry) -> Option<(String, PathBuf, String)> {
let config_path = entry.scheduler.config_file()?;
let file_name = Path::new(config_path)
.file_name()
.and_then(|n| n.to_str())
.expect("config_file must have a valid filename");
let archive_path = format!("include-files/{file_name}");
let guest_path = format!("/include-files/{file_name}");
Some((archive_path, PathBuf::from(config_path), guest_path))
}
pub(crate) fn build_cmdline_extra(entry: &KtstrTestEntry) -> String {
let mut parts = vec!["iomem=relaxed".to_string()];
for s in entry.scheduler.sysctls() {
parts.push(format!("sysctl.{}={}", s.key, s.value));
}
for &karg in entry.scheduler.kargs() {
parts.push(karg.to_string());
}
if let Ok(bt) = std::env::var("RUST_BACKTRACE") {
parts.push(format!("RUST_BACKTRACE={bt}"));
}
if let Ok(log) = std::env::var("RUST_LOG") {
parts.push(format!("RUST_LOG={log}"));
}
parts.join(" ")
}
pub(crate) fn resolve_vm_topology(
entry: &KtstrTestEntry,
topo: Option<&super::topo::TopoOverride>,
) -> (crate::vmm::topology::Topology, u32) {
match topo {
Some(t) => (crate::vmm::topology::Topology::from(t), t.memory_mb),
None => {
let cpus = entry.topology.total_cpus();
let mem = (cpus * 64).max(256).max(entry.memory_mb);
(entry.topology, mem)
}
}
}
pub(crate) fn append_base_sched_args(entry: &KtstrTestEntry, args: &mut Vec<String>) {
if let Some(cgroup_path) = entry.scheduler.cgroup_parent() {
args.push("--cell-parent-cgroup".to_string());
args.push(cgroup_path.to_string());
}
args.extend(entry.scheduler.sched_args().iter().map(|s| s.to_string()));
args.extend(entry.extra_sched_args.iter().map(|s| s.to_string()));
}
pub(crate) const KTSTR_VM_TIMEOUT: Duration = Duration::from_secs(60);
#[allow(clippy::too_many_arguments)]
pub(crate) fn build_vm_builder_base(
entry: &KtstrTestEntry,
kernel: &Path,
ktstr_bin: &Path,
scheduler: Option<&Path>,
vm_topology: crate::vmm::topology::Topology,
memory_mb: u32,
cmdline_extra: &str,
guest_args: &[String],
no_perf_mode: bool,
) -> crate::vmm::KtstrVmBuilder {
let mut builder = crate::vmm::KtstrVm::builder()
.kernel(kernel)
.init_binary(ktstr_bin)
.with_topology(vm_topology)
.memory_deferred_min(memory_mb)
.cmdline(cmdline_extra)
.shm_size(KTSTR_TEST_SHM_SIZE)
.run_args(guest_args)
.timeout(KTSTR_VM_TIMEOUT)
.no_perf_mode(no_perf_mode);
if let Some(sched_path) = scheduler {
builder = builder.scheduler_binary(sched_path);
}
if let Ok(probe_path) = std::env::var("KTSTR_JEMALLOC_PROBE_BINARY")
&& !probe_path.is_empty()
{
builder = builder.jemalloc_probe_binary(std::path::PathBuf::from(probe_path));
}
if let Ok(worker_path) = std::env::var("KTSTR_JEMALLOC_ALLOC_WORKER_BINARY")
&& !worker_path.is_empty()
{
builder = builder.jemalloc_alloc_worker_binary(std::path::PathBuf::from(worker_path));
}
for bpf_write in entry.bpf_map_write {
builder =
builder.bpf_map_write(bpf_write.map_name_suffix, bpf_write.offset, bpf_write.value);
}
builder.watchdog_timeout(entry.watchdog_timeout)
}
#[cfg(test)]
mod tests {
use super::super::entry::Scheduler;
use super::super::payload::Payload;
use super::*;
#[test]
fn config_file_parts_nested_path() {
static SCHED: Scheduler = Scheduler::new("cfg").config_file("configs/my_sched.toml");
static SCHED_PAYLOAD: Payload = Payload::from_scheduler(&SCHED);
let entry = KtstrTestEntry {
name: "cfg_test",
scheduler: &SCHED_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let (archive, host, guest) = config_file_parts(&entry).unwrap();
assert_eq!(archive, "include-files/my_sched.toml");
assert_eq!(host, PathBuf::from("configs/my_sched.toml"));
assert_eq!(guest, "/include-files/my_sched.toml");
}
#[test]
fn config_file_parts_bare_filename() {
static SCHED: Scheduler = Scheduler::new("cfg").config_file("config.toml");
static SCHED_PAYLOAD: Payload = Payload::from_scheduler(&SCHED);
let entry = KtstrTestEntry {
name: "cfg_bare",
scheduler: &SCHED_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let (archive, host, guest) = config_file_parts(&entry).unwrap();
assert_eq!(archive, "include-files/config.toml");
assert_eq!(host, PathBuf::from("config.toml"));
assert_eq!(guest, "/include-files/config.toml");
}
#[test]
fn config_file_parts_none_when_unset() {
let entry = KtstrTestEntry {
name: "no_cfg",
..KtstrTestEntry::DEFAULT
};
assert!(config_file_parts(&entry).is_none());
}
use super::super::entry::{KtstrTestEntry, Sysctl};
use super::super::test_helpers::{EnvVarGuard, lock_env};
#[test]
fn build_cmdline_extra_includes_iomem_relaxed_by_default() {
let _lock = lock_env();
let _env_bt = EnvVarGuard::remove("RUST_BACKTRACE");
let _env_log = EnvVarGuard::remove("RUST_LOG");
let entry = KtstrTestEntry {
name: "cmdline_test",
..KtstrTestEntry::DEFAULT
};
let out = build_cmdline_extra(&entry);
assert_eq!(out, "iomem=relaxed");
}
#[test]
fn build_cmdline_extra_appends_sysctls_kargs() {
let _lock = lock_env();
let _env_bt = EnvVarGuard::remove("RUST_BACKTRACE");
let _env_log = EnvVarGuard::remove("RUST_LOG");
static SYSCTLS: &[Sysctl] = &[Sysctl::new("kernel.foo", "1")];
static SCHED: Scheduler = Scheduler::new("s").sysctls(SYSCTLS).kargs(&["quiet"]);
static SCHED_PAYLOAD: Payload = Payload::from_scheduler(&SCHED);
let entry = KtstrTestEntry {
name: "cmd",
scheduler: &SCHED_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let out = build_cmdline_extra(&entry);
assert_eq!(out, "iomem=relaxed sysctl.kernel.foo=1 quiet");
}
#[test]
fn build_cmdline_extra_propagates_rust_env() {
let _lock = lock_env();
let _env_bt = EnvVarGuard::set("RUST_BACKTRACE", "1");
let _env_log = EnvVarGuard::set("RUST_LOG", "debug");
let entry = KtstrTestEntry {
name: "cmd",
..KtstrTestEntry::DEFAULT
};
let out = build_cmdline_extra(&entry);
assert!(
out.contains("RUST_BACKTRACE=1"),
"expected RUST_BACKTRACE propagation: {out}"
);
assert!(
out.contains("RUST_LOG=debug"),
"expected RUST_LOG propagation: {out}"
);
}
#[test]
fn resolve_vm_topology_override_is_verbatim() {
let entry = KtstrTestEntry {
name: "topo_test",
..KtstrTestEntry::DEFAULT
};
let over = super::super::topo::TopoOverride {
numa_nodes: 2,
llcs: 4,
cores: 8,
threads: 2,
memory_mb: 4096,
};
let (topo, mem) = resolve_vm_topology(&entry, Some(&over));
assert_eq!(mem, 4096);
assert_eq!(topo.llcs, 4);
assert_eq!(topo.cores_per_llc, 8);
assert_eq!(topo.threads_per_core, 2);
assert_eq!(topo.numa_nodes, 2);
}
#[test]
fn resolve_vm_topology_none_floors_memory_at_256() {
let entry = KtstrTestEntry {
name: "tiny",
memory_mb: 0,
..KtstrTestEntry::DEFAULT
};
let (_topo, mem) = resolve_vm_topology(&entry, None);
assert_eq!(mem, 256, "memory floor = 256 MB, got {mem}");
}
#[test]
fn resolve_vm_topology_none_honors_entry_memory_mb() {
let entry = KtstrTestEntry {
name: "mem",
memory_mb: 8192,
..KtstrTestEntry::DEFAULT
};
let (_topo, mem) = resolve_vm_topology(&entry, None);
assert_eq!(mem, 8192);
}
#[test]
fn append_base_sched_args_empty_when_none_set() {
let entry = KtstrTestEntry {
name: "nosched",
..KtstrTestEntry::DEFAULT
};
let mut args = Vec::new();
append_base_sched_args(&entry, &mut args);
assert!(args.is_empty(), "no sched args expected: {args:?}");
}
#[test]
fn append_base_sched_args_includes_cgroup_parent_and_sched_args() {
use super::super::entry::CgroupPath;
static CG: CgroupPath = CgroupPath::new("/sys/fs/cgroup/ktstr");
static SCHED: Scheduler = Scheduler::new("s")
.cgroup_parent("/sys/fs/cgroup/ktstr")
.sched_args(&["-v", "--flag"]);
static SCHED_PAYLOAD: Payload = Payload::from_scheduler(&SCHED);
let _ = &CG;
let entry = KtstrTestEntry {
name: "sched",
scheduler: &SCHED_PAYLOAD,
extra_sched_args: &["--extra"],
..KtstrTestEntry::DEFAULT
};
let mut args = Vec::new();
append_base_sched_args(&entry, &mut args);
assert_eq!(
args,
vec![
"--cell-parent-cgroup".to_string(),
"/sys/fs/cgroup/ktstr".to_string(),
"-v".to_string(),
"--flag".to_string(),
"--extra".to_string(),
],
);
}
#[test]
fn build_vm_builder_base_propagates_kernel_path() {
let entry = KtstrTestEntry {
name: "vmb_kernel_path",
..KtstrTestEntry::DEFAULT
};
let exe = crate::resolve_current_exe().unwrap();
let missing_kernel =
PathBuf::from("/nonexistent/build_vm_builder_base_test_kernel.bzImage");
let result = build_vm_builder_base(
&entry,
&missing_kernel,
&exe,
None,
crate::vmm::topology::Topology::new(1, 1, 1, 1),
256,
"",
&["run".to_string()],
true,
)
.build();
let err = match result {
Ok(_) => panic!("builder.build() unexpectedly succeeded for missing kernel"),
Err(e) => e,
};
let msg = format!("{err}");
assert!(
msg.contains("kernel not found"),
"expected kernel not found error, got: {msg}",
);
assert!(
msg.contains("build_vm_builder_base_test_kernel"),
"expected the fake kernel path to appear in the error, got: {msg}",
);
}
#[test]
fn build_vm_builder_base_propagates_topology_validation() {
let entry = KtstrTestEntry {
name: "vmb_topology",
..KtstrTestEntry::DEFAULT
};
let exe = crate::resolve_current_exe().unwrap();
let bad_topology = crate::vmm::topology::Topology {
llcs: 0,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let result = build_vm_builder_base(
&entry,
&exe,
&exe,
None,
bad_topology,
256,
"",
&["run".to_string()],
true,
)
.build();
let err = match result {
Ok(_) => panic!("builder.build() unexpectedly succeeded for zero-llcs topology"),
Err(e) => e,
};
let msg = format!("{err}");
assert!(
msg.contains("llcs must be > 0"),
"expected topology validation error, got: {msg}",
);
}
#[test]
fn build_vm_builder_base_propagates_scheduler_binary() {
let entry = KtstrTestEntry {
name: "vmb_scheduler",
..KtstrTestEntry::DEFAULT
};
let exe = crate::resolve_current_exe().unwrap();
let missing_scheduler = PathBuf::from("/nonexistent/build_vm_builder_base_test_scheduler");
let result = build_vm_builder_base(
&entry,
&exe,
&exe,
Some(&missing_scheduler),
crate::vmm::topology::Topology::new(1, 1, 1, 1),
256,
"",
&["run".to_string()],
true,
)
.build();
let err = match result {
Ok(_) => panic!("builder.build() unexpectedly succeeded for missing scheduler"),
Err(e) => e,
};
let msg = format!("{err}");
assert!(
msg.contains("scheduler binary not found"),
"expected scheduler binary error, got: {msg}",
);
assert!(
msg.contains("build_vm_builder_base_test_scheduler"),
"expected the fake scheduler path to appear, got: {msg}",
);
}
}