1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
# devtools
Small, shared wrappers around Gradle and adb whose only job is to make a build or
test result **impossible to misread**.
These exist because of a specific, repeated failure: concluding that something
passed or failed by reading console text, when the text was about a different
project, a stale result, or a command that never ran. Every script here reports
from exit codes and structured artifacts instead, and fails loudly rather than
defaulting to something plausible.
Invoke by absolute path — `/workspace/devtools/bin/<script>`. Nothing needs to be
on `PATH`, which is itself one less piece of ambient state to get wrong.
---
## The rule these encode
> **Never conclude from grepped stdout. Conclude from exit codes and structured
> artifacts.**
Corollaries, each learned the hard way:
| Trap | What it looks like | Guard |
| --- | --- | --- |
| cwd drift | `cd x && cp y .` fails; the next line runs Gradle in the old directory and prints `BUILD SUCCESSFUL` about the wrong project | `gradle-verify` takes the project dir as an argument, resolves it absolutely, and passes `-p`. A missing dir is fatal, never a fallback. |
| Stale test results | Gradle reports `UP-TO-DATE`, runs nothing, and the XML on disk describes older code | `gradle-test` compares result mtimes against a marker written at run start and refuses stale numbers |
| Zero tests | A test source set that won't compile leaves a green build with nothing executed | `gradle-test` fails when the parsed test count is 0 |
| `cmd \| tail; echo $?` | Reports `tail`'s status, not the command's | Redirect to a file, then check `$?`. Or use `PIPESTATUS[0]`. |
| `... \| grep -q` under `pipefail` | `grep -q` exits on first match and SIGPIPEs upstream; the pipeline reports failure despite matching | Do membership tests in pure shell (`case`), not pipelines |
| `set -e` + a command that legitimately fails | `pid=$(pidof x)` aborts the whole script when the process is gone — i.e. exactly when you are trying to detect a crash | `|| true` on any substitution whose non-zero status is meaningful data |
| Discarded command output | `adb install ... >/dev/null` hides "filename doesn't end .apk"; the old build stays installed and is silently what gets tested | `android-launch-check --apk` checks the install output for `Success` |
| `monkey` as a launch check | Reports a healthy app dead and a dead app healthy; lands on whatever activity is foreground | `android-launch-check` uses `am start -W` against a resolved activity and samples the pid repeatedly |
---
## Scripts
### `gradle-verify <project-dir> <task...> [-- <gradle args>]`
Runs Gradle against an explicitly named project. Exits with Gradle's exit code.
Prints the absolute project directory actually used, so the output is
self-describing.
```
/workspace/devtools/bin/gradle-verify /workspace/nav/android-app compileDebugKotlin
```
### `gradle-test <project-dir> [task] [--allow-stale] [-- <gradle args>]`
Runs the test task, then reports counts parsed from the JUnit XML. Auto-detects
`testDebugUnitTest` for Android projects and `test` otherwise.
Fails on: non-zero Gradle exit, any failure or error in the XML, **zero tests
executed**, **no XML at all**, or **results that predate this run**.
```
/workspace/devtools/bin/gradle-test /workspace/nav/android-app -- --rerun-tasks
```
`--rerun-tasks` is usually what you want when the answer matters — otherwise
Gradle may skip the task and the script will (correctly) refuse the stale
results.
### `android-launch-check <package> [--apk <path>] [--serial <s>] [--settle <secs>]`
Answers "does this build actually start and stay up", and shows the exception if
not. Optionally installs an APK first, verifying the install genuinely succeeded.
Exit codes: `0` launched and stayed up · `1` crashed or died · `2` operator error
(no device, package not installed, unusable filename).
```
/workspace/devtools/bin/android-launch-check org.terst.feedreader \
--apk /path/to/app-debug.apk --settle 15
```
---
## Tests
```
/workspace/devtools/test/run-tests.sh
```
12 cases, mostly negative — each drives a script into one failure mode and
asserts it is caught. A wrapper that only ever reports PASS is worse than no
wrapper, so the negative cases are the point.
|