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. 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 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.