summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-09-21 19:17:27 +0000
committerClaude <noreply@anthropic.com>2026-09-21 19:17:27 +0000
commit8f94e3caa0b1b477bacbbbd8954f2cea743a9337 (patch)
tree75bcbfdb34235dbdad029a63f695b03fa3cd3df0 /test
parentb51ba30f1c77bcadfbf96b2eb7b4917c20faa7b0 (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 'test')
-rwxr-xr-xtest/run-tests.sh99
1 files changed, 99 insertions, 0 deletions
diff --git a/test/run-tests.sh b/test/run-tests.sh
index 3a8117d..6e8ae43 100755
--- a/test/run-tests.sh
+++ b/test/run-tests.sh
@@ -157,5 +157,104 @@ else
bad "usage check" "rc=$rc out=$out"
fi
+printf '=== gradle-report ===\n'
+
+# 13. A config detekt rejects means the analysis never ran; the XML on disk is a previous
+# run's and looks entirely plausible. This is the trap that was read as real findings
+# three times in one session.
+d="$tmproot/report-badconfig"
+make_project "$d" 1 'echo "> Provided configuration file is invalid: Structure must be from type Map<String,Any>!" >&2'
+mkdir -p "$d/app/build/reports/detekt"
+cat > "$d/app/build/reports/detekt/detekt.xml" <<'XML'
+<?xml version="1.0"?><checkstyle><file name="/x/Old.kt"><error line="1" source="detekt.Stale" message="from an earlier run"/></file></checkstyle>
+XML
+out=$("$BIN/gradle-report" "$d" detekt 2>&1); rc=$?
+if [ $rc -ne 0 ] && printf '%s' "$out" | grep -qi "CONFIG INVALID"; then
+ ok "names an invalid config instead of reading the stale report"
+else
+ bad "invalid config detection" "rc=$rc out=$out"
+fi
+if printf '%s' "$out" | grep -q "Stale"; then
+ bad "stale findings leaked" "the previous run's findings were printed as current"
+else
+ ok "does not print findings from an earlier run"
+fi
+
+# 14. A report older than the run is refused even when the config is fine.
+d="$tmproot/report-stale"
+make_project "$d" 0
+mkdir -p "$d/app/build/reports/detekt"
+cat > "$d/app/build/reports/detekt/detekt.xml" <<'XML'
+<?xml version="1.0"?><checkstyle></checkstyle>
+XML
+touch -d '2020-01-01' "$d/app/build/reports/detekt/detekt.xml"
+out=$("$BIN/gradle-report" "$d" detekt 2>&1); rc=$?
+if [ $rc -ne 0 ] && printf '%s' "$out" | grep -qi "STALE"; then
+ ok "refuses a report that predates the run"
+else
+ bad "stale report detection" "rc=$rc out=$out"
+fi
+
+# 15. A missing project directory is fatal, never a silent fallback to cwd.
+out=$("$BIN/gradle-report" "$tmproot/nope" detekt 2>&1); rc=$?
+if [ $rc -eq 2 ]; then ok "missing project dir is fatal"; else bad "missing dir" "rc=$rc"; fi
+
+printf '=== android-ui ===\n'
+
+# 16. Usage validation without a device.
+out=$("$BIN/android-ui" 2>&1); rc=$?
+if [ $rc -eq 2 ] && printf '%s' "$out" | grep -qi "usage"; then
+ ok "requires package and command"
+else
+ bad "usage check" "rc=$rc out=$out"
+fi
+
+# 17. The node parser is where a blind tap goes wrong, so it is tested directly.
+LIB=$(cd "$BIN/../lib" && pwd)
+dump='<hierarchy><node text="" content-desc="Play" bounds="[0,0][100,100]" clickable="true"/><node text="Settings" content-desc="" bounds="[200,0][300,100]" clickable="true"/></hierarchy>'
+out=$(printf '%s' "$dump" | "$LIB/uinodes.py" find "Play")
+if [ "$out" = "50 50" ]; then ok "finds a node by content-desc"; else bad "find by desc" "got '$out'"; fi
+out=$(printf '%s' "$dump" | "$LIB/uinodes.py" find "Settings")
+if [ "$out" = "250 50" ]; then ok "finds a node by text"; else bad "find by text" "got '$out'"; fi
+out=$(printf '%s' "$dump" | "$LIB/uinodes.py" find "Nope")
+if [ "$out" = "NONE" ]; then ok "reports a missing label"; else bad "missing label" "got '$out'"; fi
+
+# 18. Several matches must refuse rather than tap the first -- that is how you act on the
+# wrong row in a list where every entry has the same button.
+dup='<hierarchy><node content-desc="Play" bounds="[0,0][100,100]" clickable="true"/><node content-desc="Play" bounds="[0,200][100,300]" clickable="true"/></hierarchy>'
+out=$(printf '%s' "$dup" | "$LIB/uinodes.py" find "Play")
+case "$out" in AMBIGUOUS*) ok "refuses to guess between duplicate labels" ;;
+ *) bad "ambiguity" "got '$out'" ;; esac
+
+# 19. A label and the button carrying it are often separate nodes; tapping the label does
+# nothing, so a clickable node wins.
+pair='<hierarchy><node text="Play" bounds="[0,0][100,100]" clickable="false"/><node content-desc="Play" bounds="[500,500][600,600]" clickable="true"/></hierarchy>'
+out=$(printf '%s' "$pair" | "$LIB/uinodes.py" find "Play")
+if [ "$out" = "550 550" ]; then ok "prefers the clickable node"; else bad "clickable preference" "got '$out'"; fi
+
+printf '=== publish-apk ===\n'
+
+# 20. Publishing from the wrong host is how a sandbox once got "deployed" to.
+out=$(HOSTNAME=x "$BIN/publish-apk" /dev/null thing.apk --host definitely-not-this-host 2>&1); rc=$?
+if [ $rc -eq 2 ] && printf '%s' "$out" | grep -qi "expected"; then
+ ok "refuses to publish from the wrong host"
+else
+ bad "host guard" "rc=$rc out=$out"
+fi
+
+# 21. A published name with a path would write outside the intended directory.
+out=$("$BIN/publish-apk" /dev/null ../escape.apk --host "$(hostname)" 2>&1); rc=$?
+if [ $rc -eq 2 ]; then ok "rejects a path in the published name"; else bad "name guard" "rc=$rc"; fi
+
+# 22. An HTML error page saved as .apk is not an APK; publishing it would serve a file that
+# installs as nothing.
+notapk="$tmproot/not.apk"; printf '<!DOCTYPE html><html>nope</html>' > "$notapk"
+out=$("$BIN/publish-apk" "$notapk" thing.apk --host "$(hostname)" 2>&1); rc=$?
+if [ $rc -eq 2 ] && printf '%s' "$out" | grep -qi "not a zip"; then
+ ok "rejects a non-APK"
+else
+ bad "apk validation" "rc=$rc out=$out"
+fi
+
printf '\n=== summary: %d passed, %d failed ===\n' "$pass" "$fail"
[ "$fail" -eq 0 ]