1. Unit tests
2. The server against real clients, on any Linux machine
The display is the browser page the server serves: on a desktop,lindroids run foot opens it
with xdg-open. For scripted tests, lindroids debug-app stands in for the page: it pairs with a local server, gives every
window a size, writes each window’s newest frame to <dir>/window-<n>.png, counts sound bytes in
<dir>/audio-<stream>.bytes, and takes input commands on stdin:
tests/host-integration.sh runs the whole loop (server, debug-app, lindroids run foot, typing,
and checks that the frame changed); CI runs it on every push. The page itself is tested in a real
browser: headless Chromium (Playwright) with phone emulation, driving taps, typing, the
soft-keyboard text path, the clipboard and cursors against GTK 3 and GTK 4 programs. By hand:
LINDROIDS_LOG=debug (commits, configures, frames), LINDROIDS_TRANSPORT=tcp
(loopback only, as on enforcing devices), debug-app --tcp.
Stuck input in the page: node tests/stuck-input.mjs [--js <lindroids.js>] [--evidence out.json],
headless Chromium through playwright-core (set LINDROIDS_PLAYWRIGHT_CORE if it is not in
node_modules). The link is a recorder; a model of the server’s seat reads what the page sent.
The cases: blur, hidden page, lost link, window switch (the focus goes, the server lets go, the
key-up sends nothing); auto-repeat (the key down again, never a release and a press; modifiers do
not repeat); key-ups with keyCode 229; modifiers from the flags both ways, Meta from keys only; Caps
and Num Lock from key events only; chorded mouse buttons and a page hidden mid-drag; one motion per
move; wheel notches as steps, touchpad deltas as pixels; a rotation (one resize, the screen
turned), a frame of the old size dropped and acknowledged, no resize when nothing changed.
Emulation.setDeviceMetricsOverride), the link closing and linking again. After each storm
everything is let go and the page comes back: nothing may be held by the page or by the server
(model), no button pressed while down and no modifier repeated, the last size sent is the stage’s with the
viewport’s screen, the last lock state the keyboard’s. A failure prints its seed and last steps.
The server’s side of the same: cargo test -p lindroids-server seeded_input_storm feeds the
seat’s routing (KeyState, key_route, focus, release_page, release_link) seeded random
key, button, focus, shown, link-drop and lock messages and time passing, to one client of
wl_keyboard 10 and two older ones, checking each client is never pressed a key or button it has
down nor told repeated for a key it has not, holds nothing (keys, buttons, depressed modifiers)
once the focus, the page or the link that held it is gone, has the locks the page said last, and
never repeats on its own a key the page has said nothing of for longer than 2 s
(LINDROIDS_SEAT_SEED, LINDROIDS_SEAT_STEPS pick another storm).
3. Android
lab/ holds a QEMU lab of Android-x86 and BlissOS images (Android 7 to 13): fetch, start,
provision (Termux, sshd), and lab/test-lindroids.sh: with no app and no Termux setting, it
installs the CLI, has lindroids run foot open the page in the device’s browser, types, checks
that the page relinks after a server restart, then exports foot, adds its home screen shortcut,
stops Termux and taps it. lab/test-window-app.sh installs the default window app, runs foot in
it (config browser app), checks one full-screen task, Back, the program’s end removing it, the
offline page, and the bridge over WebView DevTools. Results: lab/README.md.
4. Packages
tests/install-repos.sh builds every package kind (deb, pacman, rpm, apk, xbps) and the signed
repositories with throwaway keys, serves them locally, and runs install.sh twice (install, then
update) in containers of 15 distros, checking the method it picks and lindroids version. CI runs
it with docker.
A permissive device is still useful for SELinux work: it logs every access an enforcing one would
refuse (logcat | grep "avc: denied") — that is how the cross-app connectto denial behind the
TCP link was found.
Screens are recorded without a hardware encoder (the VMs have none) by lab/record.sh, which
pulls raw screencap frames over adb and encodes an MP4 and an animated WebP for the README.
A server started with LINDROIDS_STATS=1 puts the frame rate and drawing time in a corner of
each window and the page’s layout in server.log; pages also log their errors there. It also logs
how often the server woke up (wakeups: …/s); tests/idle-wakeups.py checks on any Linux machine
that an idle server hardly wakes and that a program whose page is gone draws about once a second.
lab/CHECKLIST.md lists what “fully working” means and where each item
stands.