Lab
A Lab is the unit of resource ownership in Provium. The provium global is the root Lab; you can carve out sub-labs with provium:lab("name"). Both shapes expose the same surface, so this page is the canonical reference.
Membership #
lab:vm(name, profile?, opts?) #
Three call shapes:
lab:vm("name")— lookup. Errors if no VM with that name has been declared.lab:vm("name", "profile")— create.lab:vm("name", "profile", opts)— create with sizing opts (memory,cpus). See VM boot opts for the split between creation-time and boot-time keys.
Returns a VM userdata.
lab:bridge(name, opts?) #
Two call shapes:
lab:bridge("name")— lookup or create. If the bridge already exists in the lab, returns it; otherwise creates it.lab:bridge("name", opts)— same. Opts are reserved.
Returns a Bridge userdata.
lab:lab(name?) #
Create a sub-lab. Without a name, an anonymous name is generated (__lab_0, __lab_1, …). Returns a Lab userdata. Sub-labs share the parent's config, VMM, event sink, and pool, but have their own membership graph.
local dc1 = provium:
dc1:
dc1:
dc1::
lab:depends_on_file(host_path) #
Declare an external host-file as a fixture-cache dependency. The call itself is a no-op at runtime — its purpose is to be picked up by the cache-key scanner so that editing host_path invalidates the fixture next run.
provium:
Relative paths are resolved against the directory of the file containing the call. The path must be a string literal at the call site; non-literal paths (variables, concatenation) are not detected. Folds path + mtime + size into the cache key. See Fixtures and dependencies — External host-file deps.
Files pushed into the guest via vm:push_file already track themselves — depends_on_file is for files the fixture reads on the host side.
lab:include(resource_or_list) #
Add an existing resource (VM, Bridge, or sub-Lab) to this lab. Accepts:
- A single userdata —
lab:include(other_lab.shared_vm). - An array —
lab:include({a, b, c}).
Names must not already be taken in lab (errors with DuplicateVmName / DuplicateBridgeName). Reserved names (pack, unpack, vm_fixture, lab_fixture) are also rejected. When called from a test scope, names that already exist in a parent scope (the file root) also error to avoid silent shadowing — see labs and scope.
lab:remove(name_or_userdata) #
Remove a resource. Accepts a bare name (tries vm → bridge → sub-lab in order) or a userdata. Removal is graph-state only — the underlying VM, bridge, or sub-lab is NOT shut down or unrealised.
lab:members() #
Returns an array of {kind=string, name=string} entries listing every direct child of the lab. kind is "vm", "bridge", or "sub_lab".
lab:vm_names() / lab:bridge_names() / lab:sub_lab_names() #
Arrays of names of each kind directly in this lab (does not recurse into sub-labs).
lab.<name> (dot sugar) #
Looks up a named resource. Lookup order:
- Reserved keys:
pack,unpack,vm_fixture,lab_fixture. - VM with that name (walks parent scope chain on miss).
- Bridge with that name (walks parent scope chain on miss).
- Sub-lab with that name (walks parent scope chain on miss).
- Returns
nil.
The dot form does not create. Use lab:vm("name", "profile") for that. The parent-scope walk applies when this lab is a per-test scope (the runner sets provium to a test-scope LabUd whose parent chain includes the file root). Federation sub-labs created via lab:lab(name) have an empty parent chain and don't fall through to siblings.
Batch lifecycle #
Each method here applies to every direct VM child of the lab. They do not recurse into sub-labs.
lab:boot() #
Boot every VM in the lab. Returns self so you can chain.
lab:shutdown() #
Shutdown every VM.
lab:pause() / lab:resume() #
Pause / resume every VM. Errors if any VM is in the wrong state for the transition.
Resource claims #
Each test file may make at most one claim against the dispatcher's resource pool. The claim sits across the entire file's lifetime; it is released at file end (or scope-walker end).
lab:claim({memory=…, cpus=…}) #
Reserve resources from the pool. Either field may be omitted (treated as zero). Both are accepted as integers; memory may also be a string with K/M/G suffix.
| Field | Type | Effect |
|---|---|---|
memory | int (bytes) or string "512M" / "2G" | Memory budget. |
cpus | int | vCPU budget. |
provium:
test
test
A second :claim call on the same file errors — lab claim already held; one-shot per lab. The claim is per file, not per lab: one made from a sub-lab or from inside a test is the file's claim. When no pool is wired (REPL / single-file ad-hoc runs) the call still records the one-shot constraint but is otherwise a no-op.
The call blocks until the pool can afford the claim, and errors with lab claim exceeds total pool budget if it never could. Time spent blocked here is not charged to the file's timeout.
Once held, the claim is the file's VM budget: every vm:boot() and lab:boot() in the file takes its memory (plus 100 MiB of VMM overhead per VM) and vCPUs out of the claim, gives them back on shutdown, and never reserves from the pool. A boot the claim cannot cover errors — vm x: cannot boot: needs … but the file's claim is … with … already in use; raise provium:claim to the file's peak — rather than waiting. A VM that declares no memory or cpus counts at its launch defaults, 512 MiB and one vCPU. Without a claim, each boot reserves from the pool on its own; see pools and parallelism for what that costs.
The claim emits a claim_acquired event when it goes through; a matching claim_released fires at file end.
Barriers #
lab:barrier(name, count, timeout?) #
Block until count callers have hit the same name-keyed barrier. Returns true when the barrier releases and false on timeout — it does not raise. Default timeout is 60 seconds. The timeout argument accepts a number (seconds) or nil. The count is locked in by the first arrival; a later call with a mismatching count for the same name returns false with a host-side warning. Barriers are reusable across rounds under the same name.
-- count = 1: satisfied immediately.
assert
-- Unmet count: times out and returns false.
assert
Meeting a count > 1 requires a second concurrent host-side caller. Test files run single-threaded, and workers run guest processes (which can't call barrier), so a count > 1 barrier currently times out in an ordinary test file; the multi-caller form is aimed at thread-mode workers, which aren't supported yet. See Labs and scope — Barriers.
Snapshot and restore #
lab:snapshot(dir?) #
Take a whole-lab snapshot. With an explicit dir arg, writes there and returns the dir path as a string. Without args, writes to a fresh tempdir and returns a LabSnapshot userdata.
lab:restore(dir_or_snapshot) #
Restore from a directory path or a LabSnapshot userdata. Reads lab.json for the per-VM index plus each VM's snapshot file under the same dir.
Fixtures #
lab:vm_fixture(path) #
Build (or restore from cache) a single-VM fixture. path is the test-root-relative path of a *.fixture.lua file with the .fixture.lua suffix omitted.
local vm = provium:
local vm = provium:
The cached snapshot is hashed by source bytes + every transitive vm_fixture/lab_fixture reference + every require()d helper + every profile's kernel and initrd identifier. Editing any of those invalidates the cache for that fixture (and every fixture that transitively depends on it).
Returns a VM userdata in the Booted state.
lab:lab_fixture(path) #
Build (or restore from cache) a multi-VM lab fixture. The fixture's chunk must end with return provium:snapshot(). Cached as a directory <key>.lab/ containing the per-VM snapshots and lab.json.
Returns a Lab userdata wrapping a fresh sub-lab into which the fixture has been restored.
Binary helpers #
lab:pack(fmt, ...) / lab:unpack(fmt, s) #
Pass-throughs to Lua 5.4's string.pack / string.unpack. Available so test code that processes binary protocols doesn't need to reach into string.*. Same call shape and semantics as the standard library.
Accessors #
lab:name()— Returns the lab's name. The root lab is named"provium".
See also #
- provium global — file-scope configuration globals on top of the root Lab.
- VM, Bridge — what lives in a lab.
- Snapshot — what
lab:snapshot()returns. - Fixtures and dependencies — for the cache-key model and rebuild triggers.