Skip to content

Commit 76c95bd

Browse files
[issue-1079] feat(tests): add CI script to compare test execution totals
A CI-friendly bash script was added to compare test execution totals before and after the PR changes. The script runs Maven tests on two git refs, sums Surefire XML reports, and exits non-zero if test totals differ. Usage instructions and example commands for CI integration were provided.
1 parent 0c03943 commit 76c95bd

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed

scripts/compare-test-totals.sh

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Compare Maven Surefire test execution totals between two revisions.
5+
#
6+
# Usage:
7+
# scripts/compare-test-totals.sh [BASE_REF] [HEAD_REF]
8+
#
9+
# Defaults:
10+
# BASE_REF: origin/main (or main if origin/main not found)
11+
# HEAD_REF: HEAD (current checkout)
12+
#
13+
# Notes:
14+
# - Uses git worktree to create a clean checkout for BASE_REF under ./.worktrees/base
15+
# - Runs tests with project-recommended options:
16+
# MAVEN_OPTS: -Xms3g -Xmx6g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500
17+
# Maven args: -B -Dmaven.javadoc.skip=true -PskipBundlePlugin,minimal-fix-latest test
18+
# - Sums tests, failures, errors, skipped across all modules from Surefire XML reports
19+
# - Prints a summary table and exits non-zero if totals differ
20+
# - Requires Java 8 on PATH (will warn if not JDK 8)
21+
22+
BASE_REF=${1:-}
23+
HEAD_REF=${2:-HEAD}
24+
25+
if [[ -z "${BASE_REF}" ]]; then
26+
if git show-ref --verify --quiet refs/remotes/origin/main; then
27+
BASE_REF="origin/main"
28+
else
29+
BASE_REF="main"
30+
fi
31+
fi
32+
33+
# Ensure we're at repo root (script lives in scripts/)
34+
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
35+
REPO_ROOT=$(cd -- "${SCRIPT_DIR}/.." && pwd)
36+
cd "${REPO_ROOT}"
37+
38+
# Check Java version (warn only)
39+
if command -v java >/dev/null 2>&1; then
40+
JAVA_VER=$(java -version 2>&1 | head -n1 | sed -E 's/.*version "([^"]+)".*/\1/')
41+
case "${JAVA_VER}" in
42+
1.8.*|8*) : ;; # ok
43+
*) echo "[warn] Java version is ${JAVA_VER}. The project requests JDK 8 for this check." >&2 ;;
44+
esac
45+
else
46+
echo "[warn] java not found on PATH; proceeding but Maven will likely fail." >&2
47+
fi
48+
49+
# Maven options per project guidelines
50+
export MAVEN_OPTS="-Xms3g -Xmx6g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500"
51+
MVN_CMD=("./mvnw" -B -Dmaven.javadoc.skip=true -PskipBundlePlugin,minimal-fix-latest test)
52+
53+
WORKTREES_DIR="${REPO_ROOT}/.worktrees"
54+
BASE_DIR="${WORKTREES_DIR}/base"
55+
56+
cleanup() {
57+
set +e
58+
if [[ -d "${BASE_DIR}" ]]; then
59+
git worktree remove --force "${BASE_DIR}" >/dev/null 2>&1 || true
60+
rm -rf "${BASE_DIR}" >/dev/null 2>&1 || true
61+
fi
62+
}
63+
trap cleanup EXIT
64+
65+
mkdir -p "${WORKTREES_DIR}"
66+
67+
# Add worktree for base
68+
if [[ -d "${BASE_DIR}" ]]; then
69+
git worktree remove --force "${BASE_DIR}" >/dev/null 2>&1 || true
70+
rm -rf "${BASE_DIR}"
71+
fi
72+
73+
echo "[info] Preparing base worktree at ${BASE_DIR} for ${BASE_REF}"
74+
git fetch --all --prune --tags >/dev/null 2>&1 || true
75+
# Use --detach to avoid creating a new branch
76+
git worktree add --detach "${BASE_DIR}" "${BASE_REF}" >/dev/null
77+
78+
# Function to run tests and collect totals
79+
run_and_collect() {
80+
local dir="$1"
81+
local label="$2"
82+
echo "[info] Running tests for ${label} in ${dir}"
83+
(cd "${dir}" && "${MVN_CMD[@]}" >/dev/null)
84+
85+
# Find all Surefire XML suite reports and sum attributes
86+
# We prefer files named TEST-*.xml (per-class), but also include top-level *-suite.xml if present.
87+
local xml_files
88+
# shellcheck disable=SC2207
89+
xml_files=($(find "${dir}" -type f \( -path "*/target/surefire-reports/TEST-*.xml" -o -path "*/target/surefire-reports/*-suite.xml" -o -path "*/target/surefire-reports/*.xml" \) -print))
90+
91+
if [[ ${#xml_files[@]} -eq 0 ]]; then
92+
echo "tests=0 failures=0 errors=0 skipped=0"; return 0
93+
fi
94+
95+
awk 'BEGIN{tests=0;fail=0;err=0;skip=0}
96+
/<testsuite /{
97+
if(match($0, /tests="([0-9]+)"/, a)) tests+=a[1];
98+
if(match($0, /failures="([0-9]+)"/, a)) fail+=a[1];
99+
if(match($0, /errors="([0-9]+)"/, a)) err+=a[1];
100+
if(match($0, /skipped="([0-9]+)"/, a)) skip+=a[1];
101+
}
102+
END{printf("tests=%d failures=%d errors=%d skipped=%d\n", tests, fail, err, skip)}' "${xml_files[@]}"
103+
}
104+
105+
# Base
106+
BASE_SUMMARY=$(run_and_collect "${BASE_DIR}" "BASE:${BASE_REF}")
107+
BASE_TESTS=$(echo "${BASE_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="tests") print a[2]}}')
108+
BASE_FAIL=$(echo "${BASE_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="failures") print a[2]}}')
109+
BASE_ERR=$(echo "${BASE_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="errors") print a[2]}}')
110+
BASE_SKIP=$(echo "${BASE_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="skipped") print a[2]}}')
111+
112+
# Head (current checkout or provided ref via another worktree when not HEAD)
113+
HEAD_DIR="${REPO_ROOT}"
114+
if [[ "${HEAD_REF}" != "HEAD" ]]; then
115+
# create a transient worktree for explicit HEAD_REF so current tree remains untouched
116+
HEAD_DIR="${WORKTREES_DIR}/head"
117+
git worktree add --detach "${HEAD_DIR}" "${HEAD_REF}" >/dev/null
118+
fi
119+
120+
HEAD_SUMMARY=$(run_and_collect "${HEAD_DIR}" "HEAD:${HEAD_REF}")
121+
HEAD_TESTS=$(echo "${HEAD_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="tests") print a[2]}}')
122+
HEAD_FAIL=$(echo "${HEAD_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="failures") print a[2]}}')
123+
HEAD_ERR=$(echo "${HEAD_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="errors") print a[2]}}')
124+
HEAD_SKIP=$(echo "${HEAD_SUMMARY}" | awk '{for(i=1;i<=NF;i++){split($i,a,"="); if(a[1]=="skipped") print a[2]}}')
125+
126+
# If we created a HEAD worktree, ensure it gets cleaned as well on exit
127+
if [[ -d "${WORKTREES_DIR}/head" ]]; then
128+
trap 'git worktree remove --force "${WORKTREES_DIR}/head" >/dev/null 2>&1 || true; cleanup' EXIT
129+
fi
130+
131+
# Print summary
132+
printf "\nTest totals (summed across modules)\n"
133+
printf "%-12s %10s %10s %10s %10s\n" "Ref" "Tests" "Failures" "Errors" "Skipped"
134+
printf "%-12s %10d %10d %10d %10d\n" "BASE" "${BASE_TESTS}" "${BASE_FAIL}" "${BASE_ERR}" "${BASE_SKIP}"
135+
printf "%-12s %10d %10d %10d %10d\n" "HEAD" "${HEAD_TESTS}" "${HEAD_FAIL}" "${HEAD_ERR}" "${HEAD_SKIP}"
136+
137+
DT=$(( HEAD_TESTS - BASE_TESTS ))
138+
DF=$(( HEAD_FAIL - BASE_FAIL ))
139+
DE=$(( HEAD_ERR - BASE_ERR ))
140+
DS=$(( HEAD_SKIP - BASE_SKIP ))
141+
142+
printf "%-12s %10s %10s %10s %10s\n" "Δ(HEAD-BASE)" "${DT}" "${DF}" "${DE}" "${DS}"
143+
144+
# Exit non-zero if overall test count differs
145+
if [[ ${DT} -ne 0 ]]; then
146+
echo "\n[fail] Test count changed by ${DT} (HEAD=${HEAD_TESTS}, BASE=${BASE_TESTS})." >&2
147+
exit 1
148+
fi
149+
150+
# Also flag if failures/errors increased
151+
if [[ ${DF} -gt 0 || ${DE} -gt 0 ]]; then
152+
echo "\n[warn] Failures/errors increased (Δfailures=${DF}, Δerrors=${DE})." >&2
153+
fi
154+
155+
echo "\n[ok] Test totals match between ${BASE_REF} and ${HEAD_REF}."

0 commit comments

Comments
 (0)