diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-18 21:04:06 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-18 21:04:06 +0000 |
| commit | b51ba30f1c77bcadfbf96b2eb7b4917c20faa7b0 (patch) | |
| tree | 5d4d3f5c258de177d12882ffa17803e8df323ca8 /bin/gradle-verify | |
Add devtools: build/test wrappers that can't be misread
Three times in one session I reported a build or test result that was about
something other than what I thought I was testing:
- A failed `cd` meant Gradle ran in the previous directory; the "BUILD
SUCCESSFUL" I reported was for the wrong tree.
- `adb shell monkey` + a single `pidof` sample reported a healthy app as
crashed and a crashed app as healthy, on the same emulator minutes apart.
- `adb install` refused a filename and printed so to stdout, which was
redirected to /dev/null; the old build stayed installed and got tested.
All three share a cause: concluding from console text rather than from exit
codes and structured artifacts. These wrappers make that mistake hard.
gradle-verify pins the project with -p from an absolutely-resolved argument,
treats a missing dir as fatal rather than falling back to cwd,
and reports from the exit code.
gradle-test parses JUnit XML for counts, and fails on zero tests executed,
on no XML at all, and on results whose mtime predates the run
(Gradle reporting UP-TO-DATE and skipping the task).
android-launch-check
resolves the launcher activity from the package manager rather
than guessing, starts logcat capture before launching, uses
`am start -W`, samples the pid across a settle window so a
process that starts and then dies is a failure, and prints the
fatal stack. Optionally installs an APK first and verifies the
install actually reported Success.
Two bugs of exactly this class were found and fixed in the scripts themselves
while testing them, both of which broke the crash-detection path specifically:
`... | grep -q` under pipefail SIGPIPEs its upstream and reports failure on a
successful match, and `pid=$(pidof ...)` under `set -e` aborts the script at the
moment the process is gone.
Verified against the real projects: nav/android-app 241 tests, nav/test-runner
122, feedreader 39, and both the pass and crash paths of android-launch-check on
a live emulator. gradle-test immediately caught nav reporting UP-TO-DATE with
33 stale result files, which naively read as "241 tests pass" without running
one.
12 self-tests, mostly negative cases.
Diffstat (limited to 'bin/gradle-verify')
| -rwxr-xr-x | bin/gradle-verify | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/bin/gradle-verify b/bin/gradle-verify new file mode 100755 index 0000000..73fb69d --- /dev/null +++ b/bin/gradle-verify @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# Run Gradle tasks against an explicitly-named project and report the result from +# the exit code, never from the console text. +# +# Why this exists: reading "BUILD SUCCESSFUL" out of piped Gradle output is not a +# reliable signal. A shell chain like `cd dir && cp f . ` followed on the next +# line by `./gradlew test` will happily run Gradle in whatever directory the +# shell was already in if the cd failed, and the success line it prints is then +# about a different project entirely. That failure mode is silent and reads +# exactly like success. +# +# Usage: +# gradle-verify <project-dir> <gradle-task...> [-- <extra gradle args>] +# +# Exits with Gradle's own exit code. Prints a summary block that names the +# absolute project directory actually used. + +set -euo pipefail + +die() { printf '%s: %s\n' "$(basename "$0")" "$*" >&2; exit 2; } + +[ $# -ge 2 ] || die "usage: $(basename "$0") <project-dir> <gradle-task...> [-- <gradle args>]" + +raw_dir=$1; shift + +# Resolve to an absolute path up front and fail loudly if it is not what we +# expect. Every later step uses this resolved value, so there is nothing for an +# ambient cwd to influence. +project_dir=$(cd "$raw_dir" 2>/dev/null && pwd) \ + || die "project dir does not exist or is not readable: $raw_dir" +[ -x "$project_dir/gradlew" ] \ + || die "no executable gradlew in: $project_dir" + +tasks=() +extra=() +seen_sep=0 +for arg in "$@"; do + if [ "$arg" = "--" ]; then seen_sep=1; continue; fi + if [ $seen_sep -eq 1 ]; then extra+=("$arg"); else tasks+=("$arg"); fi +done +[ ${#tasks[@]} -ge 1 ] || die "no gradle tasks given" + +printf '=== gradle-verify ===\n' +printf 'project : %s\n' "$project_dir" +printf 'tasks : %s\n' "${tasks[*]}" +[ ${#extra[@]} -gt 0 ] && printf 'args : %s\n' "${extra[*]}" + +# -p pins the project directory regardless of cwd. Note gradlew is invoked by +# absolute path too, so we are not depending on PATH or on cwd for the wrapper +# either. +set +e +"$project_dir/gradlew" -p "$project_dir" "${tasks[@]}" ${extra[@]+"${extra[@]}"} +status=$? +set -e + +printf 'exit : %d\n' "$status" +if [ $status -eq 0 ]; then + printf 'result : PASS\n' +else + printf 'result : FAIL\n' +fi +exit $status |
