These docs are under active development and cover the v0.20 Kobicha security model.
On this page
reference 5 min read

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", boot_opts) — create with creation-time boot opts. See VM boot opts.

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:lab("dc1")
dc1:vm("a", "peios")
dc1:vm("b", "peios")
dc1:bridge("lan"):attach({dc1.a, dc1.b})

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:depends_on_file("../templates/sshd_config.in")

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:

  1. Reserved keys: pack, unpack, vm_fixture, lab_fixture.
  2. VM with that name (walks parent scope chain on miss).
  3. Bridge with that name (walks parent scope chain on miss).
  4. Sub-lab with that name (walks parent scope chain on miss).
  5. 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:claim({memory = "4G", cpus = 4})

test("…", function(t) end)
test("…", function(t) end)

A second :claim call on the same file errors with claim already taken. 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 nothing; raises on timeout. Default timeout is 60 seconds. The timeout argument accepts a number (seconds) or nil.

Used inside multi-worker tests where two or more callers need to synchronise:

local w1 = vm:spawn_worker()
local w2 = vm:spawn_worker()

co.wrap(function() … provium:barrier("ready", 2); … end)()
co.wrap(function() … provium:barrier("ready", 2); … end)()

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:vm_fixture("base")
local vm = provium:vm_fixture("setups/networked")

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