vmrunner 0.0.1

micro-vm runner for testcases that require root or invasive IO
Documentation

vmrunner

A utility to enable running tests, or components of integration tests specifically, in a micro-vm.

Host Requirements

  • mkosi - create images from a rootfs
  • libkrunfw - the linux kernel compiled as an .so / shared object
  • tap/tun - support for overlay networks in your kernel

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. To create an overlay network, we need to become root on the host, for which we use unshare.

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.

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:

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_ROOTFS"),
    )
    .expect("vmrunner failed to create per-test system rootfs state directory");

    __vmrunner_inner_guest_smoke_test(vmrunner_setup)
}