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'
1530 steps :
1631 - uses : actions/checkout@v6
1732 - uses : gradle/actions/wrapper-validation@v5
@@ -24,13 +39,143 @@ jobs:
2439 run : ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
2540 - name : Publish Test Results
2641 uses : EnricoMi/publish-unit-test-result-action@v2.23.0
27- if : always()
42+ if : always() && matrix.label != 'check'
2843 with :
2944 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
45+ **/build/test-results/*/TEST-*.xml
46+ - name : Upload Coverage XML Report
47+ uses : actions/upload-artifact@v4
48+ if : always() && matrix.label == 'java25'
49+ with :
50+ name : coverage-report
51+ path : build/reports/jacoco/test/jacocoTestReport.xml
52+ retention-days : 1
53+ - name : Parse Test Results
54+ if : always() && matrix.label != 'check'
55+ run : |
56+ total=0; failures=0; errors=0; skipped=0
57+ for dir_name in ${{ matrix.test-results-dirs }}; do
58+ dir="build/test-results/$dir_name"
59+ for f in "$dir"/TEST-*.xml; do
60+ [ -f "$f" ] || continue
61+ t=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
62+ fl=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
63+ e=$(grep -o 'errors="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
64+ s=$(grep -o 'skipped="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
65+ total=$((total + ${t:-0}))
66+ failures=$((failures + ${fl:-0}))
67+ errors=$((errors + ${e:-0}))
68+ skipped=$((skipped + ${s:-0}))
69+ done
70+ done
71+ passed=$((total - failures - errors - skipped))
72+ mkdir -p /tmp/test-stats
73+ echo "{\"total\":$total,\"passed\":$passed,\"failed\":$failures,\"errors\":$errors,\"skipped\":$skipped}" \
74+ > "/tmp/test-stats/${{ matrix.label }}.json"
75+ - name : Upload Test Stats
76+ if : always() && matrix.label != 'check'
77+ uses : actions/upload-artifact@v4
78+ with :
79+ name : test-stats-${{ matrix.label }}
80+ path : /tmp/test-stats/${{ matrix.label }}.json
81+ update-baseline :
82+ needs : buildAndTest
83+ runs-on : ubuntu-latest
84+ steps :
85+ - uses : actions/checkout@v6
86+ - name : Download Test Stats
87+ uses : actions/download-artifact@v4
88+ with :
89+ pattern : test-stats-*
90+ merge-multiple : true
91+ path : test-stats/
92+ - name : Download Coverage Report
93+ uses : actions/download-artifact@v4
94+ continue-on-error : true
95+ with :
96+ name : coverage-report
97+ path : coverage/
98+ - name : Update Baseline
99+ uses : actions/github-script@v7
100+ with :
101+ script : |
102+ const fs = require('fs');
103+ const path = require('path');
104+
105+ const versions = ['java11', 'java17', 'java21', 'java25'];
106+ const zeroTest = { total: 0, passed: 0, failed: 0, errors: 0, skipped: 0 };
107+ const zeroCov = { covered: 0, missed: 0 };
108+
109+ // Read current baseline
110+ const baselineFile = 'test-baseline.json';
111+ let baseline = { tests: {}, coverage: {} };
112+ if (fs.existsSync(baselineFile)) {
113+ baseline = JSON.parse(fs.readFileSync(baselineFile, 'utf8'));
114+ }
115+
116+ // Update test stats from artifacts
117+ const tests = baseline.tests || {};
118+ for (const v of versions) {
119+ const file = path.join('test-stats', `${v}.json`);
120+ if (fs.existsSync(file)) {
121+ tests[v] = JSON.parse(fs.readFileSync(file, 'utf8'));
122+ } else {
123+ tests[v] = tests[v] || zeroTest;
124+ }
125+ }
126+
127+ // Update coverage from JaCoCo XML
128+ let coverage = { overall: {}, classes: {} };
129+ const jacocoFile = path.join('coverage', 'jacocoTestReport.xml');
130+ if (fs.existsSync(jacocoFile)) {
131+ const xml = fs.readFileSync(jacocoFile, 'utf8');
132+
133+ // Overall counters (outside <package> tags)
134+ const stripped = xml.replace(/<package[\s\S]*?<\/package>/g, '');
135+ const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
136+ let m;
137+ while ((m = re.exec(stripped)) !== null) {
138+ if (m[1] === 'LINE') coverage.overall.line = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
139+ else if (m[1] === 'BRANCH') coverage.overall.branch = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
140+ else if (m[1] === 'METHOD') coverage.overall.method = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
141+ }
142+
143+ // Per-class counters from <package>/<class> elements
144+ const pkgRe = /<package\s+name="([^"]+)">([\s\S]*?)<\/package>/g;
145+ let pkgMatch;
146+ while ((pkgMatch = pkgRe.exec(xml)) !== null) {
147+ const pkgName = pkgMatch[1].replace(/\//g, '.');
148+ const pkgBody = pkgMatch[2];
149+ const classRe = /<class\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/class>/g;
150+ let classMatch;
151+ while ((classMatch = classRe.exec(pkgBody)) !== null) {
152+ const className = classMatch[1].replace(/\//g, '.');
153+ const classBody = classMatch[2];
154+ const counters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
155+ const cntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
156+ let cntMatch;
157+ while ((cntMatch = cntRe.exec(classBody)) !== null) {
158+ const entry = { covered: parseInt(cntMatch[3]), missed: parseInt(cntMatch[2]) };
159+ if (cntMatch[1] === 'LINE') counters.line = entry;
160+ else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
161+ else if (cntMatch[1] === 'METHOD') counters.method = entry;
162+ }
163+ coverage.classes[className] = counters;
164+ }
165+ }
166+ }
167+
168+ const updated = { tests, coverage };
169+ fs.writeFileSync(baselineFile, JSON.stringify(updated, null, 2) + '\n');
170+ - name : Commit Updated Baseline
171+ run : |
172+ git config user.name "github-actions[bot]"
173+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
174+ git add test-baseline.json
175+ git diff --cached --quiet || {
176+ git commit -m "Update test baseline [skip ci]"
177+ git push
178+ }
34179 javadoc :
35180 runs-on : ubuntu-latest
36181 steps :
62207 java-version : ' 25'
63208 distribution : ' corretto'
64209 - name : publishToMavenCentral
65- run : ./gradlew assemble && ./gradlew check -x test -x testng --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace
210+ 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