New rule, and a hook rather than a habit. Source work needs the unit tests and the instrumented tests green at every supported API level before it is committed or pushed; test work needs the whole suite green at every level. tools/git-hooks/local-gate.sh is wired in as pre-commit and pre-push (symlinks, so shellcheck sees one file), enabled with `git config core.hooksPath tools/git-hooks`. WHAT "EVERY LEVEL" CAN MEAN HERE, measured rather than assumed. 33-36 run the whole suite on emulators. API 37 CANNOT be run on an emulator on this host at all -- not "is red", cannot run: the image logs `3 new surfaceflinger aborts in 45 s (want 0)` and the APK install then fails with `Can't find service: package`, because the framework is gone before Gradle installs anything. Starting 0 tests. So 37 runs on the attached Pixel 10 Pro XL when it is there, and the hook says plainly that the level is uncovered when it is not, rather than claiming five levels having run four. The first cut passed a notAnnotation filter through E2E_EXTRA_GRADLE_ARGS, which run-e2e.sh:587 overwrites with --rerun -- so that argument was discarded and would have been discarded silently. The sweep is cached under the app/src SUBTREE hash, not the whole repo tree. The first cut used the whole tree and that was wrong in a way that would teach people to resent this hook: editing a comment in CLAUDE.md discarded a sweep of byte-identical application code and re-ran forty minutes of emulators to prove nothing. Any change under app/src still invalidates it; the JVM gate always runs. There is deliberately no skip variable -- that would be --no-verify wearing a different hat. Why it is worth the time: #256 spent several gating legs learning one leg at a time what a sweep answers in one pass, and the failing leg MOVED between runs (API 35 red then green, API 34 green then red). One leg at a time reads as someone else's flake; as a sweep it is one signal. Also here, and the reason the rule arrived now: awaitNode treated "the app has no composition right now" as a failure rather than as not-yet. fetchSemanticsNodes throws IllegalStateException when nothing is attached and waitUntil propagates it on the first poll instead of waiting out the deadline. This class spends much of its time behind the picker, the save dialog and the permission dialog, so there is always a window where the app is coming back with no composition -- and on run 34057706195's API 34 leg both SAF tests died in it. Now it is not-yet, with the last composition error carried into the timeout message so a genuinely dead app stays diagnosable. Verified: this commit's own hook swept API 33, 34, 35 and 36 at 71/71 failed=0, API 34 included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Symbolic link
1 line
13 B
Plaintext
Symbolic link
1 line
13 B
Plaintext
local-gate.sh |