VMs and profiles
Every Provium test ultimately drives one or more guest VMs. This page covers the lifecycle: creating a VM, picking its profile, controlling boot, taking snapshots, and tearing down.
The exhaustive reference is on VM.
Creating a VM #
local vm = provium:
The first arg is the VM's name (per-scope unique). The second arg is a profile name from provium.toml. The VM is in Created state until you call :boot().
Two forms. provium:vm(name, profile) creates a VM in the current scope. provium:vm(name) looks one up by name, walking from the current scope to the file root. Inside a test() body, the current scope is the per-test scope; at file top-level, it's the file root. See labs and scope for the full rules.
Optional third arg is a sizing table — the two keys the scheduler and QEMU need before boot:
local vm = provium:
Everything else about a boot — kernel command line, determinism seeds, injected files — is passed to vm:boot(opts) instead:
vm:
The split is deliberate: memory and cpus size the VM, and the scheduler needs them when the VM is declared; the boot opts shape one particular boot and merge per field into the VM's pending boot options.
Booting #
local vm = provium::
:boot() returns self so you can chain. The VM is in Booted state on return. A vm_spawned event fires as soon as the agent has handshaken; consumers see it before :boot() returns.
For multi-VM tests, you can boot each individually:
local a = provium::
local b = provium::
Or batch-boot via the lab:
provium:
provium:
provium: -- boots both
The batch form is mainly useful when you've declared a topology in a fixture builder and want to bring it up atomically.
Profiles #
A profile is a [profiles.<name>] block in provium.toml:
[]
= "/build/peios/bzImage"
= "/build/peios/initrd.cpio.gz"
= "console=ttyS0 quiet"
= "peios"
Each profile names a (kernel, initrd, cmdline) tuple. A test picks which profile to use by name:
provium: -- uses [profiles.peios]
provium: -- uses [profiles.peios-debug]
You can have any number of profiles. Common patterns:
| Pattern | Profiles |
|---|---|
| Test against multiple kernel versions | peios-stable, peios-mainline |
| Compare optimised and debug builds | peios, peios-debug |
| Test pre/post a feature flag | peios-prefeatx, peios-postfeatx |
Multi-profile fixtures invalidate every cached fixture when any profile's kernel or initrd identifier changes — see fixtures and dependencies.
Lifecycle methods #
A VM's lifecycle: :boot() takes it from Created to Booted; :pause() / :resume() toggle between Booted and Paused; :reset() warm-reboots (still Booted); and :shutdown() or :power_button() end in Shutdown. The full state machine and per-method semantics are in the VM reference.
Operations against a VM in the wrong state error cleanly with a hint: VM not booted on a Created VM, VM is paused; use resume() on a Paused one, VM is shutdown; create a new one after shutdown.
You typically don't call :shutdown() explicitly. The harness's resource-graph walker tears every VM down at the appropriate scope boundary: test-scope VMs at the end of their test() body, file-scope VMs at file end. (reset_between_tests = true snapshots and restores instead — see labs and scope.)
Boot opts in detail #
The opts you'll use most often are memory and cpus (sizing, at creation), kernel_cmdline (per-VM loglevel, nokaslr, etc. — replaces the profile's cmdline; at boot), and files (inject config before init runs; at boot):
local vm = provium:
vm:
This is a subset — the full boot-opts table (types, defaults, rng_seed, initial_time) is in the VM reference.
Querying VM state #
The accessors you'll use most are vm:state() (returns "created", "booted", "paused", "shutdown", or "dead") and vm:is_quiescent() (true when there are no in-flight ops, open files, or open streams). The full accessor list — name, profile, cid, open_file_count, open_stream_count — is in the VM reference.
is_quiescent and the open-count accessors are useful for snapshot precondition asserts:
test
Snapshots #
local s = vm: -- writes to a tempfile
local s = vm: -- writes to that path
Returns a Snapshot userdata wrapping the path. Use it to:
- Restore later in the same test:
vm:shutdown(); vm:restore(s). - Inspect size:
s:size(). - Delete:
s:delete()(idempotent).
The snapshot file is what fixture builders return. If the snapshot fails because of an open stream, the error names the stream's creation site — close streams before snapshotting:
test
Restoring #
Restore from a Snapshot userdata or a bare path string:
vm:
vm: -- from snapshot userdata
vm: -- from path
The VM moves through Shutdown → Created → Booted (the restored state is already Booted). Restoring requires the VM to be in Created or Shutdown first.
Determinism patterns #
For tests that depend on randomness or wall-clock time, fix both at boot:
local vm = provium:
vm:
After boot, you can move time forward (or backward) with vm:clock():advance(N) — see Clock reference.
Pausing for inspection #
vm:pause() freezes the guest's vCPUs. Useful for:
- Time-sensitive tests where you need to read multiple bits of state without races.
- Snapshotting (the snapshot path will pause anyway, but explicit pause makes the test's intent clear).
vm:
local before = vm:
vm:
Reset and power-button #
vm:reset() warm-reboots the guest — same VM, same RAM image initially, then init re-runs. Stays in Booted, and does not return until the guest is answering again, so the next line of the test can use the VM straight away. A reset that returns is a guest that came back; one that does not is an error rather than a VM that quietly stops responding.
vm:power_button() sends ACPI power-button. The guest's init handles it as a graceful shutdown signal (typically: stop services, sync filesystems, kernel halts). Returns in Shutdown after confirmation. If the guest does not finish within 60 seconds after accepting the request, the call errors without forcing it off; see the VM reference.
test
Multi-VM topologies #
The two-VM pattern is the workhorse of networking tests:
local lan = provium:
local a = provium::
local b = provium::
lan:
a:
b:
test
For larger topologies, use sub-labs to keep names organised — see labs and scope.
See also #
- VM reference — every method, every option.
- Snapshot reference — snapshot/lab-snapshot userdata.
- provium.toml reference — profile configuration.
- Bridges and impairments — wiring VMs together.