Five layers, cheapest first. The live layers deliberately use the machine as fixture (a monitor passing only synthetic data proves nothing).
| layer | command | needs |
|---|---|---|
| parser units + fixtures | cargo test -p sysmon-core --lib |
nothing (pure) |
| settings migration | cargo test -p sysmon-app --lib |
nothing |
| accuracy cross-checks | cargo test -p sysmon-core --test accuracy |
a live Linux box |
| live probes (smoke, icons, net attribution) | cargo test -p sysmon-core --test live_smoke --test live_icons --test live_net_attribution |
live box; net test wants WAN + skips offline |
| UI interactions | cargo test -p sysmon-app --test ui_kittest |
GPU or lavapipe (wgpu) |
| full e2e | scripts/e2e.sh out/ target/release/sysmon |
Xvfb, openbox, xdotool, imagemagick, jq |
cargo test runs everything but the e2e script. All 41 green is the
resting state.
Each check compares a collector against an independent authority
(free -b, df -B1, ps, /proc//sys re-reads) under a justified
tolerance. The law: a red check means fix the collector. Never
widen a tolerance to pass. Identities and the tolerance table:
docs/dev/ACCURACY.md. The CPU check pins a spinner thread — brief
100%-of-one-core during the run is the test working.
Drives the real SysMonApp (real sampler, live /proc) through
AccessKit — clicks and typing land the way assistive tech would.
Patterns to keep:
- Construct with
Harness::builder().build_eframe(…), scratchXDG_CONFIG_HOMEset first (the app saves settings on changes). - Query by label (
get_by_label("Processes")), byRole::TextInputfor the filter,get_by_label_containsfor count labels. Custom-painted widgets are findable only becauseglyph_buttonregistersWidgetInfo::labeled. Do likewise for every new hand-painted control. - Assert on
harness.state()(the app struct) and on the UI where possible (e.g. panel_fill color after a theme switch). - One
#[test]fn, sequential — harnesses mutate process-global env; parallel harnesses race. - After clicking anything animated (glyph buttons, menu rows — they
ease via
animate_boolfor ~0.1 s), userun_steps(8), neverrun():run()demands quiescence within 4 steps and the easing legitimately repaints for ~6. click_secondary()opens context menus;click_modifiersdoes Ctrl+click multi-select. Off-screen columns (horizontal scroll) can't be clicked —set_sizethe harness wider first.
One command, full live receipts: boots Xvfb + openbox, launches the
GUI with scratch config/socket, screenshots all 10 themes × 2 pages
through ctl shot, opens a pop-out and docks it by drag-drop,
checks single-instance raise, probe-via-socket, tap streaming, exit
codes, clean socket removal. Screenshots land in the output dir
and regenerate README images (fresh-from-this-build is
the law for release screenshots).
- Bare Xvfb has no window manager: no keyboard-focus semantics
(typed keys vanish —
xdotool windowfocus --syncor runopenbox), and windows cannot move at all. - Synthetic XTEST titlebar drags do not drive openbox's
interactive move. To emulate a user drag for dock testing:
xdotool mousedown 1+ step the window withxdotool windowmovemouseup 1— the same signal triad (motion + button held + release position) the app's dock logic reads.
- Never blind-click a row of a live-sorted process table to test
kill flows — rows re-sort between coordinate lookup and click. Spawn a sacrificial process (
sleep 3000 &) and filter by its exact PID first; the confirm modal must name the sacrifice before you click confirm. - Scratch env or you drive the real thing:
XDG_CONFIG_HOME(settings writes!) andXDG_RUNTIME_DIR(socket ownership!) both. A ctl theme test once silently rewrote the user's saved theme. pkill -f <pattern>kills your own test shell when the pattern appears in its command text (exit 144, script dies mid-run). Matchreadlink /proc/<pid>/exeinstead.- Xvfb renders via llvmpipe (software): CPU figures there are worst-case, not real-GPU truth. Label them; the resource table in RECEIPTS.md does.
ctl shotcaptures the main viewport only; compose pop-outs withimport -window rooton the Xvfb display.
# own CPU% over a window, no tools needed:
clk=$(getconf CLK_TCK)
t0=$(awk '{print $14+$15}' /proc/<pid>/stat); sleep 60
t1=$(awk '{print $14+$15}' /proc/<pid>/stat)
echo "$(( (t1-t0) ))" # ticks; % of one core = ticks/clk/60*100
awk '/VmRSS/{print $2}' /proc/<pid>/status # RSS KiBLet the GUI settle ~8 s first (icon index + first paints). Baseline
numbers to beat are in docs/dev/RECEIPTS.md.