rouse-context/scripts/tests
monkopedia-coder 3783728e2d
Some checks are pending
Android CI / test (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Build & Test (push) Waiting to run
Give the two untested CI gates a self-test, and mechanise the rule (#705) (#759)
* Give the two untested CI gates a self-test, and mechanise the rule (#705)

`scripts/lib/gate-filters.sh` opens with the rule, deliberately as the first
thing a new gate author reads: every `scripts/check-*.sh` MUST ship a matching
`scripts/tests/<gate>-test.sh` and MUST have it wired into CI -- "that
requirement, not this file, is the part that catches the bug". It was a
comment, and on main it was false for two of the nine gates.

check-apk-distribution.sh is the one that matters. release.yml runs it as
"Staged FOSS APK contains no Google/Firebase code"; it is the whole of #548's
answer, and a violation would be invisible until someone decompiled a shipped
APK. It had never been observed going red. Being invoked in CI is not the same
as being known to work -- #678's step-order gate ran on every PR for months
while exiting 1 with zero output on the two inputs it existed to catch, and
#686's backfill exited 0 after a failed secret push.

Every case in both harnesses is a demonstrated control, red case first.

scripts/tests/check-apk-distribution-test.sh (42 cases). FIXTURES, NOT REAL
APKs: the gate reads an APK as a zip of files and greps the bytes, so a zip of
text files exercises every path it has -- the 1MB size floor, the dex-presence
check and the com/rousecontext control included -- in about two seconds with no
SDK. Red: firebase and gms DEX class references separately; each of the three
google-services string resources on its own; a dot-form provider in the
manifest; res/raw/firebase_common_keep.xml; --expect google over a clean APK
(the double-staging case release.yml's shared output path makes possible);
firebase without gms; a resources-only marker not satisfying --expect google;
and the five preflight refusals (missing, too small, not a zip, no dex, not
this app). Green: a clean fixture, and -- pinned because it is the judgement
most likely to be "tidied" later -- the four dot-form GMS strings a genuinely
clean FOSS build carries in its DEX, alongside the same string in the manifest
asserted RED, so the slash-vs-dot asymmetry is measured rather than assumed.
Part 5 mutates the gate so a pattern stops matching its own sample, and
requires the gate's own anti-vacuity self-test to fire (#579's failure mode).

scripts/tests/check-zombie-screens-test.sh (36 cases). #657 already found this
gate printing "OK: no zombie screens" and exiting 0 over a tree containing one,
with a stubbed-failing `awk`, so that red case is pinned rather than described:
the real gate is run with a PATH-shimmed failing `awk` and must fail by name,
and a reconstruction of the pre-#657 shape must still produce the lie on the
same tree -- otherwise the first assertion could be passing for an unrelated
reason. Also pinned: self-reference does not rescue a screen, nor a test-only
reference, nor a non-Kotlin file, nor a substring of the name; the exemption
list works and covers only the file it names. Three residues are asserted
rather than left implicit -- one live composable vouches for its whole file, a
PUBLIC @Preview composable is NOT exempt (only `private fun` is dropped), and
the gate reports OK when its screens directory does not exist, which is
green-by-absence and is filed as #758.

scripts/check-gate-selftests.sh then makes the rule mechanical: every
check-*.sh has a harness, and every harness is named in a workflow. The second
half matters more than it looks -- a harness nobody runs is a file, not a gate.
Discovery is `git ls-files`, membership is tested against the TRACKED list (an
untracked harness satisfies `[[ -f ]]` locally and does not exist in CI's
checkout), and zero gates, zero harnesses or zero workflows is a FAILURE rather
than a vacuous pass. The predicate is name-based for a content-based class --
the objection #658 raises about the shellcheck gate's `*.sh` -- and the header
says so explicitly instead of inheriting it silently: the convention is uniform
across all nine gates, proximity enforces it, and the miss direction is safe (a
gate not named check-* escapes the check; it cannot cause a false red).
backfill-relay-secrets.sh is deliberately outside the gate set, and its harness
is still covered by the wiring half -- nothing forces it to HAVE a self-test,
but having one means it must be RUN.

Its own harness (27 cases) covers both gaps, both together, the untracked-
harness case, and every way the gate could look at an empty set and call it
clean -- no gates, no harnesses, no workflows, not a work tree, no such
directory. A coverage check is the easiest kind to make vacuous (#629 proved
comparisons happened, not that they could detect anything).

All three harnesses read the gate's output with `grep` over a FILE, never
`printf ... | grep -q`. That pipeline folds three outcomes into one answer --
no match, grep could not answer, and grep matching EARLY and `pipefail`
reporting the writer's SIGPIPE for `!` to invert into "no match" -- and the
last is payload-size dependent and tracked as a live defect in #725. With no
pipe there is no writer to kill.

Four steps added to ci.yml's lint job, next to the five self-tests already
there. release.yml is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYSPuh9bZEJkrzvnEpY1QK

* TEMPORARY: a gate with no self-test, to watch #705's check go red in CI

Reverted in the next commit. `scripts/check-gate-selftests.sh` is itself a
gate, and a gate whose red case has only ever been exercised against fixtures
in its own harness is exactly what #705 is about. This makes the real CI step
fail on the real tree, once, so the link is citable.

Expected: the "Every gate ships a wired self-test (#705)" step in the lint job
exits 1 with

  1 gate(s) ship with no self-test:
    scripts/check-fixture-no-harness.sh -> scripts/tests/check-fixture-no-harness-test.sh

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYSPuh9bZEJkrzvnEpY1QK

* Revert the temporary no-harness fixture gate

The point is made: run 34925372964, job Lint, step "Every gate ships a wired
self-test (#705)" exited 1 on the real tree with

  1 gate(s) ship with no self-test:
    scripts/check-fixture-no-harness.sh -> scripts/tests/check-fixture-no-harness-test.sh

and the two new self-test steps ahead of it passed on the same run. The
coverage gate is now a demonstrated red, not an assumed one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYSPuh9bZEJkrzvnEpY1QK

---------

Co-authored-by: Monkopedia <monkopedia@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:20:07 -04:00
..
backfill-relay-secrets-test.sh Make a partial secret backfill fail loudly instead of exiting 0 (#686) (#693) 2026-09-02 23:23:12 -04:00
check-apk-distribution-test.sh Give the two untested CI gates a self-test, and mechanise the rule (#705) (#759) 2026-09-15 00:20:07 -04:00
check-cancellation-catch-order-test.sh Gate: a broad catch must be preceded by a cancellation clause (#674) (#720) 2026-09-03 06:59:24 -04:00
check-gate-selftests-test.sh Give the two untested CI gates a self-test, and mechanise the rule (#705) (#759) 2026-09-15 00:20:07 -04:00
check-no-manual-json-test.sh Share one anchoring helper across the grep gates (#597) (#697) 2026-09-03 00:31:25 -04:00
check-no-production-runblocking-test.sh Share one anchoring helper across the grep gates (#597) (#697) 2026-09-03 00:31:25 -04:00
check-no-sensitive-logging-test.sh Tell a grep that answered "no" from one that never answered (#716) (#726) 2026-09-03 15:32:22 -04:00
check-roborazzi-coverage-test.sh ci: verify the Roborazzi goldens, and prove the check can go red (#628) (#629) 2026-08-28 08:01:25 -04:00
check-roborazzi-step-order-test.sh Make the step-order gate report an unreadable jobs: block (#678) (#694) 2026-09-03 00:03:17 -04:00
check-shell-lint-test.sh Triage the masked-return findings and enable the two checks (#657) (#682) 2026-09-02 13:59:17 -04:00
check-zombie-screens-test.sh Give the two untested CI gates a self-test, and mechanise the rule (#705) (#759) 2026-09-15 00:20:07 -04:00
gate-filters-test.sh Share one anchoring helper across the grep gates (#597) (#697) 2026-09-03 00:31:25 -04:00