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

Assertions and Sub-tests

Provium provides assertion helpers and a sub-test system for organizing tests within a single file.

Assertions

Three assertion functions are available globally in every test script:

assert(condition, message)

Raises an error if the condition is falsy (false or nil):

assert(fd >= 0, "open failed: " .. fd)
assert(r.ok, "command failed")
assert(r.stdout.contains("expected"))

Provium overrides Lua's built-in assert with a version that produces clearer error messages.

assert_eq(expected, actual, message)

Checks equality between two values. On failure, the error message shows both values:

assert_eq(0, r.exit_code, "expected success")
assert_eq("hello\n", r.stdout.value, "wrong output")
assert_eq(data, h.SID_SYSTEM, "expected SYSTEM SID")

assert_contains(haystack, needle, message)

Checks that a string contains a substring:

assert_contains(r.stdout.value, "Linux", "not Linux")
assert_contains(log, "initialized", "module not loaded")

Sub-tests

The test(name, fn) function runs a named test case within the file. Each sub-test has isolated failure handling — a failure in one does not prevent others from running.

local vm = provium.create("minimal")
vm:boot()
vm:mount_vfs()

test("kernel boots", function()
    local r = vm:exec("uname -r")
    assert(r.ok)
end)

test("proc mounted", function()
    local r = vm:exec("cat /proc/version")
    assert(r.ok)
end)

test("sysfs mounted", function()
    local r = vm:exec("ls /sys/kernel/")
    assert(r.ok)
end)

vm:shutdown()

How sub-tests are reported

Each sub-test is reported individually:

  boot.lua ... ok (3 tests, 2.1s)
    · kernel boots ... ok
    · proc mounted ... ok
    · sysfs mounted ... ok

If any sub-test fails, the file is marked as failed:

  boot.lua ... FAIL (2.1s)
    · kernel boots ... ok
    · proc mounted ... FAIL: /proc/version: No such file or directory
    · sysfs mounted ... ok

Shared setup

Code outside test() blocks runs once and is shared across all sub-tests. Use this for VM setup:

local h = dofile("tests/helpers.lua")
local vm = provium.fixture("kacs")

-- This token is available to all sub-tests
local fd = h.open_self_token(vm)

test("token is primary", function()
    local data = h.query_token(vm, fd, h.TOKEN_CLASS_TYPE)
    local ttype = provium.unpack("u32", data)
    assert_eq(ttype, 1, "expected Primary")
end)

test("token is SYSTEM", function()
    local data = h.query_token(vm, fd, h.TOKEN_CLASS_USER)
    assert_eq(data, h.SID_SYSTEM)
end)

vm:shutdown()

When to use sub-tests

Use test() blocks when a file contains multiple logically distinct assertions that share the same VM setup. Without sub-tests, a failure on the first assertion stops the entire file. With sub-tests, every test case runs and all failures are visible at once.

For simple tests with a single assertion or a linear sequence where each step depends on the previous, sub-tests are unnecessary.

Marking tests as not yet implemented

The todo() function marks a test case as not yet implemented. It counts as a skip, not a failure:

test("complex scenario", function()
    todo("waiting on kernel feature X")
end)

test("another todo", function()
    todo()
end)

Output:

    · complex scenario ... todo: waiting on kernel feature X
    · another todo ... todo

Use todo() as a placeholder for test cases you plan to write later. It documents intent without producing false failures.