Testing
Updated
Tests are separated by what they need to observe. A unit test that reads a struct, an integration test that needs a hypervisor, and an acceptance run on real hardware answer different questions, and the tooling keeps them apart so that a green local run never implies hardware was touched.
The one command
Section titled “The one command”tools/preflight.shThis is the local source gate: nineteen steps under set -euo pipefail, covering the
frontend build, source hygiene, file-size limits, runtime-string language, the
documentation and schema contract, the release and acceptance contract tests, dependency
advisories and licenses, the secret-history scan, ESLint, formatting, cargo fmt,
Clippy with -D warnings, and the whole Rust test and doctest suite. It requires
cargo-deny on PATH.
Its first step builds the console, which is not housekeeping: the UI is compiled into
the binary, so a stale or missing web/dist has to fail here rather than ship an
agent with no interface.
Unit and integration tests
Section titled “Unit and integration tests”cargo nextest run --profile ci --all-targets # or: cargo test --all-targetscargo test --doctests/ holds the integration suites: lifecycle, persistence, supervision, storage
planning, network contracts, session auth, snapshots, instance API, and the recovery
paths across an agent restart. Where a shape must not drift, a test asserts against a
recorded fixture in tests/fixtures/ instead of restating it in prose.
Tests that need a host, and are skipped by design
Section titled “Tests that need a host, and are skipped by design”Every integration suite in tests/ that spawns a real cloud-hypervisor or
qemu-storage-daemon process is marked #[ignore], so an ordinary run reports it as
skipped rather than passed.
That marking exists because of a specific failure. These suites used to return early when their resources were missing, and the test framework recorded that as a pass. CI was green while nobody knew when a hardware-dependent behaviour had last been checked. Now they are invisible by default and, when you name them explicitly, they panic if a prerequisite is absent instead of pretending.
Three unit tests in src/ that drive a real hypervisor are not in that tier and still
return early while CH_TEST_ASSETS_DIR is unset: vmm_ping_real_ch,
spawn_ch_creates_socket and spawn_ch_runs_in_new_session pass without running when the
assets are absent.
-
Install the assets. Point
CH_TEST_ASSETS_DIRat a directory containing:File Purpose hypervisor-fwBoot firmware, passed to Cloud Hypervisor as firmware_pathdummy-disk.rawThrowaway disk fixture; each test copies it before use /usr/bin/cloud-hypervisormust also exist. -
Run the whole ignored set:
Terminal window CH_TEST_ASSETS_DIR=/var/lib/virtainer-test \cargo nextest run --run-ignored all -
Or name a group:
Terminal window # qemu-storage-daemon only: needs qemu-storage-daemon and qemu-img on PATHcargo nextest run --test qsd_integration --run-ignored all# online snapshot, through the manager, against a real qsd; also needs qemu-iocargo nextest run --test snapshot_manager_e2e --run-ignored all# the thaw guard's real systemd round trip; needs to create a system unitsudo cargo nextest run --test thaw_guard_integration --run-ignored all
Browser tests
Section titled “Browser tests”cd web && pnpm test:e2e:mocked # PW_MOCKED=1, no agent requiredcd web && pnpm test:e2e:live # PW_LIVE=1, drives a running agentThe mocked suite runs Chromium, Firefox and WebKit, in light and dark, at a narrow viewport, with axe accessibility checks. It is part of release acceptance, which is why it is invoked locally against the release environment rather than on a hosted runner.
Contract tests for the release tooling
Section titled “Contract tests for the release tooling”The scripts that build and ship the product are themselves under test, in tools/:
test-host-image-build-contract.sh pins what the image bake must refuse and must stage,
and siblings pin the release artifact set, the acceptance contract, the AppVM acceptance
harness, the notification path and the secret scanner. They run inside preflight with the
container tooling stubbed, so they assert on the shape of a build without performing one.
The evidence is checked later, and by a different script than the one that ships it:
tools/check-release-evidence-bundle.sh opens the acceptance archive, hashes it against
the receipt, re-hashes every file the receipt references, and rejects a missing or
unreferenced member. tools/release.sh --promote runs
tools/check-release-acceptance.sh, which re-derives the source digest of the tree it is
about to release and requires a passing result for every name the receipt has to carry.
What hosted CI does not do
Section titled “What hosted CI does not do”.github/workflows/ci.yml is a manual, workflow_dispatch workflow. Every gate it runs
also runs in preflight, so it is a backstop and not the enforcement point. It installs no
container tooling and builds no host image. The full-history secret scan is no longer the
exception to that: tools/check-secret-history.sh runs the same pinned Gitleaks 8.29.1
across the whole history, in preflight, so no gate here is the only place it runs.