diff options
| author | Claude <noreply@anthropic.com> | 2026-09-21 19:17:27 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-09-21 19:17:27 +0000 |
| commit | 8f94e3caa0b1b477bacbbbd8954f2cea743a9337 (patch) | |
| tree | 75bcbfdb34235dbdad029a63f695b03fa3cd3df0 /bin/gradle-report | |
| parent | b51ba30f1c77bcadfbf96b2eb7b4917c20faa7b0 (diff) | |
Add gradle-report, android-ui and publish-apk
Three more wrappers, each from a failure that actually happened while building
Listen rather than an imagined one.
gradle-report: detekt rejects an invalid config property before analysing
anything, exits non-zero, and leaves the previous run's XML in place. That file
was read as current findings three times in one session; the tell was line
numbers that never moved after a refactor. It now names the rejected property
and refuses any report older than the run. Both real shapes of the message are
covered -- a misspelled property and structurally invalid YAML say different
things.
android-ui: a blind `input tap 400 300` landed in FeeDreader's alarm settings,
and the screenshot taken immediately after was reported as Listen. Every
command verifies the foreground package before acting, and taps resolve by
label rather than coordinate. A label matching several elements refuses rather
than taking the first -- in a list where every row has the same Play button,
the first match is rarely the one meant.
The node parsing is a separate file on purpose. The first version inlined it as
python3 -c inside a bash case block and the nested quoting mangled the regexes
silently: the dump reported one element and a tap landed somewhere else
entirely. Shell-quoted parsers are their own trap.
publish-apk: publishing is where a silent mistake costs most -- a stale
published APK once sat for eight weeks and installing it threw a Room
downgrade. It checks the host, proves the file is a real APK rather than an
HTML error page, byte-compares what landed against what was tested, and
confirms the server serves those same bytes.
Test suite is 25 cases, still weighted toward the negative ones: a wrapper that
only ever reports PASS is worse than no wrapper.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'bin/gradle-report')
| -rwxr-xr-x | bin/gradle-report | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/bin/gradle-report b/bin/gradle-report new file mode 100755 index 0000000..c59945a --- /dev/null +++ b/bin/gradle-report @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# gradle-report <project-dir> <detekt|lint> -- [gradle args] +# +# Runs a static-analysis task and reports from its XML report, refusing to speak if the report +# is older than the run. +# +# Exists because detekt has a failure mode that looks exactly like findings: an invalid config +# property makes it abort BEFORE analysing anything, leaving the previous run's XML on disk. +# Three times in one session that stale file was read as the current result, and the line +# numbers never moved because the analysis never happened. The only honest signal was the exit +# code, which said "failed" while the report said "here are your 8 findings". +set -uo pipefail + +die() { echo "gradle-report: $1" >&2; exit 2; } + +[ $# -ge 2 ] || die "usage: gradle-report <project-dir> <detekt|lint> [-- <gradle args>]" + +PROJECT_DIR=$(cd "$1" 2>/dev/null && pwd) || die "no such project directory: $1" +KIND="$2" +shift 2 +[ "${1:-}" = "--" ] && shift +EXTRA=("$@") + +case "$KIND" in + detekt) TASK=":app:detekt"; REPORT="$PROJECT_DIR/app/build/reports/detekt/detekt.xml" ;; + lint) TASK=":app:lintDebug"; REPORT="$PROJECT_DIR/app/build/lint-results-debug.xml" ;; + *) die "kind must be 'detekt' or 'lint', got '$KIND'" ;; +esac + +[ -x "$PROJECT_DIR/gradlew" ] || die "no gradlew in $PROJECT_DIR" + +# Marker predates the run, so any report not newer than it is stale. +MARKER=$(mktemp) +trap 'rm -f "$MARKER"' EXIT +sleep 1 + +LOG=$(mktemp) +( cd "$PROJECT_DIR" && ./gradlew "$TASK" "${EXTRA[@]}" ) >"$LOG" 2>&1 +STATUS=$? + +echo "=== gradle-report ($KIND) ===" +echo "project : $PROJECT_DIR" +echo "task : $TASK" +echo "exit : $STATUS" + +# An invalid config aborts detekt before it analyses anything. Name it, because the report on +# disk will be the previous run's and will look perfectly reasonable. +if grep -qiE "is misspelled or does not exist|configuration file is invalid|invalid config" "$LOG"; then + echo + echo "CONFIG INVALID — analysis never ran. Offending properties:" + grep -iE "is misspelled or does not exist|configuration file is invalid|invalid config" "$LOG" | sed 's/^/ /' | sort -u + echo + echo "result : FAIL (config rejected; any report on disk is from an earlier run)" + rm -f "$LOG" + exit 1 +fi + +if [ ! -f "$REPORT" ]; then + echo "report : MISSING ($REPORT)" + echo "result : $([ $STATUS -eq 0 ] && echo 'PASS (no report produced, nothing to read)' || echo 'FAIL')" + rm -f "$LOG" + exit $STATUS +fi + +if [ "$REPORT" -ot "$MARKER" ]; then + echo "report : STALE — $REPORT predates this run" + echo "result : FAIL — refusing to report numbers that describe older code" + rm -f "$LOG" + exit 1 +fi + +FINDINGS=$(python3 - "$REPORT" "$KIND" <<'PY' +import sys, xml.etree.ElementTree as ET +path, kind = sys.argv[1], sys.argv[2] +try: + root = ET.parse(path).getroot() +except Exception as e: + print(f"UNREADABLE {e}") + raise SystemExit +rows = [] +if kind == "detekt": + for f in root.iter("file"): + for err in f.iter("error"): + rows.append(f"{f.get('name','').split('/')[-1]}:{err.get('line')} " + f"[{(err.get('source') or '').split('.')[-1]}] {(err.get('message') or '')[:90]}") +else: + for issue in root.iter("issue"): + if issue.get("severity", "").lower() not in ("error", "fatal"): + continue + loc = next(issue.iter("location"), None) + where = f"{(loc.get('file','') if loc is not None else '').split('/')[-1]}:{loc.get('line') if loc is not None else '?'}" + rows.append(f"{where} [{issue.get('id')}] {(issue.get('message') or '')[:90]}") +print(f"COUNT {len(rows)}") +for r in rows: + print(f" {r}") +PY +) + +echo "report : fresh" +echo "$FINDINGS" | head -1 | sed 's/^COUNT /findings: /' +echo "$FINDINGS" | tail -n +2 + +COUNT=$(echo "$FINDINGS" | head -1 | awk '{print $2}') +if [ "$COUNT" = "0" ] && [ $STATUS -eq 0 ]; then + echo "result : PASS (0 findings, gradle exit 0)" +else + echo "result : FAIL (${COUNT:-?} findings, gradle exit $STATUS)" +fi +rm -f "$LOG" +[ $STATUS -eq 0 ] && [ "$COUNT" = "0" ] |
