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