Host-side unit tests for the watchapp logic #68

Closed
opened 2026-09-01 17:35:46 +02:00 by robert · 1 comment
robert commented 2026-09-01 17:35:46 +02:00 (Migrated from git.butzei.de)

Goal

Test the C. Every test issue in the plan targets the JVM companion - the watchapp, which is the half written in C, has none.

The field store, the formatter table, the staleness rule, the template geometry and the descriptor packing are all pure functions over plain data. Compiled for the host with a small harness they test without an emulator, a watch or a phone.

Acceptance criteria

  • Host build of the testable watchapp sources, with the Pebble SDK surface stubbed where touched
  • Small assertion harness - no framework dependency; this is a few hundred lines of C
  • Field store: set, read, absent-renders---, zero-is-not-absent
  • Formatter table: every FieldId formats, in metric and imperial, including its unavailable rendering
  • Staleness: a value greys once older than its expected update interval, and recovers
  • Page rules: a sourceless page is skipped in the carousel; the last enabled page cannot be skipped away
  • Template geometry: no slot exceeds its bounds on emery, gabbro or basalt - the assertion that makes the deferred round work in #62 safe to do later
  • Descriptor pack/unpack round-trips, and rejects unknown field ids and wrong slot counts (mirrors #61's validation)
  • Runs in CI (#67)

Files

  • watchapp/test/
  • watchapp/test/stubs/

Notes

See D34. Keeping the geometry pure - taking bounds in, giving rects out - is what makes this possible; that constraint is worth honouring in #59 for its own sake.

Update — 2026-09-02: two more things to test on the host

  • Map projection (#42): anchor-relative decimetre offsets to screen pixels — translate, scale,
    rotate — asserted against fixture slices on all three geometries. Pure integer functions, no SDK
  • Stale vs unavailable (#58, D44): a field whose group heartbeat is missed greys; a field sent
    as its type's sentinel renders --; a constant value under changed-keys-only updates does
    neither
  • Per-type sentinel handling, including that zero is never mistaken for unavailable
## Goal Test the C. Every test issue in the plan targets the JVM companion - the watchapp, which is the half written in C, has none. The field store, the formatter table, the staleness rule, the template geometry and the descriptor packing are all pure functions over plain data. Compiled for the host with a small harness they test without an emulator, a watch or a phone. ## Acceptance criteria - [ ] Host build of the testable watchapp sources, with the Pebble SDK surface stubbed where touched - [ ] Small assertion harness - no framework dependency; this is a few hundred lines of C - [ ] Field store: set, read, absent-renders-`--`, zero-is-not-absent - [ ] Formatter table: every `FieldId` formats, in metric and imperial, including its unavailable rendering - [ ] Staleness: a value greys once older than its expected update interval, and recovers - [ ] Page rules: a sourceless page is skipped in the carousel; the last enabled page cannot be skipped away - [ ] **Template geometry: no slot exceeds its bounds on `emery`, `gabbro` or `basalt`** - the assertion that makes the deferred round work in #62 safe to do later - [ ] Descriptor pack/unpack round-trips, and rejects unknown field ids and wrong slot counts (mirrors #61's validation) - [ ] Runs in CI (#67) ## Files - `watchapp/test/` - `watchapp/test/stubs/` ## Notes See D34. Keeping the geometry pure - taking bounds in, giving rects out - is what makes this possible; that constraint is worth honouring in #59 for its own sake. ## Update — 2026-09-02: two more things to test on the host - [ ] **Map projection** (#42): anchor-relative decimetre offsets to screen pixels — translate, scale, rotate — asserted against fixture slices on all three geometries. Pure integer functions, no SDK - [ ] **Stale vs unavailable** (#58, D44): a field whose group heartbeat is missed greys; a field sent as its type's sentinel renders `--`; a constant value under changed-keys-only updates does **neither** - [ ] Per-type sentinel handling, including that zero is never mistaken for unavailable
Owner

PR #82 lands a first, partial round of this issue's host test harness, scoped to what
currently exists in watchapp/src/c/ (fields.c/page.c from #58/PR #81). Not closing this issue —
several acceptance criteria below depend on work that hasn't landed yet.

Satisfied by #82

  • Host build of the testable watchapp sources, with the Pebble SDK surface stubbed where
    touched (watchapp/tests/stubs/pebble.h — the persist_* calls in page.c)
  • Small assertion harness — no framework dependency (watchapp/tests/harness.h, ~90 lines)
  • Field store: set, read, absent-renders---, zero-is-not-absent
  • Formatter table: every FieldId formats and renders its unavailable text — metric only.
    Imperial is blocked on #44 (not yet built); not faked here.
  • Staleness: a value greys once older than its group's threshold, and recovers — tests the
    actual group-heartbeat model PR #81 built (D44: stale vs unavailable are different states, a
    group heartbeat ages a whole group, not a single key)
  • Descriptor pack/unpack round-trips, and rejects unknown field ids and out-of-range template
    ids — mirrors #61's future validation at the level this API exists at today. ("wrong slot
    counts" doesn't apply to page_descriptor_unpack's fixed-size signature; that's a concern for
    whatever decodes a variable-length CONFIG_PAGES payload into it, once #61 exists.)
  • Runs in CI (#67): watchapp/tests/CMakeLists.txt now exists, so fast-lane.yml's
    host-c-tests job's skip guard finds it and will build+run it once a runner is registered.
  • Per-type sentinel handling (u8/u16/u32), including that zero is never mistaken for
    unavailable — from the 2026-09-02 update
  • Stale vs unavailable (#58, D44) — from the 2026-09-02 update

Still blocked — not built here, not faked

  • Page rules: sourceless page skipped in the carousel, last enabled page can't be skipped away
    — blocked on #60 (page carousel), not yet built. page_has_live_field (the primitive #60
    will consume) is tested in #82 as far as it currently goes.
  • Template geometry: no slot exceeds its bounds on emery/gabbro/basalt — blocked on #59
    (template geometry), not yet built
  • Map projection: anchor-relative decimetre offsets to screen pixels — blocked on #42
    (map projection), not yet built

A future PR should extend watchapp/tests/ (test_fields.c, test_page.c, plus new test_*.c files)
rather than rebuilding the harness — the CMakeLists.txt, harness.h and stubs/ are all meant to be
shared across whichever of #59/#60/#42/#44 lands next.

https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt

PR #82 lands a first, partial round of this issue's host test harness, scoped to what currently exists in `watchapp/src/c/` (fields.c/page.c from #58/PR #81). Not closing this issue — several acceptance criteria below depend on work that hasn't landed yet. ## Satisfied by #82 - [x] Host build of the testable watchapp sources, with the Pebble SDK surface stubbed where touched (`watchapp/tests/stubs/pebble.h` — the `persist_*` calls in page.c) - [x] Small assertion harness — no framework dependency (`watchapp/tests/harness.h`, ~90 lines) - [x] Field store: set, read, absent-renders-`--`, zero-is-not-absent - [x] Formatter table: every `FieldId` formats and renders its unavailable text — **metric only**. Imperial is blocked on #44 (not yet built); not faked here. - [x] Staleness: a value greys once older than its group's threshold, and recovers — tests the actual group-heartbeat model PR #81 built (D44: stale vs unavailable are different states, a group heartbeat ages a whole group, not a single key) - [x] Descriptor pack/unpack round-trips, and rejects unknown field ids and out-of-range template ids — mirrors #61's future validation at the level this API exists at today. ("wrong slot counts" doesn't apply to `page_descriptor_unpack`'s fixed-size signature; that's a concern for whatever decodes a variable-length CONFIG_PAGES payload into it, once #61 exists.) - [x] Runs in CI (#67): `watchapp/tests/CMakeLists.txt` now exists, so `fast-lane.yml`'s `host-c-tests` job's skip guard finds it and will build+run it once a runner is registered. - [x] Per-type sentinel handling (u8/u16/u32), including that zero is never mistaken for unavailable — from the 2026-09-02 update - [x] Stale vs unavailable (#58, D44) — from the 2026-09-02 update ## Still blocked — not built here, not faked - [ ] Page rules: sourceless page skipped in the carousel, last enabled page can't be skipped away — blocked on **#60** (page carousel), not yet built. `page_has_live_field` (the primitive #60 will consume) is tested in #82 as far as it currently goes. - [ ] Template geometry: no slot exceeds its bounds on emery/gabbro/basalt — blocked on **#59** (template geometry), not yet built - [ ] Map projection: anchor-relative decimetre offsets to screen pixels — blocked on **#42** (map projection), not yet built A future PR should extend `watchapp/tests/` (test_fields.c, test_page.c, plus new test_*.c files) rather than rebuilding the harness — the CMakeLists.txt, harness.h and stubs/ are all meant to be shared across whichever of #59/#60/#42/#44 lands next. https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#68
No description provided.