- docs/coverage/: COVERAGE.md (methodology, exclusions, landmines) + llvm-cov report + LCOV export. Union of engine (unit+integration) and headless UI layers over the real release apps: 95.71% lines / 94.59% regions / 97.88% functions across the three ports (149 tests green). - patches/0002-hub-graceful-child-shutdown.patch: hub closes the child app's stdin (EOF -> clean exit -> LLVM profile flush) before falling back to SIGKILL; without it app-side coverage reads 0%. - setup_makepad.sh: pin MAKEPAD_SHA, fatal patch checks, apply both patches, rustup default toolchain 1.98.0. - koboyo: FIX RefCell double-borrow crash in the Zoom-to-fit handler (read viewport before canvas_mut; found by the new UI test, app died with exit 101 'RefCell already borrowed' at widgets/widget.rs:1168). - New tests: koboyo +5 integration (key->tool map, kind_for_tool arms, delete/undo, content_bbox/zoom_to_fit, lasso) +4 UI (freehand gestures, select/move/delete + history keys, wheel zoom, ten element kinds + fit, rail lock/connect/export); rider +1 integration +4 UI (account rows, crosshair/Now pill, found-back/call/cancel, send button); insurance +2 integration (SheetTab tables, SheetDrag clamps).
7 KiB
Test Coverage — makepad-ports (rider · koboyo · insurance)
Measured 2026-09-01 with LLVM source-based coverage (-C instrument-coverage)
on Rust 1.98.0 / cargo-llvm-cov 0.9.0 tooling, Makepad pinned at
b41e7404b6bb893d32f7d2924cd7b1c28983547f + patches/.
Results
149 tests, all green: 123 engine-layer (unit + integration) + 26 UI-layer (headless full-app). Coverage below is the union of all three layers, i.e. model files are counted from unit, integration and real app binaries.
| File | Lines | Regions | Functions |
|---|---|---|---|
| insurance/src/app.rs | 99.57% | 98.98% | 100.00% |
| insurance/src/model.rs | 100.00% | 100.00% | 100.00% |
| koboyo/src/app.rs | 88.85% | 89.77% | 88.00% |
| koboyo/src/canvas_view.rs | 82.48% | 81.01% | 94.44% |
| koboyo/src/model.rs | 99.09% | 97.35% | 100.00% |
| rider/src/app.rs | 98.19% | 97.90% | 100.00% |
| rider/src/model.rs | 100.00% | 100.00% | 100.00% |
| TOTAL | 95.71% | 94.59% | 97.88% |
Raw artifacts: coverage-report.txt (llvm-cov table)
and coverage.lcov (LCOV export, per-line data).
Layer mapping
| Layer | What runs | What it covers |
|---|---|---|
| Engine | cargo test -p <crate> --lib --test integration |
model.rs pure state machines (no Makepad runtime) |
| UI | cargo test -p <crate> --test ui (headless, MAKEPAD=headless) |
app.rs + canvas_view.rs in the real release app, driven over the studio stdin protocol by the hub |
Methodology
-
Everything is built with
cargo llvm-cov show-envwrapper env sourced,MAKEPAD=headlessexported for every cargo invocation (see landmine 3), andLLVM_PROFILE_FILE=<dir>/cov-%p-%m.profrawso every process — test harness, hub, and the spawned app — writes its own profile. -
Test binaries build into a shared target dir; the UI harness spawns the release app via the studio hub (each crate's
target/symlinked to a shared app target dir so the hub's child build cache-hits and stays inside the 600 s startup timeout). -
Reporting drives the LLVM tools directly (see landmine 1):
llvm-profdata merge -sparse profraw/*.profraw -o all.profdata llvm-cov report --instr-profile=all.profdata \ --object <lib-test-bin> --object <integration-bin> --object <ui-bin> ... \ --object target/release/makepad-example-<crate> \ --ignore-filename-regex='.*/(platform|widgets|draw|libs|studio|...)/.*|.*/main\.rs$|.*/tests/.*'
Exclusions (deliberate)
- Makepad framework code (
platform/,widgets/,draw/,libs/,studio/): not ours; filtered by--ignore-filename-regex. main.rsper crate: 3-line platform startup stub (app_main!), only reachable as a process entry point outside the harness.- Generated Makepad code:
live_design!DSL blocks compile into registration code attributed to framework files, so the filter above already removes it; derived#[derive(Live, LiveHook)]impls that remain in our files are exercised by app startup in the UI layer. - In-file
#[cfg(test)]panic arms (koboyo model.rs 1170/1176/1186):panic!branches of the crate's own unit-test assertions — unreachable when tests pass. - Mobile-only chrome (koboyo app.rs ~994-1020 dots-menu rows): the ⋮
dots_btnisvisible: falseat the desktop breakpoint the headless window uses, so the rows cannot be reached without viewport resizing support in the harness.
Remaining uncovered (audited, not blind spots)
koboyo/app.rs: dots-menu rows (mobile-only, above), a few popup auto-close edges andsearch_inputEnter-pick fallback rows.koboyo/canvas_view.rs: draft-preview paint for shape kinds only reachable mid-gesture with a live draft of every kind (table/browser-frame/phone frame previews), plus the selection-handles paint path for zero-size picks.rider/app.rs: chat list draw for the empty-history branch (933) and two timer-race guards (1091/1095, 1278-1279) — the reply/toast timers usually fire before the suite tears down; the guards need a cancel in the same tick.insurance/app.rs1554: toast-expiry timer race (same class).
Test additions made for coverage (this tranche)
- koboyo: +5 integration (full key→tool map, every
kind_for_toolarm + badge counter, delete/undo,content_bbox/zoom_to_fit, lasso), +4 UI (freehand gestures pen/laser/lasso/eraser/hand, select-move-delete + ⌘Z/⇧⌘Z/⌘Y, wheel zoom, ten element kinds + zoom-to-fit, rail lock/connect/export). - rider: +1 integration (
NavEffect::none), +4 UI (account rows via avatar, crosshair + Now pill, found-back + call + cancel-arriving, send button). - insurance: +2 integration (SheetTab tables, SheetDrag translate clamps).
🐛 Real bug found and fixed by the new tests
koboyo_element_kinds_and_zoom_to_fit crashed the app (exit 101):
RefCell double-borrow in the Zoom-to-fit handler — it read the canvas
widget's area inside the canvas_mut closure that already held the same
widget's RefCell mutably (widgets/src/widget.rs:1168: RefCell already borrowed). Fixed in koboyo/src/app.rs by reading the viewport before
borrowing. The handler was previously 0%-covered — exactly the class of bug
coverage work is supposed to surface.
Landmines (read before re-running)
cargo llvm-cov reportomits example-crate objects in this workspace (0.9.0 object discovery misses them even with profdata + binaries present). Drivellvm-profdata/llvm-covdirectly with explicit--objectflags — the toolchain's own copies live under$RUSTUP_HOME/toolchains/<tc>/lib/rustlib/<target>/bin/.- SIGKILL eats app coverage. The hub terminates the child app with
SIGKILL to the process group, so the LLVM runtime never flushes and
app.rsreads 0%.patches/0002-hub-graceful-child-shutdown.patchcloses the child's stdin first (EOF cleanly ends the headless stdin loop → atexit flush) and only escalates to SIGKILL after 5 s. - Keep
MAKEPAD=headlessset for every build. The platform build script re-runs when it changes, flipping theheadlesscfg and forcing a full rebuild ofmakepad-platform/makepad-widgets— on a small box that rebuild can OOM. Add disk-backed swap and build-j1if RAM < 4 GB. - Login-shell PATH reset. The hub spawns the app via
/bin/sh -lc, which resets PATH;cargomust resolve on the system default PATH (e.g. symlinks in/usr/local/bin) andRUSTUP_HOME/CARGO_HOMEmust be exported. - Per-crate target dirs. The test runtime forces
CARGO_TARGET_DIR=<crate>/targetfor the child build; symlink eachexamples/<crate>/targetto a shared pre-warmed release dir or the child build blows the 600 s startup timeout. - Never run
cargo llvm-covinside its ownshow-envcontext — it degrades to plaincargo testagainst the wrong target dir and rebuilds the world.