Skip to content

Commit c897548

Browse files
authored
Merge branch 'master' into extract-out-operation-directives
2 parents 9f65d67 + ec31e0b commit c897548

10 files changed

Lines changed: 546 additions & 59 deletions

.github/workflows/master.yml

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ jobs:
2727
- gradle-argument: 'test -x testWithJava11 -x testWithJava17 -x testWithJava21 testng jacocoTestReport'
2828
label: 'java25'
2929
test-results-dirs: 'test testng'
30+
- gradle-argument: 'jcstress'
31+
label: 'jcstress'
3032
steps:
3133
- uses: actions/checkout@v6
3234
- uses: gradle/actions/wrapper-validation@v5
@@ -36,10 +38,17 @@ jobs:
3638
java-version: '25'
3739
distribution: 'corretto'
3840
- name: build and test
39-
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
4049
- name: Publish Test Results
4150
uses: EnricoMi/[email protected]
42-
if: always() && matrix.label != 'check'
51+
if: always() && matrix.label != 'check' && matrix.label != 'jcstress'
4352
with:
4453
files: |
4554
**/build/test-results/*/TEST-*.xml
@@ -51,7 +60,7 @@ jobs:
5160
path: build/reports/jacoco/test/jacocoTestReport.xml
5261
retention-days: 1
5362
- name: Parse Test Results
54-
if: always() && matrix.label != 'check'
63+
if: always() && matrix.label != 'check' && matrix.label != 'jcstress'
5564
run: |
5665
total=0; failures=0; errors=0; skipped=0
5766
for dir_name in ${{ matrix.test-results-dirs }}; do
@@ -72,6 +81,24 @@ jobs:
7281
mkdir -p /tmp/test-stats
7382
echo "{\"total\":$total,\"passed\":$passed,\"failed\":$failures,\"errors\":$errors,\"skipped\":$skipped}" \
7483
> "/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"
75102
- name: Upload Test Stats
76103
if: always() && matrix.label != 'check'
77104
uses: actions/upload-artifact@v4
@@ -102,7 +129,7 @@ jobs:
102129
const fs = require('fs');
103130
const path = require('path');
104131
105-
const versions = ['java11', 'java17', 'java21', 'java25'];
132+
const versions = ['java11', 'java17', 'java21', 'java25', 'jcstress'];
106133
const zeroTest = { total: 0, passed: 0, failed: 0, errors: 0, skipped: 0 };
107134
const zeroCov = { covered: 0, missed: 0 };
108135
@@ -160,7 +187,10 @@ jobs:
160187
else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
161188
else if (cntMatch[1] === 'METHOD') counters.method = entry;
162189
}
163-
coverage.classes[className] = counters;
190+
// Skip classes with 0 total lines (interfaces, annotations, abstract classes)
191+
if (counters.line.covered + counters.line.missed > 0) {
192+
coverage.classes[className] = counters;
193+
}
164194
}
165195
}
166196
}

.github/workflows/pull_request.yml

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ jobs:
3636
- gradle-argument: 'test -x testWithJava11 -x testWithJava17 -x testWithJava21 testng jacocoTestReport'
3737
label: 'java25'
3838
test-results-dirs: 'test testng'
39+
- gradle-argument: 'jcstress'
40+
label: 'jcstress'
3941
steps:
4042
- uses: actions/checkout@v6
4143
- uses: gradle/actions/wrapper-validation@v5
@@ -45,7 +47,14 @@ jobs:
4547
java-version: '25'
4648
distribution: 'corretto'
4749
- name: build and test
48-
run: ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
50+
run: |
51+
if [ "${{ matrix.label }}" = "jcstress" ]; then
52+
set -o pipefail
53+
mkdir -p build
54+
./gradlew ${{matrix.gradle-argument}} --info --stacktrace 2>&1 | tee build/jcstress-output.txt
55+
else
56+
./gradlew ${{matrix.gradle-argument}} --info --stacktrace
57+
fi
4958
- name: Upload Coverage HTML Report
5059
uses: actions/upload-artifact@v4
5160
if: always() && matrix.label == 'java25'
@@ -61,7 +70,7 @@ jobs:
6170
path: build/reports/jacoco/test/jacocoTestReport.xml
6271
retention-days: 1
6372
- name: Parse Test Results
64-
if: always() && matrix.label != 'check'
73+
if: always() && matrix.label != 'check' && matrix.label != 'jcstress'
6574
run: |
6675
total=0; failures=0; errors=0; skipped=0
6776
for dir_name in ${{ matrix.test-results-dirs }}; do
@@ -82,13 +91,32 @@ jobs:
8291
mkdir -p /tmp/test-stats
8392
echo "{\"total\":$total,\"passed\":$passed,\"failed\":$failures,\"errors\":$errors,\"skipped\":$skipped}" \
8493
> "/tmp/test-stats/${{ matrix.label }}.json"
94+
- name: Parse jcstress Results
95+
if: always() && matrix.label == 'jcstress'
96+
run: |
97+
total=0; passed=0; failed=0; errors=0; skipped=0
98+
if [ -f build/jcstress-output.txt ]; then
99+
line=$(grep 'Results:.*planned.*passed.*failed' build/jcstress-output.txt | tail -1)
100+
if [ -n "$line" ]; then
101+
total=$(echo "$line" | sed 's/.*Results: \([0-9]*\) planned.*/\1/')
102+
passed=$(echo "$line" | sed 's/.*; \([0-9]*\) passed.*/\1/')
103+
failed=$(echo "$line" | sed 's/.*passed, \([0-9]*\) failed.*/\1/')
104+
soft=$(echo "$line" | sed 's/.*failed, \([0-9]*\) soft.*/\1/')
105+
hard=$(echo "$line" | sed 's/.*soft errs, \([0-9]*\) hard.*/\1/')
106+
errors=$((soft + hard))
107+
fi
108+
fi
109+
mkdir -p /tmp/test-stats
110+
echo "{\"total\":$total,\"passed\":$passed,\"failed\":$failed,\"errors\":$errors,\"skipped\":$skipped}" \
111+
> "/tmp/test-stats/${{ matrix.label }}.json"
85112
- name: Upload Test Stats
86113
if: always() && matrix.label != 'check'
87114
uses: actions/upload-artifact@v4
88115
with:
89116
name: test-stats-${{ matrix.label }}
90117
path: /tmp/test-stats/${{ matrix.label }}.json
91118
test-summary:
119+
name: "Test Report & Per-Class Coverage Gate"
92120
needs: buildAndTest
93121
if: always() && github.event_name == 'pull_request'
94122
runs-on: ubuntu-latest
@@ -106,14 +134,14 @@ jobs:
106134
with:
107135
name: coverage-report
108136
path: coverage/
109-
- name: Generate Unified Test Report Comment
137+
- name: Post Test Report and Enforce Per-Class Coverage Gate
110138
uses: actions/github-script@v7
111139
with:
112140
script: |
113141
const fs = require('fs');
114142
const path = require('path');
115143
116-
const versions = ['java11', 'java17', 'java21', 'java25'];
144+
const versions = ['java11', 'java17', 'java21', 'java25', 'jcstress'];
117145
const zeroTest = { total: 0, passed: 0, failed: 0, errors: 0, skipped: 0 };
118146
const zeroCov = { covered: 0, missed: 0 };
119147
@@ -270,6 +298,9 @@ jobs:
270298
// --- Per-class coverage deltas ---
271299
const changedClasses = [];
272300
for (const [cls, curr] of Object.entries(classCounters)) {
301+
// Skip classes with 0 total lines (interfaces, annotations, abstract classes)
302+
const totalLines = curr.line.covered + curr.line.missed;
303+
if (totalLines === 0) continue;
273304
const base = baseClasses[cls] || { line: zeroCov, branch: zeroCov, method: zeroCov };
274305
const currLinePct = pct(curr.line.covered, curr.line.missed);
275306
const baseLinePct = pct(base.line.covered, base.line.missed);
@@ -290,6 +321,9 @@ jobs:
290321
}
291322
// Also detect classes removed (in baseline but not in current)
292323
for (const cls of Object.keys(baseClasses)) {
324+
// Skip baseline classes with 0 total lines (interfaces, annotations)
325+
const baseTotalLines = baseClasses[cls].line.covered + baseClasses[cls].line.missed;
326+
if (baseTotalLines === 0) continue;
293327
if (!classCounters[cls]) {
294328
changedClasses.push({
295329
name: cls,
@@ -364,25 +398,23 @@ jobs:
364398
});
365399
}
366400
367-
// --- Coverage gate: fail if any metric drops ---
368-
if (covLine || covBranch || covMethod) {
369-
const drops = [];
370-
for (const { name, curr, baseKey } of [
371-
{ name: 'Line', curr: covLine, baseKey: 'line' },
372-
{ name: 'Branch', curr: covBranch, baseKey: 'branch' },
373-
{ name: 'Method', curr: covMethod, baseKey: 'method' },
374-
]) {
375-
if (!curr) continue;
376-
const b = baseCov[baseKey] || zeroCov;
377-
const currPct = pct(curr.covered, curr.missed);
378-
const basePct = pct(b.covered, b.missed);
401+
// --- Coverage gate: fail if any class regresses on any metric ---
402+
const regressions = [];
403+
for (const [cls, curr] of Object.entries(classCounters)) {
404+
// Skip classes with 0 total lines (interfaces, annotations, abstract classes)
405+
const totalLines = curr.line.covered + curr.line.missed;
406+
if (totalLines === 0) continue;
407+
const base = baseClasses[cls] || { line: zeroCov, branch: zeroCov, method: zeroCov };
408+
for (const [metric, key] of [['Line', 'line'], ['Branch', 'branch'], ['Method', 'method']]) {
409+
const currPct = pct(curr[key].covered, curr[key].missed);
410+
const basePct = pct(base[key].covered, base[key].missed);
379411
if (currPct < basePct - 0.05) {
380-
drops.push(`${name}: ${currPct.toFixed(1)}% (was ${basePct.toFixed(1)}%, delta ${(currPct - basePct).toFixed(1)}%)`);
412+
regressions.push(` ${cls} ${metric}: ${currPct.toFixed(1)}% (was ${basePct.toFixed(1)}%, delta ${(currPct - basePct).toFixed(1)}%)`);
381413
}
382414
}
383-
if (drops.length > 0) {
384-
core.setFailed(`Coverage decreased:\n${drops.join('\n')}`);
385-
}
415+
}
416+
if (regressions.length > 0) {
417+
core.setFailed(`Per-class coverage regressions detected:\n${regressions.join('\n')}\n\nUpdate test-baseline.json if these changes are intentional.`);
386418
}
387419
javadoc:
388420
runs-on: ubuntu-latest
@@ -411,7 +443,7 @@ jobs:
411443
exit 1
412444
fi
413445
if [ "${{ needs.test-summary.result }}" != "success" ] && [ "${{ needs.test-summary.result }}" != "skipped" ]; then
414-
echo "test-summary failed with result: ${{ needs.test-summary.result }}"
446+
echo "Test Report & Per-Class Coverage Gate failed with result: ${{ needs.test-summary.result }}"
415447
exit 1
416448
fi
417449
if [ "${{ needs.javadoc.result }}" != "success" ]; then

0 commit comments

Comments
 (0)