Project structure
Provium has no scaffolding command. A project is whatever directory provium.toml lives in. This page documents what each piece does.
Directory layout #
my-project/
provium.toml # Required. Profiles + scan roots.
tests/ # One of the directories listed in `roots`.
smoke.test.lua # Run by `provium`.
networking/
partition.test.lua
uplink.test.lua
helpers/
assert_pingable.lua # `require("helpers.assert_pingable")`
fixtures/
booted-pair.fixture.lua # Built once, restored per test.
~/.cache/provium/
fixtures/ # Per-user fixture cache.
<hash>.snap # Single-VM fixture snapshots.
<hash>.lab/ # Multi-VM lab-fixture directories.
rerun.json # `--rerun-failed` state.
The only required file is provium.toml. Tests, helpers, and fixtures all live under whatever directory you list in [provium].roots.
provium.toml #
The configuration file at the project root. Two sections:
[]
= ["tests", "vendor/upstream-tests"]
= "/var/cache/provium/fixtures" # optional
= "20G" # optional
[]
= "/build/peios/bzImage"
= "/build/peios/initrd.cpio.gz"
= "console=ttyS0 quiet"
= "peios"
[]
= "/build/peios-debug/bzImage"
= "/build/peios-debug/initrd.cpio.gz"
= "console=ttyS0 debug loglevel=7"
[profiles.<name>] blocks declare named (kernel, initrd, cmdline) tuples. Test code looks them up by name: provium:vm("a", "peios") boots the VM using [profiles.peios]. You can have any number of profiles; tests pick whichever they need.
[provium].roots is the list of directories scanned for *.test.lua files. It also controls where require("helper.module") resolves; helpers/foo.lua under any root is reachable as require("helpers.foo").
cache_dir and cache_max_size configure where fixture snapshots are stored and how big the cache is allowed to grow. Defaults: ~/.cache/provium/fixtures/, 20 GiB. See provium.toml reference for full detail.
Test files #
Files matching *.test.lua under any roots directory are picked up by provium. Each file is run in a fresh Lua state with a fresh root Lab.
A test file consists of test(name, [meta,] fn) calls. Tests run in declaration order, and each test runs sequentially (one at a time within a file). The harness builds a fresh t context for each test and passes it as the function's first argument.
test
test
test
See the test-framework reference for test(), the t context, todo(), and wait_until().
Fixture files #
Files matching *.fixture.lua are not picked up by the test runner directly. Instead, test files reference them by name:
test
When the test runs, Provium:
- Locates
<root>/booted-base.fixture.lua(under anyrootsdirectory). - Hashes the file's source bytes plus every transitive dependency (other fixtures it references via
vm_fixture/lab_fixture, every helper itrequires, and any host file declared withdepends_on_file) plus every profile's kernel and initrd identity and the wire-protocol version into a cache key. - Looks up
<cache_dir>/<key>.snap. If present, restores it and hands the test a fresh VM. - If absent, builds the fixture by running its chunk under a build-time Lua state, takes the resulting snapshot, sparse-zstd-compresses it, and installs it into the cache.
A fixture file's chunk must end in a return statement that hands back either a vm:snapshot() (single VM) or a provium:snapshot() (whole lab). Example:
-- booted-base.fixture.lua
local vm = provium::
vm::
return vm:
See fixtures and dependencies for the cache lifecycle, eviction policy, and what triggers a rebuild.
Helper files #
Plain Lua files anywhere under a roots directory are reachable through require. The roots directories are prepended to package.path at file-runner setup, so tests/helpers/assert_pingable.lua can be loaded as:
local pingable = require
Helpers are not detected as tests (they don't end in .test.lua) and not detected as fixtures. Editing a helper invalidates the cache key of every fixture that transitively requires it.
The fixture cache #
~/.cache/provium/fixtures/
c5e6f8….snap # Single-VM fixture snapshot (sparse, zstd).
c5e6f8….lock # Per-key build lock.
abc123….lab/ # Lab-fixture directory.
lab.json # Per-VM snapshot index.
base.snap # Per-VM snapshot.
extra.snap
abc123….lab.lock
The cache is per-user by default (~/.cache/provium/fixtures/). Override it system-wide with [provium].cache_dir in provium.toml. The cache is shared across runs and across files within a run — concurrent file runners building the same fixture coordinate via the per-key lock file.
LRU eviction runs at provium startup before tests dispatch: the harness sums file sizes under cache_dir, sorts entries by access time, and deletes the oldest until the total is under cache_max_size.
Inspect or manage the cache from the CLI:
provium fixture list # Show every cached entry, with sizes.
provium fixture build P # Force-build the named fixture.
provium fixture rebuild P # Evict and rebuild.
provium fixture clean # Wipe the cache.
provium fixture stale # List fixtures whose source hashes don't match any cache entry.
The rerun state file #
After every run that produced at least one failure, Provium writes the canonical paths of the failing files to ~/.cache/provium/rerun.json (override with $PROVIUM_RERUN_STATE). The --rerun-failed flag intersects discovered files against this list, so:
provium tests/ --rerun-failed
re-runs only files that failed last time. A clean run leaves the file alone, so the failed set persists until you fix the failures.
Required external binaries #
| Binary | Used for | Provided by |
|---|---|---|
qemu-system-x86_64 | VMM backend | qemu-system-x86 package |
ip | Bridge / TAP / link operations | iproute2 |
tc | Latency / drop-rate / bandwidth qdiscs | iproute2 |
nft | Per-bridge partition rules, NAT for uplink | nftables |
tcpdump | bridge:capture() and nic:capture() | tcpdump |
Provium's startup pre-flight checks for /dev/kvm, /dev/vhost-vsock, ip, tc, nft, qemu-system-x86_64, and effective CAP_NET_ADMIN. Missing pieces fail with an actionable message before any test runs. The exception is tcpdump: the pre-flight does not check for it, because it is only needed once a test calls capture().
What lives outside the project tree #
- The fixture cache (default:
~/.cache/provium/fixtures/). - The rerun-state file (default:
~/.cache/provium/rerun.json). - Optional
--save-events/--events-socketpaths (your choice). - The kernel and initrd files referenced by
[profiles.<name>].
Everything else — tests, fixtures, helpers, configuration — lives inside the project directory and is the project author's responsibility to keep version-controlled.