Testing
Every test here runs in isolation: test sessions use their own D-Bus buses, stand-ins for system services and a scratch profile, and the VMs change nothing on the host.
Kestrel sessions
Section titled “Kestrel sessions”Build Kestrel first (see Building Luft). Then from the repository root:
| Command | Runs |
|---|---|
kestrel/tools/session.sh nested |
A visible nested session in Mutter’s development kit window, for trying things by hand |
kestrel/tools/session.sh capture |
The full headless check suite, including the login screen, saving screenshots of every surface |
kestrel/tools/session.sh greeter |
Only the login screen checks, against a stand-in greetd |
kestrel/tools/session.sh setup |
Only the first-run setup checks, in Kestrel’s setup mode with Install Luft (needs KESTREL_DEV_APPS=setup or an installed build) |
kestrel/tools/session.sh scaling |
The default display scale for laptop panels and monitors of different sizes, as virtual monitors |
kestrel/tools/session.sh performance |
Startup memory, search, notification bursts, actor reuse and idle paints |
kestrel/tools/session.sh frames |
Frame times of animations and gestures, see Frame timing |
kestrel/tools/session.sh motion |
Every painted frame of animations, see Motion clips |
capture splits the checks into groups and runs several sessions side by side:
| Option | Effect |
|---|---|
--only GROUPS |
Only these comma-separated groups. An area such as apps runs every group in it |
--jobs N |
Up to N sessions at once (default: one per four processors, fewer when the inotify limit is close); --jobs 1 runs one session at a time |
--list |
Lists the groups |
The board groups, input:scrolling, panel:battery and panel:screenshot also run on two laptop panels at their automatic display scale: a 14-inch 1920×1200 panel at 150%, as the groups ending in +laptop, and a 15.6-inch 1080p panel at 125%, as the groups ending in +laptop15. Their screenshots go to a laptop or laptop15 folder inside the capture folder.
The nested session copies the host’s wallpaper, interface settings, keyboard layout and favorite apps. Closing its window ends it. Headless sessions instead show Luft’s own apps plus the stand-in apps in kestrel/tools/fixtures/apps, sign in as Mara Lindqvist on a computer named Mara’s Laptop, and read accounts, networks and Bluetooth devices from the stand-in system bus that KESTREL_STAND_IN_BUS names. Rover lists two stand-in drives from the mount table that LUFT_MOUNTS points it at, Schelf finds no system Flatpaks in the empty folder FLATPAK_SYSTEM_DIR names, and with LUFT_SAMPLE_SYSTEM set Barometer and Settings → About show sample hardware, so screenshots never show who uses the host or what is installed, running or plugged in on it. Theme edits reload live after meson compile -C kestrel/build.
| Variable | Default | Purpose |
|---|---|---|
KESTREL_SESSION_DIR |
kestrel/run |
Settings, caches and app data of the test session. Point it at an empty folder for a clean profile or to run sessions side by side |
KESTREL_CAPTURE_DIR |
kestrel/run/screenshots |
Where captures are saved |
KESTREL_CAPTURE_SIZE |
1440x900 |
Size of the virtual monitor |
KESTREL_CAPTURE_SECONDARY_SIZE |
none | Adds a second virtual monitor, such as 1280x720 |
KESTREL_CAPTURE_SCHEME |
the host’s | light or dark, to capture every surface in one style |
KESTREL_CAPTURE_WALLPAPER |
the host’s | An image used as the wallpaper for both styles, also behind the Luft apps in place of their test gradient, to check the glass on light and dark pictures |
KESTREL_DEV_APPS |
none | Luft apps to test from their build folders instead of the installed ones, such as rover,settings (run bun run desktop:build in each first) |
KESTREL_BUILD |
kestrel/build |
Another Kestrel build to run, set up against KESTREL_COMPOSITOR |
KESTREL_COMPOSITOR |
kestrel/run/mutter-install |
Another Mutter install to run on, such as one from kestrel/compositor/build.sh BUILD_DIR PREFIX |
Optional tools widen the capture: with rclone installed Rover connects to local WebDAV and FTP servers, and with stalwart 0.15 Mailman signs in to a local mail server and sends from aliases over SMTP and JMAP. Stalwart 0.16 and later dropped the in-memory directory these checks configure. Pointing KESTREL_HELIUM at a Helium AppImage adds a web page in Helium to input:scrolling.
Frame timing
Section titled “Frame timing”frames first opens Start, quick settings, the notification center, the overview, the emoji picker and the lock screen’s password prompt once each, as they first open after signing in. Then it opens each of them again, types a search into Start, opens, closes and drags windows, slides windows under stacked glass and under a blurred app window, and enters, pans, pinches, zooms and leaves a board. Each of these runs three times for timing and twice more to time the GPU. For every interaction it prints the time until the first frame, the frames drawn, missed refreshes, frames slower than a refresh, CPU time per frame on the compositor thread and on the slowest frame, update time, layout time and GPU time after each frame, and saves them as frames-WIDTHxHEIGHT@SCALE.json in the capture folder.
| Variable | Purpose |
|---|---|
KESTREL_FRAMES_BASELINE |
A saved report to compare against; each value prints as before → after |
KESTREL_FRAMES_ONLY |
Comma-separated words; only interactions whose name contains one run |
KESTREL_CAPTURE_SIZE=1920x1200:302x189mm measures a 14-inch laptop panel at its own scale. To compare two builds, point KESTREL_BUILD and KESTREL_COMPOSITOR at each in turn, ideally while nothing else loads the machine.
Motion clips
Section titled “Motion clips”motion records every frame Kestrel paints while surfaces open, close and change their mind half-way, windows minimise and restore, and workspaces switch, back to back and with reduced motion. Each clip goes to its own folder in clips inside the capture folder, with the frames as pictures and frames.json listing when each was painted and how far along the change it looks. KESTREL_MOTION_ONLY picks groups of clips: surfaces, windows, board, gestures, reduced and snap.
kestrel/tools/motion/sheets.py CLIPS turns each clip into a contact sheet and a video at real speed, with ImageMagick and FFmpeg.
Polkit, real network credentials, unlocking with the computer’s own sign-in rules, physical displays and suspend still need a real login session.
Apps and packages
Section titled “Apps and packages”From an app’s folder, bun run check type-checks its interface. The Rust side builds with bun run desktop:build. Shared Rust crates have their own cargo test.
NVIDIA graphics
Section titled “NVIDIA graphics”Install Luft and Settings find NVIDIA graphics by reading /sys/bus/pci/devices. Pointing LUFT_PCI_DEVICES at another folder laid out the same way, each device a folder with class, vendor and device files, makes them read that instead, to try NVIDIA’s offers on a computer or VM without an NVIDIA card:
mkdir -p ~/pci/0000:01:00.0 ~/.config/environment.dprintf '0x030200\n' >~/pci/0000:01:00.0/classprintf '0x10de\n' >~/pci/0000:01:00.0/vendorprintf '0x28e0\n' >~/pci/0000:01:00.0/deviceprintf 'LUFT_PCI_DEVICES=%s\n' ~/pci >~/.config/environment.d/90-pci.confAfter signing in again, the session sees an NVIDIA card; removing the file brings back the real devices.
Image systems
Section titled “Image systems”Schelf and Settings tell Luft’s image from a regular Fedora install by composefs= on the kernel command line in /proc/cmdline. Pointing LUFT_KERNEL_COMMAND_LINE at another file makes them read that instead: kestrel/tools/fixtures/system/image.cmdline makes them behave as on Luft’s image, and /dev/null as on a regular Fedora install. The apps:schelf checks capture Schelf both ways.
Installer
Section titled “Installer”apps/setup/installer/vm/run.sh installs Luft for real with bootc install to-filesystem inside a QEMU VM with spare drives, checks the result and starts the installed drive with UEFI. See Install Luft.
Luft Keyring and Passkeys
Section titled “Luft Keyring and Passkeys”See Luft Keyring and Luft Passkeys.
Boot VM
Section titled “Boot VM”A Fedora system built with Podman, with greetd and Kestrel’s login screen, booting through SushiBoot under QEMU and OVMF with SELinux enforcing. It needs no sudo. From boot/sushi:
scripts/vm/tree.sh # Fedora root tree (first run downloads packages)scripts/vm/kestrel.sh # build Kestrel into the treescripts/vm/security.sh # build trustd and USB protection into the treescripts/vm/disk.sh # install Sushi, build the initramfs, root disk and ESPscripts/vm/run.sh # boot it in a windowmake vm-build, make vm-build-luks, make vm-run and make vm-clean wrap the same steps.
Everything lives in boot/sushi/vm; set SUSHI_VM to use another folder. The sushi account’s password, and the disk passphrase, is sushi-vm.
disk.sh options:
| Variable | Effect |
|---|---|
LUKS=1 |
Encrypts the root disk |
MENU_TIMEOUT=3 |
Shows SushiBoot’s menu |
LAYOUT=fedora |
Lays the disk out like Fedora’s installer, with shim and GRUB |
DRIVER=initramfs (default) |
Sushi loads the graphics driver itself in the initramfs, as with NVIDIA. Use with GPU=vga |
DRIVER=system |
The driver isn’t in the initramfs, as right after a kernel update |
DRIVER=udev |
udev loads the driver, which switches the screen off on its own |
run.sh options:
| Variable | Effect |
|---|---|
SECURE_BOOT=1 |
Secure Boot on, with Microsoft’s keys enrolled |
TPM=2 / TPM=1.2 |
A software TPM, kept next to the disk |
XRES=3440 YRES=1440 |
Monitor resolution (default 1920×1080) |
GPU=vga |
QEMU’s standard VGA instead of virtio |
FRAMEBUFFER=1024x768 |
Hands SushiBoot a smaller framebuffer, as some firmware does |
EDID=0 |
Doesn’t describe the monitor to SushiBoot |
Recording boots frame by frame:
scripts/vm/record.py /tmp/frames --seconds 40 --type "16:sushi-vm" # type the passphrase at 16 sscripts/vm/frames.py /tmp/frames # when the screen went black, froze or changed modescripts/vm/journal.sh -b 0 -u sushi # the VM's journal, after it shuts downTo watch an offline update:
scripts/vm/update.shscripts/vm/disk.shscripts/vm/record.py /tmp/frames --seconds 60 \ --serial "login: =>root" --serial "Password: =>sushi-vm" --serial "]# =>dnf5 offline reboot -y"