# vmrunner
A utility to enable running tests, or components of integration tests specifically, in a micro-vm.
## Supported hosts and limitations
`vmrunner` is currently intended for Linux integration tests that launch Linux guests with
`libkrun`. The tested path is Fedora Linux on `x86_64` with a recent kernel and KVM enabled.
Minimum practical host requirements:
* Linux with KVM support. `/dev/kvm` must exist and be accessible to the user running the tests
(for example through the host's `kvm` group or equivalent permissions).
* Kernel support for unprivileged user and network namespaces. The default `#[vmrunner::test]`
wrapper uses `unshare` to enter an isolated user+network namespace before preparing VM state.
* TUN/TAP support (`/dev/net/tun`) when tests opt into host-visible `tun-rs` TAP-backed networking.
The default harness network uses libkrun Unix-stream sockets and an in-process L2 switch instead
of Linux bridges or `krun_add_net_tap`.
* `libkrun` and its runtime dependencies installed for the host architecture.
* `libkrunfw`, the Linux kernel firmware shared object loaded by `libkrun`.
* A prepared Linux guest root filesystem. For `#[vmrunner::test(system = "fedora")]`, the consuming
crate's build script must set `KRUN_TEST_BASE_DIR` to an unpacked rootfs path. Tests can also pass an
explicit unpacked rootfs with `system_rootfs = "..."`.
Known usage limitations:
* Linux hosts only for the default runner path. The harness code contains a few macOS-specific
`libkrun` settings, but `vmrunner`'s default namespace setup depends on Linux `unshare`, user
namespaces, network namespaces, and TAP semantics.
* The `#[vmrunner::test(system = "fedora")]` macro remains the built-in shorthand for
`KRUN_TEST_BASE_DIR`. Callers can provide an unpacked rootfs directly with `system_rootfs = "..."`.
* Requires Rust 1.85 or newer, matching the crate's `rust-version` and Edition 2024 manifest.
* Run VM tests from a normal user account that is allowed to use KVM and unprivileged namespaces.
Running inside containers, CI sandboxes, nested virtualization, or hardened distributions may
require extra host configuration or may not work at all.
* VM launch uses `fork` and then enters `libkrun`; avoid doing manual namespace setup or VM launch
from already-complex multithreaded test processes. The macro-generated wrapper performs namespace
setup at the start of the test to avoid common Tokio/threading pitfalls.
## The Magic
It takes `std::env::current_exe()` and uses a provided rootfs to execute the current process again inside the VM, mapping
the specific binary into the VM. For Linux tests, the default macro still uses `unshare` to prepare an isolated user/network
namespace before VM setup. The harness network itself uses libkrun Unix-stream sockets by default; host-visible TAP/feth
uplinks are available through the optional `tun-rs` backend.
A typical test uses `#[vmrunner::test(...)]` instead of `#[test]`. The macro builds a
`TestSetup` before calling the test body; VM harness code can then use the setup's per-test
system rootfs path when launching the guest.
```rust,no_run
use vmrunner::TestSetup;
#[vmrunner::test(system = "fedora")]
fn guest_smoke_test(setup: TestSetup) -> anyhow::Result<()> {
assert_eq!(setup.test_name(), "guest_smoke_test");
println!("guest rootfs: {}", setup.system_rootfs_path().display());
// Launch the VM or prepare guest files using setup.system_rootfs_path().
Ok(())
}
```
The generated wrapper is roughly equivalent to this expanded form:
```rust,no_run
use vmrunner::TestSetup;
fn __vmrunner_inner_guest_smoke_test(setup: TestSetup) -> anyhow::Result<()> {
assert_eq!(setup.test_name(), "guest_smoke_test");
println!("guest rootfs: {}", setup.system_rootfs_path().display());
Ok(())
}
#[test]
fn guest_smoke_test() -> anyhow::Result<()> {
if vmrunner::run_current_test_in_unshare_child("guest_smoke_test")
.expect("vmrunner failed to run test in user+network namespace")
{
return Ok(());
}
let vmrunner_setup = TestSetup::new_with_isolated_system_rootfs(
"guest_smoke_test",
std::env!("KRUN_TEST_BASE_DIR"),
)
.expect("vmrunner failed to create per-test system rootfs state directory");
__vmrunner_inner_guest_smoke_test(vmrunner_setup)
}
```