makepad-ports/docs/coverage/COVERAGE.md
andodeki c24e87e99f Coverage tranche: full-stack LLVM coverage 95.7% lines, +16 tests, RefCell crash fix
- 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).
2026-09-01 19:58:51 +00:00

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

  1. Everything is built with cargo llvm-cov show-env wrapper env sourced, MAKEPAD=headless exported for every cargo invocation (see landmine 3), and LLVM_PROFILE_FILE=<dir>/cov-%p-%m.profraw so every process — test harness, hub, and the spawned app — writes its own profile.

  2. 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).

  3. 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.rs per 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_btn is visible: false at 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 and search_input Enter-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.rs 1554: toast-expiry timer race (same class).

Test additions made for coverage (this tranche)

  • koboyo: +5 integration (full key→tool map, every kind_for_tool arm + 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)

  1. cargo llvm-cov report omits example-crate objects in this workspace (0.9.0 object discovery misses them even with profdata + binaries present). Drive llvm-profdata/llvm-cov directly with explicit --object flags — the toolchain's own copies live under $RUSTUP_HOME/toolchains/<tc>/lib/rustlib/<target>/bin/.
  2. SIGKILL eats app coverage. The hub terminates the child app with SIGKILL to the process group, so the LLVM runtime never flushes and app.rs reads 0%. patches/0002-hub-graceful-child-shutdown.patch closes the child's stdin first (EOF cleanly ends the headless stdin loop → atexit flush) and only escalates to SIGKILL after 5 s.
  3. Keep MAKEPAD=headless set for every build. The platform build script re-runs when it changes, flipping the headless cfg and forcing a full rebuild of makepad-platform/makepad-widgets — on a small box that rebuild can OOM. Add disk-backed swap and build -j1 if RAM < 4 GB.
  4. Login-shell PATH reset. The hub spawns the app via /bin/sh -lc, which resets PATH; cargo must resolve on the system default PATH (e.g. symlinks in /usr/local/bin) and RUSTUP_HOME/CARGO_HOME must be exported.
  5. Per-crate target dirs. The test runtime forces CARGO_TARGET_DIR=<crate>/target for the child build; symlink each examples/<crate>/target to a shared pre-warmed release dir or the child build blows the 600 s startup timeout.
  6. Never run cargo llvm-cov inside its own show-env context — it degrades to plain cargo test against the wrong target dir and rebuilds the world.