use anyhow::Result;
use ktstr::assert::{AssertDetail, AssertResult, DetailKind};
use ktstr::scenario::ops::{CgroupDef, HoldSpec, Step, execute_steps};
use ktstr::test_support::{Scheduler, SchedulerSpec, sidecar_dir};
const KTSTR_SCHED: Scheduler =
Scheduler::new("ktstr_sched").binary(SchedulerSpec::Discover("scx-ktstr"));
fn failure_dump_path(test_name: &str) -> std::path::PathBuf {
sidecar_dir().join(format!("{test_name}.failure-dump.json"))
}
fn read_dump_or_fail(test_name: &str) -> Result<serde_json::Value> {
let dump_path = failure_dump_path(test_name);
let json = std::fs::read_to_string(&dump_path).map_err(|e| {
anyhow::anyhow!(
"failure dump file missing at {}: {e} — freeze coordinator did \
not write (no SCX_EXIT_ERROR_STALL latch fired, owned_accessor / \
dump_btf was None, or the file write failed silently)",
dump_path.display()
)
})?;
serde_json::from_str(&json).map_err(|e| {
anyhow::anyhow!(
"failure dump JSON at {} is malformed: {e}",
dump_path.display()
)
})
}
fn run_stalled_workload(ctx: &ktstr::scenario::Ctx) -> Result<AssertResult> {
let steps = vec![Step {
setup: vec![CgroupDef::named("cg_0").workers(ctx.workers_per_cgroup)].into(),
ops: vec![],
hold: HoldSpec::FULL,
}];
execute_steps(ctx, steps)
}
fn scenario_dsq_and_rq_walker_populates_failure_dump(
ctx: &ktstr::scenario::Ctx,
) -> Result<AssertResult> {
let mut result = run_stalled_workload(ctx)?;
let dump = read_dump_or_fail("vm_integration_dsq_and_rq_walker")?;
let dsq_states: &[serde_json::Value] = match dump.get("dsq_states") {
Some(s) => s
.as_array()
.map(|a| a.as_slice())
.ok_or_else(|| anyhow::anyhow!("dsq_states is present but not an array: {s}"))?,
None => &[],
};
if dsq_states.is_empty() {
let unavailable = dump
.get("scx_walker_unavailable")
.and_then(|v| v.as_str())
.unwrap_or("(no diagnostic)");
anyhow::bail!(
"dsq_states is empty (absent or zero-length). The walker either \
did not resolve BTF offsets, did not translate *scx_root, or \
the IDR walk yielded no DSQs. scx_walker_unavailable={unavailable:?}"
);
}
let rq_scx_states: &[serde_json::Value] = match dump.get("rq_scx_states") {
Some(s) => s
.as_array()
.map(|a| a.as_slice())
.ok_or_else(|| anyhow::anyhow!("rq_scx_states is present but not an array: {s}"))?,
None => &[],
};
if rq_scx_states.is_empty() {
anyhow::bail!(
"rq_scx_states is empty (absent or zero-length). Per-CPU rq->scx \
walk failed wholesale — every CPU's percpu translation errored \
or the offsets were unavailable."
);
}
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"scx walker captured {} DSQ entries and {} rq->scx entries from \
frozen-VM walk",
dsq_states.len(),
rq_scx_states.len(),
),
));
Ok(result)
}
fn scenario_perf_counters_capture_populates_dump(
ctx: &ktstr::scenario::Ctx,
) -> Result<AssertResult> {
let mut result = run_stalled_workload(ctx)?;
let dump = read_dump_or_fail("vm_integration_perf_counters_capture")?;
let vcpu_perf: &[serde_json::Value] = match dump.get("vcpu_perf_at_freeze") {
Some(v) => v
.as_array()
.map(|a| a.as_slice())
.ok_or_else(|| anyhow::anyhow!("vcpu_perf_at_freeze present but not an array: {v}"))?,
None => &[],
};
if vcpu_perf.is_empty() {
anyhow::bail!(
"vcpu_perf_at_freeze is empty (absent or zero-length). \
DumpContext::perf_capture was None — perf_event_open(exclude_host=1) \
unavailable on this host (kernel.perf_event_paranoid too \
restrictive, or capability missing). To run this test the host \
needs `sysctl kernel.perf_event_paranoid=2` or lower."
);
}
let populated: Vec<&serde_json::Value> =
vcpu_perf.iter().filter(|slot| slot.is_object()).collect();
if populated.is_empty() {
anyhow::bail!(
"vcpu_perf_at_freeze has {} entries but every slot is null \
(read(2) failed for every vCPU). Capture wiring may be broken: \
check perf_event_attr.exclude_host and that the per-vCPU fd \
remained valid through freeze.",
vcpu_perf.len(),
);
}
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"vcpu_perf_at_freeze: {}/{} vCPUs reported a non-null \
perf_event_open(exclude_host=1) sample at freeze",
populated.len(),
vcpu_perf.len(),
),
));
Ok(result)
}
fn scenario_event_counter_timeline_populates_dump(
ctx: &ktstr::scenario::Ctx,
) -> Result<AssertResult> {
let mut result = run_stalled_workload(ctx)?;
let dump = read_dump_or_fail("vm_integration_event_counter_timeline")?;
let timeline: &[serde_json::Value] = match dump.get("event_counter_timeline") {
Some(t) => t.as_array().map(|a| a.as_slice()).ok_or_else(|| {
anyhow::anyhow!("event_counter_timeline present but not an array: {t}")
})?,
None => &[],
};
if timeline.is_empty() {
anyhow::bail!(
"event_counter_timeline is empty (absent or zero-length). \
The monitor's per-tick capture either: did not run, \
could not resolve SCX_EV_* counter offsets from BTF, or the \
freeze coordinator's EventCounterCapture parameter was None. \
A real scx-ktstr run with --stall-after=1 must emit at least \
one sample over the watchdog window."
);
}
let non_object: Vec<&serde_json::Value> = timeline.iter().filter(|s| !s.is_object()).collect();
if !non_object.is_empty() {
anyhow::bail!(
"event_counter_timeline has {} non-object entries; \
EventCounterSample must serialize as a JSON object. \
Sample bad entry: {}",
non_object.len(),
non_object[0],
);
}
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"event_counter_timeline captured {} per-tick samples across \
the run window",
timeline.len(),
),
));
Ok(result)
}
fn scenario_sched_deadline_real_setattr(ctx: &ktstr::scenario::Ctx) -> Result<AssertResult> {
use ktstr::workload::{AffinityIntent, SchedPolicy, WorkType, WorkloadConfig, WorkloadHandle};
use std::time::Duration;
let config = WorkloadConfig {
num_workers: 1,
work_type: WorkType::SpinWait,
affinity: AffinityIntent::Inherit,
sched_policy: SchedPolicy::Deadline {
runtime: Duration::from_micros(500),
deadline: Duration::from_millis(1),
period: Duration::from_millis(10),
},
..Default::default()
};
let mut handle = WorkloadHandle::spawn(&config)?;
handle.start();
std::thread::sleep(ctx.duration);
let reports = handle.stop_and_collect();
let mut result = AssertResult::pass();
if reports.is_empty() {
result.passed = false;
result.details.push(AssertDetail::new(
DetailKind::Other,
"SCHED_DEADLINE worker produced no report — sched_setattr likely \
rejected the params"
.to_string(),
));
return Ok(result);
}
let r = &reports[0];
if !r.completed {
result.passed = false;
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"SCHED_DEADLINE worker reported completed=false (sentinel) — \
sched_setattr returned an error or the worker died before \
the work loop. exit_info={:?}, work_units={}",
r.exit_info, r.work_units,
),
));
return Ok(result);
}
if r.work_units == 0 {
result.passed = false;
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"SCHED_DEADLINE worker reported work_units=0 — the SCHED_DL \
band did not grant any run time within the declared period. \
wall_time_ns={}, cpu_time_ns={}",
r.wall_time_ns, r.cpu_time_ns,
),
));
return Ok(result);
}
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"SCHED_DEADLINE worker completed cleanly: tid={}, work_units={}, \
wall_time_ns={}, cpu_time_ns={} — sched_setattr(2) syscall \
path verified end-to-end on real kernel",
r.tid, r.work_units, r.wall_time_ns, r.cpu_time_ns,
),
));
Ok(result)
}
fn scenario_failure_dump_trigger_minimal_invariants(
ctx: &ktstr::scenario::Ctx,
) -> Result<AssertResult> {
let mut result = run_stalled_workload(ctx)?;
let dump = read_dump_or_fail("vm_integration_failure_dump_trigger")?;
let schema = dump
.get("schema")
.and_then(|s| s.as_str())
.ok_or_else(|| anyhow::anyhow!("dump JSON missing top-level `schema` field"))?;
if schema != "single" {
anyhow::bail!(
"schema discriminant is {schema:?}, expected \"single\". \
A rename or refactor of the schema constant must update \
every consumer that pins this string."
);
}
let maps = dump
.get("maps")
.and_then(|m| m.as_array())
.ok_or_else(|| anyhow::anyhow!("dump JSON missing top-level `maps` array"))?;
if maps.is_empty() {
anyhow::bail!(
"dump JSON `maps` array is empty — BPF map enumeration did not \
find a single map after the SCX_EXIT_ERROR_STALL freeze. The \
scheduler always loads at least the .bss + arena maps; an \
empty map list means dump_state's IDR walk is broken."
);
}
let vcpu_regs = dump
.get("vcpu_regs")
.and_then(|v| v.as_array())
.ok_or_else(|| anyhow::anyhow!("dump JSON missing top-level `vcpu_regs` array"))?;
if vcpu_regs.is_empty() {
anyhow::bail!(
"dump JSON `vcpu_regs` array is empty — freeze rendezvous \
collected no vCPU snapshots. Either the rendezvous timed out \
before any vCPU completed handle_freeze, or the regs-attach \
callback was never registered."
);
}
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"failure-dump trigger pipeline produced schema={schema:?}, \
{} maps, {} vcpu_regs entries — full-stack capture path \
verified end-to-end",
maps.len(),
vcpu_regs.len(),
),
));
Ok(result)
}
const KTSTR_DISK_DEFAULT: ktstr::prelude::DiskConfig = ktstr::prelude::DiskConfig {
capacity_mb: 256,
filesystem: ktstr::prelude::Filesystem::Raw,
throttle: ktstr::prelude::DiskThrottle {
iops: None,
bytes_per_sec: None,
iops_burst_capacity: None,
bytes_burst_capacity: None,
},
read_only: false,
name: None,
no_auto_mount: false,
};
const KTSTR_DISK_READ_ONLY: ktstr::prelude::DiskConfig = ktstr::prelude::DiskConfig {
capacity_mb: 256,
filesystem: ktstr::prelude::Filesystem::Raw,
throttle: ktstr::prelude::DiskThrottle {
iops: None,
bytes_per_sec: None,
iops_burst_capacity: None,
bytes_burst_capacity: None,
},
read_only: true,
name: None,
no_auto_mount: false,
};
fn scenario_disk_default_appears_at_dev_vda(_ctx: &ktstr::scenario::Ctx) -> Result<AssertResult> {
use std::fs::OpenOptions;
use std::os::unix::fs::FileTypeExt;
use std::os::unix::io::AsRawFd;
let path = std::path::Path::new("/dev/vda");
let metadata = std::fs::metadata(path).map_err(|e| {
anyhow::anyhow!(
"/dev/vda missing in guest: {e}. The virtio-blk device was \
not attached, the guest kernel does not have CONFIG_VIRTIO_BLK, \
or the MMIO probe failed before devtmpfs populated /dev/vda."
)
})?;
let ftype = metadata.file_type();
if !ftype.is_block_device() {
anyhow::bail!(
"/dev/vda exists but is not a block device (file_type={ftype:?}). \
devtmpfs created the node but the underlying device is not \
a real virtio-blk; check the kernel-side virtio probe path."
);
}
let file = OpenOptions::new()
.read(true)
.open(path)
.map_err(|e| anyhow::anyhow!("open /dev/vda for capacity probe: {e}"))?;
let mut size_bytes: u64 = 0;
let rc = unsafe { libc::ioctl(file.as_raw_fd(), 0x80081272, &mut size_bytes as *mut u64) };
if rc != 0 {
let errno = std::io::Error::last_os_error();
anyhow::bail!(
"BLKGETSIZE64 on /dev/vda returned {rc} (errno={errno}). The \
kernel did not surface a capacity through the virtio config \
space — possible config-space layout mismatch."
);
}
let expected_bytes = (KTSTR_DISK_DEFAULT.capacity_mb as u64) << 20;
if size_bytes != expected_bytes {
anyhow::bail!(
"BLKGETSIZE64 on /dev/vda reported {size_bytes} bytes; \
expected {expected_bytes} ({} MB). The host advertised \
a different capacity than the test configured.",
KTSTR_DISK_DEFAULT.capacity_mb,
);
}
let mut result = AssertResult::pass();
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"/dev/vda is a block device with capacity {size_bytes} bytes \
({} MB), matching the configured DiskConfig",
KTSTR_DISK_DEFAULT.capacity_mb,
),
));
Ok(result)
}
fn scenario_disk_write_read_roundtrip(_ctx: &ktstr::scenario::Ctx) -> Result<AssertResult> {
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
const SECTOR_SIZE: usize = 512;
const PATTERN_BYTE: u8 = 0xA5;
let path = std::path::Path::new("/dev/vda");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|e| {
anyhow::anyhow!(
"open /dev/vda for read+write: {e}. The disk should be \
attached read-write by default; if the host advertised \
VIRTIO_BLK_F_RO unexpectedly the kernel would refuse \
O_WRONLY on the device node."
)
})?;
let pattern = [PATTERN_BYTE; SECTOR_SIZE];
file.seek(SeekFrom::Start(0))
.map_err(|e| anyhow::anyhow!("seek to sector 0 for write: {e}"))?;
file.write_all(&pattern)
.map_err(|e| anyhow::anyhow!("write pattern to sector 0: {e}"))?;
file.sync_all()
.map_err(|e| anyhow::anyhow!("fsync /dev/vda after write: {e}"))?;
let mut readback = OpenOptions::new()
.read(true)
.open(path)
.map_err(|e| anyhow::anyhow!("re-open /dev/vda for readback: {e}"))?;
let mut buf = [0u8; SECTOR_SIZE];
readback
.seek(SeekFrom::Start(0))
.map_err(|e| anyhow::anyhow!("seek to sector 0 for read: {e}"))?;
readback
.read_exact(&mut buf)
.map_err(|e| anyhow::anyhow!("read sector 0: {e}"))?;
if buf != pattern {
let first_bad = buf
.iter()
.zip(pattern.iter())
.position(|(a, b)| a != b)
.unwrap_or(0);
anyhow::bail!(
"/dev/vda sector 0 readback mismatch: byte {first_bad} \
read=0x{:02X} expected=0x{:02X}. The first 16 bytes \
read back as {:02X?}, expected {:02X?}.",
buf[first_bad],
pattern[first_bad],
&buf[..16.min(buf.len())],
&pattern[..16.min(pattern.len())],
);
}
let mut result = AssertResult::pass();
result.details.push(AssertDetail::new(
DetailKind::Other,
format!(
"{SECTOR_SIZE}-byte pattern written to sector 0 round-tripped \
cleanly through virtio-blk write+fsync+read"
),
));
Ok(result)
}
fn scenario_disk_read_only_rejects_write(_ctx: &ktstr::scenario::Ctx) -> Result<AssertResult> {
use std::fs::OpenOptions;
let path = std::path::Path::new("/dev/vda");
{
let _r = OpenOptions::new()
.read(true)
.open(path)
.map_err(|e| anyhow::anyhow!("open /dev/vda read-only: {e}"))?;
}
let result = OpenOptions::new().write(true).open(path);
match result {
Ok(_) => {
anyhow::bail!(
"open(/dev/vda, O_WRONLY) succeeded on a read-only disk. \
Either VIRTIO_BLK_F_RO was not advertised by the host, \
the guest driver did not honor it, or the device's \
read-only gate is broken."
);
}
Err(e) => {
let raw_errno = e.raw_os_error();
let expected = libc::EROFS;
if raw_errno != Some(expected) {
anyhow::bail!(
"open(/dev/vda, O_WRONLY) failed with errno={raw_errno:?}, \
expected EROFS ({expected}). The kernel rejected the \
open but for a different reason than read-only — check \
for ENODEV (device missing) or EBUSY (concurrent open)."
);
}
}
}
let mut result = AssertResult::pass();
result.details.push(AssertDetail::new(
DetailKind::Other,
"open(/dev/vda, O_WRONLY) returned EROFS as expected — \
VIRTIO_BLK_F_RO is honored end-to-end"
.to_string(),
));
Ok(result)
}
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_DSQ_RQ_WALKER: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_dsq_and_rq_walker",
func: scenario_dsq_and_rq_walker_populates_failure_dump,
scheduler: &KTSTR_SCHED,
extra_sched_args: &["--stall-after=1"],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_secs(10),
expect_err: true,
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_PERF_COUNTERS: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_perf_counters_capture",
func: scenario_perf_counters_capture_populates_dump,
scheduler: &KTSTR_SCHED,
extra_sched_args: &["--stall-after=1"],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_secs(10),
expect_err: true,
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_EVENT_TIMELINE: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_event_counter_timeline",
func: scenario_event_counter_timeline_populates_dump,
scheduler: &KTSTR_SCHED,
extra_sched_args: &["--stall-after=1"],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_secs(15),
expect_err: true,
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_DEADLINE: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_sched_deadline",
func: scenario_sched_deadline_real_setattr,
scheduler: &KTSTR_SCHED,
extra_sched_args: &[],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_millis(500),
expect_err: false,
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_DUMP_TRIGGER: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_failure_dump_trigger",
func: scenario_failure_dump_trigger_minimal_invariants,
scheduler: &KTSTR_SCHED,
extra_sched_args: &["--stall-after=1"],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_secs(10),
expect_err: true,
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
const CARGO_KTSTR_BINARY: &str = env!("CARGO_BIN_EXE_cargo-ktstr");
fn linux_source_dir() -> std::path::PathBuf {
let crate_root = env!("CARGO_MANIFEST_DIR");
std::path::PathBuf::from(crate_root)
.join("..")
.join("linux")
}
fn drive_ktstr_test(scenario_name: &str) {
let source = linux_source_dir();
assert!(
source.is_dir(),
"../linux source tree missing — VM tests need a kernel source \
tree. Expected: {}",
source.display(),
);
let output = std::process::Command::new(CARGO_KTSTR_BINARY)
.arg("ktstr")
.arg("test")
.arg("--kernel")
.arg(&source)
.arg("--")
.arg("--filter")
.arg(scenario_name)
.output()
.expect("spawn cargo-ktstr test");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"cargo ktstr test --filter {scenario_name} failed (exit={:?})\n\
STDOUT:\n{stdout}\n\nSTDERR:\n{stderr}",
output.status.code(),
);
}
#[test]
#[ignore = "long-running VM integration test (~30s); requires KVM, \
../linux, scx-ktstr binary, kernel BTF. Run via \
`cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_dsq_and_rq_walker`."]
fn vm_integration_dsq_and_rq_walker() {
drive_ktstr_test("vm_integration_dsq_and_rq_walker");
}
#[test]
#[ignore = "long-running VM integration test (~30s); requires KVM, \
../linux, scx-ktstr binary, AND kernel.perf_event_paranoid \
<= 2 on the host (CAP_PERFMON or root). Run via \
`cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_perf_counters_capture`."]
fn vm_integration_perf_counters_capture() {
drive_ktstr_test("vm_integration_perf_counters_capture");
}
#[test]
#[ignore = "long-running VM integration test (~45s, longer duration \
for timeline samples); requires KVM, ../linux, \
scx-ktstr. Run via `cargo nextest run --run-ignored all` \
or `cargo ktstr test --kernel ../linux \
--filter vm_integration_event_counter_timeline`."]
fn vm_integration_event_counter_timeline() {
drive_ktstr_test("vm_integration_event_counter_timeline");
}
#[test]
#[ignore = "VM integration test (~10s); requires KVM, ../linux, \
scx-ktstr, AND guest kernel with CONFIG_SCHED_DEADLINE. \
Run via `cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_sched_deadline`."]
fn vm_integration_sched_deadline() {
drive_ktstr_test("vm_integration_sched_deadline");
}
#[test]
#[ignore = "long-running VM integration test (~30s); requires KVM, \
../linux, scx-ktstr. Run via \
`cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_failure_dump_trigger`."]
fn vm_integration_failure_dump_trigger() {
drive_ktstr_test("vm_integration_failure_dump_trigger");
}
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_DISK_DEFAULT: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_disk_default_appears",
func: scenario_disk_default_appears_at_dev_vda,
scheduler: &KTSTR_SCHED,
extra_sched_args: &[],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_millis(500),
expect_err: false,
disk: Some(KTSTR_DISK_DEFAULT),
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_DISK_ROUNDTRIP: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_disk_write_read_roundtrip",
func: scenario_disk_write_read_roundtrip,
scheduler: &KTSTR_SCHED,
extra_sched_args: &[],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_millis(500),
expect_err: false,
disk: Some(KTSTR_DISK_DEFAULT),
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[ktstr::__private::linkme::distributed_slice(ktstr::test_support::KTSTR_TESTS)]
#[linkme(crate = ktstr::__private::linkme)]
static __KTSTR_ENTRY_DISK_READ_ONLY: ktstr::test_support::KtstrTestEntry =
ktstr::test_support::KtstrTestEntry {
name: "vm_integration_disk_read_only_rejects_write",
func: scenario_disk_read_only_rejects_write,
scheduler: &KTSTR_SCHED,
extra_sched_args: &[],
watchdog_timeout: std::time::Duration::from_secs(3),
duration: std::time::Duration::from_millis(500),
expect_err: false,
disk: Some(KTSTR_DISK_READ_ONLY),
..ktstr::test_support::KtstrTestEntry::DEFAULT
};
#[test]
#[ignore = "VM integration test (~10s); requires KVM, ../linux, \
CONFIG_VIRTIO_BLK in guest kernel. Run via \
`cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_disk_default_appears`."]
fn vm_integration_disk_default_appears() {
drive_ktstr_test("vm_integration_disk_default_appears");
}
#[test]
#[ignore = "VM integration test (~10s); requires KVM, ../linux, \
CONFIG_VIRTIO_BLK in guest kernel. Run via \
`cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_disk_write_read_roundtrip`."]
fn vm_integration_disk_write_read_roundtrip() {
drive_ktstr_test("vm_integration_disk_write_read_roundtrip");
}
#[test]
#[ignore = "VM integration test (~10s); requires KVM, ../linux, \
CONFIG_VIRTIO_BLK in guest kernel. Run via \
`cargo nextest run --run-ignored all` or \
`cargo ktstr test --kernel ../linux \
--filter vm_integration_disk_read_only_rejects_write`."]
fn vm_integration_disk_read_only_rejects_write() {
drive_ktstr_test("vm_integration_disk_read_only_rejects_write");
}