44 push :
55 branches :
66 - master
7- permissions : # For test summary bot
7+ permissions :
8+ contents : write
89 checks : write
910jobs :
1011 buildAndTest :
1112 runs-on : ubuntu-latest
1213 strategy :
1314 matrix :
14- gradle-argument : [ 'assemble && ./gradlew check -x test','testWithJava11', 'testWithJava17','testWithJava21', 'test -x testWithJava11 -x testWithJava17 -x testWithJava21' ]
15+ include :
16+ - gradle-argument : ' assemble && ./gradlew check -x test -x testng -x testngWithJava11 -x testngWithJava17 -x testngWithJava21'
17+ label : ' check'
18+ - gradle-argument : ' testWithJava11 testngWithJava11'
19+ label : ' java11'
20+ test-results-dirs : ' testWithJava11 testngWithJava11'
21+ - gradle-argument : ' testWithJava17 testngWithJava17'
22+ label : ' java17'
23+ test-results-dirs : ' testWithJava17 testngWithJava17'
24+ - gradle-argument : ' testWithJava21 testngWithJava21'
25+ label : ' java21'
26+ test-results-dirs : ' testWithJava21 testngWithJava21'
27+ - gradle-argument : ' test -x testWithJava11 -x testWithJava17 -x testWithJava21 testng jacocoTestReport'
28+ label : ' java25'
29+ test-results-dirs : ' test testng'
30+ - gradle-argument : ' jcstress'
31+ label : ' jcstress'
1532 steps :
1633 - uses : actions/checkout@v6
1734 - uses : gradle/actions/wrapper-validation@v5
@@ -21,16 +38,176 @@ jobs:
2138 java-version : ' 25'
2239 distribution : ' corretto'
2340 - name : build and test
24- run : ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
41+ run : |
42+ if [ "${{ matrix.label }}" = "jcstress" ]; then
43+ set -o pipefail
44+ mkdir -p build
45+ ./gradlew ${{matrix.gradle-argument}} --info --stacktrace 2>&1 | tee build/jcstress-output.txt
46+ else
47+ ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
48+ fi
2549 - name : Publish Test Results
2650 uses : EnricoMi/publish-unit-test-result-action@v2.23.0
27- if : always()
51+ if : always() && matrix.label != 'check' && matrix.label != 'jcstress'
2852 with :
2953 files : |
30- **/build/test-results/test/TEST-*.xml
31- **/build/test-results/testWithJava11/TEST-*.xml
32- **/build/test-results/testWithJava17/TEST-*.xml
33- **/build/test-results/testWithJava21/TEST-*.xml
54+ **/build/test-results/*/TEST-*.xml
55+ - name : Upload Coverage XML Report
56+ uses : actions/upload-artifact@v4
57+ if : always() && matrix.label == 'java25'
58+ with :
59+ name : coverage-report
60+ path : build/reports/jacoco/test/jacocoTestReport.xml
61+ retention-days : 1
62+ - name : Parse Test Results
63+ if : always() && matrix.label != 'check' && matrix.label != 'jcstress'
64+ run : |
65+ total=0; failures=0; errors=0; skipped=0
66+ for dir_name in ${{ matrix.test-results-dirs }}; do
67+ dir="build/test-results/$dir_name"
68+ for f in "$dir"/TEST-*.xml; do
69+ [ -f "$f" ] || continue
70+ t=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
71+ fl=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
72+ e=$(grep -o 'errors="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
73+ s=$(grep -o 'skipped="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
74+ total=$((total + ${t:-0}))
75+ failures=$((failures + ${fl:-0}))
76+ errors=$((errors + ${e:-0}))
77+ skipped=$((skipped + ${s:-0}))
78+ done
79+ done
80+ passed=$((total - failures - errors - skipped))
81+ mkdir -p /tmp/test-stats
82+ echo "{\"total\":$total,\"passed\":$passed,\"failed\":$failures,\"errors\":$errors,\"skipped\":$skipped}" \
83+ > "/tmp/test-stats/${{ matrix.label }}.json"
84+ - name : Parse jcstress Results
85+ if : always() && matrix.label == 'jcstress'
86+ run : |
87+ total=0; passed=0; failed=0; errors=0; skipped=0
88+ if [ -f build/jcstress-output.txt ]; then
89+ line=$(grep 'Results:.*planned.*passed.*failed' build/jcstress-output.txt | tail -1)
90+ if [ -n "$line" ]; then
91+ total=$(echo "$line" | sed 's/.*Results: \([0-9]*\) planned.*/\1/')
92+ passed=$(echo "$line" | sed 's/.*; \([0-9]*\) passed.*/\1/')
93+ failed=$(echo "$line" | sed 's/.*passed, \([0-9]*\) failed.*/\1/')
94+ soft=$(echo "$line" | sed 's/.*failed, \([0-9]*\) soft.*/\1/')
95+ hard=$(echo "$line" | sed 's/.*soft errs, \([0-9]*\) hard.*/\1/')
96+ errors=$((soft + hard))
97+ fi
98+ fi
99+ mkdir -p /tmp/test-stats
100+ echo "{\"total\":$total,\"passed\":$passed,\"failed\":$failed,\"errors\":$errors,\"skipped\":$skipped}" \
101+ > "/tmp/test-stats/${{ matrix.label }}.json"
102+ - name : Upload Test Stats
103+ if : always() && matrix.label != 'check'
104+ uses : actions/upload-artifact@v4
105+ with :
106+ name : test-stats-${{ matrix.label }}
107+ path : /tmp/test-stats/${{ matrix.label }}.json
108+ update-baseline :
109+ needs : buildAndTest
110+ runs-on : ubuntu-latest
111+ steps :
112+ - uses : actions/checkout@v6
113+ with :
114+ token : ${{ secrets.ADMIN_PAT }}
115+ - name : Download Test Stats
116+ uses : actions/download-artifact@v4
117+ with :
118+ pattern : test-stats-*
119+ merge-multiple : true
120+ path : test-stats/
121+ - name : Download Coverage Report
122+ uses : actions/download-artifact@v4
123+ continue-on-error : true
124+ with :
125+ name : coverage-report
126+ path : coverage/
127+ - name : Update Baseline
128+ uses : actions/github-script@v7
129+ with :
130+ script : |
131+ const fs = require('fs');
132+ const path = require('path');
133+
134+ const versions = ['java11', 'java17', 'java21', 'java25', 'jcstress'];
135+ const zeroTest = { total: 0, passed: 0, failed: 0, errors: 0, skipped: 0 };
136+ const zeroCov = { covered: 0, missed: 0 };
137+
138+ // Read current baseline
139+ const baselineFile = 'test-baseline.json';
140+ let baseline = { tests: {}, coverage: {} };
141+ if (fs.existsSync(baselineFile)) {
142+ baseline = JSON.parse(fs.readFileSync(baselineFile, 'utf8'));
143+ }
144+
145+ // Update test stats from artifacts
146+ const tests = baseline.tests || {};
147+ for (const v of versions) {
148+ const file = path.join('test-stats', `${v}.json`);
149+ if (fs.existsSync(file)) {
150+ tests[v] = JSON.parse(fs.readFileSync(file, 'utf8'));
151+ } else {
152+ tests[v] = tests[v] || zeroTest;
153+ }
154+ }
155+
156+ // Update coverage from JaCoCo XML
157+ let coverage = { overall: {}, classes: {} };
158+ const jacocoFile = path.join('coverage', 'jacocoTestReport.xml');
159+ if (fs.existsSync(jacocoFile)) {
160+ const xml = fs.readFileSync(jacocoFile, 'utf8');
161+
162+ // Overall counters (outside <package> tags)
163+ const stripped = xml.replace(/<package[\s\S]*?<\/package>/g, '');
164+ const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
165+ let m;
166+ while ((m = re.exec(stripped)) !== null) {
167+ if (m[1] === 'LINE') coverage.overall.line = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
168+ else if (m[1] === 'BRANCH') coverage.overall.branch = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
169+ else if (m[1] === 'METHOD') coverage.overall.method = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
170+ }
171+
172+ // Per-class counters from <package>/<class> elements
173+ const pkgRe = /<package\s+name="([^"]+)">([\s\S]*?)<\/package>/g;
174+ let pkgMatch;
175+ while ((pkgMatch = pkgRe.exec(xml)) !== null) {
176+ const pkgName = pkgMatch[1].replace(/\//g, '.');
177+ const pkgBody = pkgMatch[2];
178+ const classRe = /<class\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/class>/g;
179+ let classMatch;
180+ while ((classMatch = classRe.exec(pkgBody)) !== null) {
181+ const className = classMatch[1].replace(/\//g, '.');
182+ const classBody = classMatch[2];
183+ const counters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
184+ const cntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
185+ let cntMatch;
186+ while ((cntMatch = cntRe.exec(classBody)) !== null) {
187+ const entry = { covered: parseInt(cntMatch[3]), missed: parseInt(cntMatch[2]) };
188+ if (cntMatch[1] === 'LINE') counters.line = entry;
189+ else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
190+ else if (cntMatch[1] === 'METHOD') counters.method = entry;
191+ }
192+ // Skip classes with 0 total lines (interfaces, annotations, abstract classes)
193+ if (counters.line.covered + counters.line.missed > 0) {
194+ coverage.classes[className] = counters;
195+ }
196+ }
197+ }
198+ }
199+
200+ const updated = { tests, coverage };
201+ fs.writeFileSync(baselineFile, JSON.stringify(updated, null, 2) + '\n');
202+ - name : Commit Updated Baseline
203+ run : |
204+ git config user.name "github-actions[bot]"
205+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
206+ git add test-baseline.json
207+ git diff --cached --quiet || {
208+ git commit -m "Update test baseline [skip ci]"
209+ git push
210+ }
34211 javadoc :
35212 runs-on : ubuntu-latest
36213 steps :
@@ -43,6 +220,34 @@ jobs:
43220 distribution : ' corretto'
44221 - name : Verify Javadoc
45222 run : ./gradlew javadoc --info --stacktrace
223+ allBuildAndTestSuccessful :
224+ if : always()
225+ needs :
226+ - buildAndTest
227+ - update-baseline
228+ - javadoc
229+ - publishToMavenCentral
230+ runs-on : ubuntu-latest
231+ steps :
232+ - name : Verify all jobs passed
233+ run : |
234+ if [ "${{ needs.buildAndTest.result }}" != "success" ]; then
235+ echo "buildAndTest failed with result: ${{ needs.buildAndTest.result }}"
236+ exit 1
237+ fi
238+ if [ "${{ needs.update-baseline.result }}" != "success" ]; then
239+ echo "update-baseline failed with result: ${{ needs.update-baseline.result }}"
240+ exit 1
241+ fi
242+ if [ "${{ needs.javadoc.result }}" != "success" ]; then
243+ echo "javadoc failed with result: ${{ needs.javadoc.result }}"
244+ exit 1
245+ fi
246+ if [ "${{ needs.publishToMavenCentral.result }}" != "success" ]; then
247+ echo "publishToMavenCentral failed with result: ${{ needs.publishToMavenCentral.result }}"
248+ exit 1
249+ fi
250+ echo "All build and test jobs passed successfully."
46251 publishToMavenCentral :
47252 needs : buildAndTest
48253 runs-on : ubuntu-latest
62267 java-version : ' 25'
63268 distribution : ' corretto'
64269 - name : publishToMavenCentral
65- run : ./gradlew assemble && ./gradlew check -x test -x testng --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace
270+ run : ./gradlew assemble && ./gradlew check -x test -x testng -x testngWithJava11 -x testngWithJava17 -x testngWithJava21 - -info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace
0 commit comments