Testing
A milestone is done when it boots and is checked, not when it compiles.
Most of the checks are cargo xtask commands that build an image, install
it on a scratch disk in QEMU, boot it, and type at its serial console. The
command list is cargo xtask help; the code is in
crates/xtask/src/main.rs.
Before pushing
What CI runs on a release, and nothing slow:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo xtask firmware-smoke --arch x86_64
cargo xtask firmware-smoke --arch aarch64
CI runs only for v* tags or by hand, never on push; see
CONTRIBUTING.md.
What every boot test shares
- It builds what it tests. Each test runs
cargo xtask imagefirst, in the builder, so it tests this tree. With nothing changed that is a few seconds; after a change to a recipe or to a crate that ships, it is that rebuild. hideforge stamps the image directory with what the image was assembled from (.hideforge-image), and leaves an image assembled from the same inputs as it is. Two tests in one checkout never run at once: see Development notes. - It installs a fresh disk the way
cargo xtask installdoes, by booting Minimal from RAM withhide installas PID 1. An install may take up to 15 minutes before the test gives up. - A failed boot ends QEMU. QEMU runs with
-no-reboot, so a guest that reboots — after a panic, a watchdog reset, hidestage giving up — exits, and the test starts it again when it wants the next attempt. - It reads and types on the serial console, which is
/dev/consolein test images; see Development notes. - Each check prints
okorFAILwith the console output that made it fail. The test fails if any check did. - It needs QEMU with UEFI firmware on the host (
cargo xtask doctor) and the builder. On a Mac, two kinds of boot run on QEMU’s emulated CPUs rather than macOS’s hypervisor, and take noticeably longer: Secure Boot, because x86 firmware keeps its variables in System Management Mode, which the hypervisor does not offer; and a machine with an emulated TPM, whose firmware stalls under the hypervisor before it prints anything.
Durations below are given as what a test does — builds, installs, boots — because few runs have been timed. Recorded numbers are marked.
A round of tests
cargo xtask round # every test, in lanes side by side
cargo xtask round --lanes 2 beside-test installer-test --edition workstation
Each lane is a copy of the checkout in target/lanes/N, with its own
target/, and runs its tests one after another; the lanes run at once.
The images are built once, before the lanes, and each lane gets them as
hard links, so that four lanes do not assemble four Workstations at once
on one disk. Logs go to target/logs/round-<test>.log, one line per test
to target/logs/round.log, and every guest’s serial console as it comes
to the lane’s target/logs/console-<pid>.log — where to look while a test
waits for a line. The default is one lane per two CPUs, at most three.
On an eight-CPU Linux PC with KVM, a guest boots in seconds where the
Mac’s emulated Secure Boot takes minutes; hw-test took 2 minutes there
and 15 on the Mac.
The tests
firmware-smoke [--arch ARCH]
Boots the UEFI firmware with no disk and checks it reaches boot device selection. Every later boot stands on this. Recorded: 1.6 s on x86_64 (HVF) and 6.8 s on aarch64 (TCG) on an Intel Mac. Needs only QEMU and its firmware.
forge-selftest
Builds the fixtures in crates/hideforge/tests/recipes in a scratch work
directory and checks each guarantee the build sandbox makes: no network
and a read-only builder for host builds; only declared inputs for target
builds; an output that is exactly what the build created; a later stage
replacing an earlier one’s files, and the same stage refused; unchanged
inputs not rebuilt; a failed build leaving no store path and no build
directory. Needs the builder. See hideforge.
boot --test
Boots Minimal from RAM and waits for the banner unit’s line, hideOS: booted on: the kernel booted, oxinit is PID 1 and ran a program on the
glibc userspace. Recorded: 4.7 s (H1). --disk boots disk.raw through
UEFI instead.
seal-test
H2’s checks, on a disk of their own, under Secure Boot: it boots from the
sealed disk; the firmware enforced Secure Boot; writing to /usr fails and
/etc stays writable; an object in the store replaced by a modified copy
makes reading its file fail with EIO; the image swapped for another
sealed file is refused by hidestage; a UKI changed by one byte — changed
from Minimal booted beside the disk — is refused by the firmware before
anything in it runs. One install and four boots: three of the disk under
Secure Boot, one of Minimal from RAM beside it.
x86_64 only: there is no Secure Boot firmware for aarch64 here yet.
update-test
H3’s “done when”. It builds N from this tree and N+1, the same system one version later, with a 45-second watchdog so that the hang costs minutes. N+1 is handed to the guest as an oci-archive on a second disk. Then, each on a freshly installed disk:
- a good update: staged through hideupd (which refuses anyone but root),
collecting an orphaned object, N+1 boots and is marked good,
hide rollbackgoes back to N; - an N+1 whose image cannot be mounted, one that hangs, one whose kernel panics: each tried three times, then N boots by itself and N+1 is marked bad;
- an image changed after it was built: refused, nothing staged;
- a power cut after each of the five steps (
pull,stage,commit,prune,collect): N boots before the commit, N+1 after.
Ten installs and dozens of boots: the longest test. To chase one power
cut, HIDEOS_UPDATE_TEST_STEPS=commit,prune (or tamper) runs only those,
and keeps the disk to look at. See Updates and rollback.
net-test
Installs Minimal and checks that NetworkManager connects the wired port by
itself, writes /etc/resolv.conf with the DHCP server’s DNS, that names
resolve, that iwd answers on the bus, and that NetworkManager logs to
oxlogd rather than the console. QEMU’s user network gives DHCP and DNS;
resolving a name needs the host to be online. One install, one boot.
power-test
Installs Minimal, checks the swap file is on and the kernel knows where to
hibernate, suspends it and lets the clock wake it (rtcwake), then
hibernates it, starts QEMU again, and checks the same session is back —
a file in /tmp, which is memory only, still there. One install, two boots.
crypt-test
Installs Minimal encrypted and checks: the root is not readable on the
disk; it asks for the passphrase and refuses a wrong one; the first boot
seals the key to the TPM; the second boots without asking; with Secure Boot
turned on, PCR 7 changes, the TPM refuses, it asks, and the passphrase
still opens it; with no TPM, it asks. Needs swtpm on PATH (brew install swtpm), which runs on the host and gives QEMU its TPM. One
install, four boots; on a Mac the three with a TPM run emulated. ROADMAP H4 records whether the TPM path has been run.
sysext-test
Builds hello-sysext (in recipes/system/test) as a system extension
three ways and checks, on an installed Minimal: an unsigned one is refused
by hide ext add; one built for another image is added but left out at
boot; one signed for this image is merged over /usr, read-only; and that
one is left out once its record’s signature is damaged. One install, four
boots. See System extensions.
registry-test
Builds Minimal N and N+1 and the test extension for each, and serves N+1
and N+1’s extension from a small read-only registry of xtask’s own. On an
installed N with the extension for N: plain hide update waits while the
registry has no extension build for N+1; once it has, the update fetches
the image and the build and commits; N+1 boots with its build merged; and
after hide rollback, N boots with N’s. See Updates and rollback and
System extensions.
nvidia-test
Builds the Workstation and the nvidia extension for it, and installs the
two together, as the installer does from its medium on a machine with an
NVIDIA GPU (hide install --extensions; finding the GPU is tested on the
host, QEMU has none to show). Then it checks, in QEMU: its
modules are the running kernel’s and are indexed with the image’s;
nvidia.ko loads as far as finding no GPU; NVIDIA’s libraries and
nvidia-smi link, its EGL vendor file sits beside Mesa’s in libglvnd’s,
and modprobe.d keeps nouveau off; and the desktop still comes up, drawn by
Mesa through libglvnd. See System extensions.
beside-test
Lays out a disk as Windows 11 lays out its own — the ESP with a stand-in
for bootmgfw.efi, the reserved partition, an NTFS system volume, the
recovery partition — with 40 GiB free after them, made by Minimal from RAM.
Then the installer: it says the disk holds Windows, offers the space beside
it, installs there; the disk boots from the firmware’s own entry for
hideOS, first in its order; Windows’s partitions, their places and
contents are as they were; and the hardware clock is read as local time.
See Install and recover.
installer-test
Builds an installer medium and drives it as a person would: boots it beside
an empty disk, answers its questions on the console, waits for it to
install and show a recovery key; boots the installed disk, opened with that
key; opens hideBoot’s menu with a held key, starts the recovery system and
chooses what starts next; then boots the medium again and reinstalls over
the disk, keeping /home, and checks a file left in the home is still
there, the account’s. See Install and recover.
desktop-test
Builds and installs the Workstation and checks, at its console, what it
adds to Minimal: hideupd answers os.hide.Update1.Deployments on the system
bus with the running deployment; polkit knows hideupd’s actions; Flathub is
configured with no file in /etc and its signed summary verifies (needs
the host online); bubblewrap makes an unprivileged sandbox for a user; the
portals, Flatpak’s system helper and Settings’ Updates page are in the
image. Then it logs in at the greeter and checks the desktop: an
application started in the session stays up; the session is hidelogin’s,
on seat0, in a cgroup its user owns, and active as polkit sees it; the
sound card — QEMU’s, heard by nobody — is the person’s, and their
PipeWire plays to it; hide shell enters a container; polkit asks an administrator’s password and
takes it; the power button asks rather than powering off, and the
machine powers off by itself at the end of COSMIC’s countdown, within a
minute, with the session up. A second boot logs in again and checks that
COSMIC’s Suspend action suspends and the clock wakes the machine — last,
because after QEMU’s S3 resume virtio-gpu’s commits stall and a session
can no longer end cleanly. The first Workstation build is most of a day; see
Building.
setup-test
Installs the Workstation as the installer does — encrypted, with a setup
key and no account — and checks first-boot setup: the setup key opens the
disk with nothing asked; hidesetup is on the screen as the greeter’s user;
it offers languages, layouts and zones; anyone but the greeter is refused,
and a zone outside /usr/share/zoneinfo too. Then, as the greeter, each
page’s call: the language, keyboard, time zone and account are taken; the
account is an administrator with its home, keyboard, clock and subordinate
IDs; the disk takes the passphrase and gives a recovery key, both open it
and the setup key no longer does; setup finishes and answers no one after.
The login screen lets the new account in, in its language, and the next
boot asks for the passphrase and opens with it. Saves setup-test.png,
setup-test-greeter.png and setup-test-desktop.png.
Pictures
Not tests, but real boots, and how the site’s and the READMEs’ pictures are made:
screenshot [--edition E] [--login]: boots the edition with a display, typescat /etc/os-release,uname -sr,oxctl listandls /at Minimal’s console, or waits for the Workstation’s greeter (and logs in with--login), and savesscreenshot.pngin the image directory.hideboot-screenshot [--no-build] [--manager FILE] [--display WxH]: installs Minimal, gives its ESP an entry being tried, a good one, a failed one and the recovery system, and saves pictures of hideBoot’s menu.--displaysets the screen QEMU announces through EDID.