summaryrefslogtreecommitdiff
path: root/bin/gradle-test
diff options
context:
space:
mode:
Diffstat (limited to 'bin/gradle-test')
-rwxr-xr-xbin/gradle-test140
1 files changed, 140 insertions, 0 deletions
diff --git a/bin/gradle-test b/bin/gradle-test
new file mode 100755
index 0000000..5b059d7
--- /dev/null
+++ b/bin/gradle-test
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+#
+# Run a Gradle test task and report counts parsed from the JUnit XML it wrote,
+# not from console text.
+#
+# Catches three failure modes that all look like success from the console:
+#
+# 1. Zero tests ran. A unit-test source set that fails to compile, or a filter
+# that matches nothing, can leave the build green with nothing executed.
+# "BUILD SUCCESSFUL" and "0 tests" are the same line of output.
+# 2. Stale results. If Gradle decides the test task is UP-TO-DATE it does not
+# re-run anything, and the XML on disk describes an older version of the
+# code. Reading those numbers as current is how a passing suite gets
+# reported for code that was never tested.
+# 3. Results from the wrong project, via cwd drift — handled by gradle-verify.
+#
+# Usage:
+# gradle-test <project-dir> [test-task] [--allow-stale] [-- <gradle args>]
+#
+# Default task: testDebugUnitTest for Android projects, test otherwise.
+
+set -euo pipefail
+
+here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+die() { printf '%s: %s\n' "$(basename "$0")" "$*" >&2; exit 2; }
+
+[ $# -ge 1 ] || die "usage: $(basename "$0") <project-dir> [test-task] [--allow-stale] [-- <gradle args>]"
+
+raw_dir=$1; shift
+project_dir=$(cd "$raw_dir" 2>/dev/null && pwd) || die "no such project dir: $raw_dir"
+
+task=""
+allow_stale=0
+extra=()
+seen_sep=0
+for arg in "$@"; do
+ if [ "$arg" = "--" ]; then seen_sep=1; continue; fi
+ if [ $seen_sep -eq 1 ]; then extra+=("$arg"); continue; fi
+ case "$arg" in
+ --allow-stale) allow_stale=1 ;;
+ -*) extra+=("$arg") ;;
+ *) task=$arg ;;
+ esac
+done
+
+if [ -z "$task" ]; then
+ if grep -rqs "com.android.application\|com.android.library" \
+ "$project_dir"/build.gradle "$project_dir"/build.gradle.kts \
+ "$project_dir"/*/build.gradle "$project_dir"/*/build.gradle.kts 2>/dev/null; then
+ task=testDebugUnitTest
+ else
+ task=test
+ fi
+fi
+
+# Marker file: anything written at or after this is from the run we just did.
+# Using a file rather than a clock reading avoids any filesystem/shell timestamp
+# granularity mismatch.
+marker=$(mktemp)
+trap 'rm -f "$marker"' EXIT
+sleep 1 # ensure a strictly-earlier mtime than anything the build writes
+
+set +e
+"$here/gradle-verify" "$project_dir" "$task" ${extra[@]+-- "${extra[@]}"}
+gradle_status=$?
+set -e
+
+printf '\n=== gradle-test results ===\n'
+
+mapfile -t all_xml < <(find "$project_dir" -path '*/test-results/*' -name 'TEST-*.xml' 2>/dev/null | sort)
+if [ ${#all_xml[@]} -eq 0 ]; then
+ printf 'result : FAIL — no JUnit XML found anywhere under %s\n' "$project_dir"
+ printf 'hint : the test task may not have run, or writes results elsewhere\n'
+ exit 1
+fi
+
+fresh=(); stale=()
+for f in "${all_xml[@]}"; do
+ if [ "$f" -nt "$marker" ]; then fresh+=("$f"); else stale+=("$f"); fi
+done
+
+printf 'task : %s\n' "$task"
+printf 'xml : %d file(s) total, %d fresh, %d stale\n' \
+ "${#all_xml[@]}" "${#fresh[@]}" "${#stale[@]}"
+
+if [ ${#fresh[@]} -eq 0 ]; then
+ if [ $allow_stale -eq 1 ]; then
+ printf 'note : no fresh results; counting stale ones because --allow-stale\n'
+ fresh=("${all_xml[@]}")
+ else
+ printf 'result : FAIL — every result file predates this run\n'
+ printf 'hint : Gradle treated %s as UP-TO-DATE, so these numbers describe\n' "$task"
+ printf ' older code. Re-run with: -- --rerun-tasks\n'
+ exit 1
+ fi
+fi
+
+counts=$(python3 - "${fresh[@]}" <<'PY'
+import sys, xml.etree.ElementTree as ET
+tests = failures = errors = skipped = 0
+bad = []
+for p in sys.argv[1:]:
+ try:
+ r = ET.parse(p).getroot()
+ except Exception as e:
+ bad.append(f"{p}: unparseable ({e})")
+ continue
+ tests += int(r.get('tests', 0))
+ failures += int(r.get('failures', 0))
+ errors += int(r.get('errors', 0))
+ skipped += int(r.get('skipped', 0))
+print(f"{tests} {failures} {errors} {skipped}")
+for b in bad:
+ print("UNPARSEABLE " + b, file=sys.stderr)
+PY
+)
+read -r tests failures errors skipped <<<"$counts"
+
+printf 'tests : %s\n' "$tests"
+printf 'failures: %s\n' "$failures"
+printf 'errors : %s\n' "$errors"
+printf 'skipped : %s\n' "$skipped"
+
+rc=0
+[ "$gradle_status" -ne 0 ] && rc=1
+[ "$failures" -gt 0 ] && rc=1
+[ "$errors" -gt 0 ] && rc=1
+if [ "$tests" -eq 0 ]; then
+ printf 'result : FAIL — zero tests executed\n'
+ printf 'hint : a green build with no tests is not a passing suite\n'
+ exit 1
+fi
+
+if [ $rc -eq 0 ]; then
+ printf 'result : PASS (%s tests, gradle exit %d)\n' "$tests" "$gradle_status"
+else
+ printf 'result : FAIL (gradle exit %d, %s failures, %s errors)\n' \
+ "$gradle_status" "$failures" "$errors"
+fi
+exit $rc