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

131 lines
7 KiB
Markdown

# 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`](coverage-report.txt) (llvm-cov table)
and [`coverage.lcov`](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):
```sh
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.