@@ -31,18 +31,103 @@ jobs:
3131 distribution : ' corretto'
3232 - name : build and test
3333 run : ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
34- - name : Publish Coverage Report
35- uses : Madrapps/jacoco-report@50d3aff4548aa991e6753342d9ba291084e63848 # v1.7.2
34+ - name : Upload Coverage HTML Report
35+ uses : actions/upload-artifact@v4
36+ if : >
37+ always() &&
38+ contains(matrix.gradle-argument, 'jacocoTestReport')
39+ with :
40+ name : jacoco-html-report
41+ path : build/reports/jacoco/test/html/
42+ retention-days : 14
43+ - name : Publish Per-Package Coverage Comment
3644 if : >
3745 always() &&
3846 github.event_name == 'pull_request' &&
3947 contains(matrix.gradle-argument, 'jacocoTestReport')
48+ uses : actions/github-script@v7
4049 with :
41- paths : ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
42- token : ${{ secrets.GITHUB_TOKEN }}
43- min-coverage-overall : 0
44- min-coverage-changed-files : 0
45- update-comment : true
50+ script : |
51+ const fs = require('fs');
52+ const path = require('path');
53+ const xmlFile = path.join(process.env.GITHUB_WORKSPACE, 'build/reports/jacoco/test/jacocoTestReport.xml');
54+ if (!fs.existsSync(xmlFile)) {
55+ core.warning('JaCoCo XML report not found');
56+ return;
57+ }
58+ const xml = fs.readFileSync(xmlFile, 'utf8');
59+
60+ function extractCounters(element) {
61+ const counters = {};
62+ const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
63+ let m;
64+ while ((m = re.exec(element)) !== null) {
65+ counters[m[1]] = { missed: parseInt(m[2]), covered: parseInt(m[3]) };
66+ }
67+ return counters;
68+ }
69+
70+ function pct(c) {
71+ if (!c) return 'N/A';
72+ const total = c.missed + c.covered;
73+ return total === 0 ? 'N/A' : (c.covered / total * 100).toFixed(1) + '%';
74+ }
75+
76+ // Parse overall counters (last set of <counter> tags at report level)
77+ const reportMatch = xml.match(/<report[^>]*>([\s\S]*)<\/report>/);
78+ const reportBody = reportMatch ? reportMatch[1] : xml;
79+
80+ // Extract packages
81+ const pkgRegex = /<package name="([^"]*)">([\s\S]*?)<\/package>/g;
82+ const packages = [];
83+ let pm;
84+ while ((pm = pkgRegex.exec(reportBody)) !== null) {
85+ const name = pm[1].replace(/\//g, '.');
86+ const counters = extractCounters(pm[2]);
87+ packages.push({ name, counters });
88+ }
89+
90+ // Sort by package name
91+ packages.sort((a, b) => a.name.localeCompare(b.name));
92+
93+ // Overall counters (direct children of <report>, after all packages)
94+ const overallCounters = extractCounters(reportBody.replace(/<package[\s\S]*?<\/package>/g, ''));
95+
96+ let body = '## JaCoCo Coverage Report\n\n';
97+ body += '| Package | Line | Branch | Method |\n';
98+ body += '|:--------|-----:|-------:|-------:|\n';
99+
100+ for (const pkg of packages) {
101+ const c = pkg.counters;
102+ body += `| \`${pkg.name}\` | ${pct(c.LINE)} | ${pct(c.BRANCH)} | ${pct(c.METHOD)} |\n`;
103+ }
104+
105+ body += `| **Overall** | **${pct(overallCounters.LINE)}** | **${pct(overallCounters.BRANCH)}** | **${pct(overallCounters.METHOD)}** |\n`;
106+ body += '\n> Full HTML report available as build artifact `jacoco-html-report`\n';
107+
108+ // Find and update or create the comment
109+ const { data: comments } = await github.rest.issues.listComments({
110+ owner: context.repo.owner,
111+ repo: context.repo.repo,
112+ issue_number: context.issue.number,
113+ });
114+ const marker = '## JaCoCo Coverage Report';
115+ const existing = comments.find(c => c.body && c.body.startsWith(marker));
116+ if (existing) {
117+ await github.rest.issues.updateComment({
118+ owner: context.repo.owner,
119+ repo: context.repo.repo,
120+ comment_id: existing.id,
121+ body,
122+ });
123+ } else {
124+ await github.rest.issues.createComment({
125+ owner: context.repo.owner,
126+ repo: context.repo.repo,
127+ issue_number: context.issue.number,
128+ body,
129+ });
130+ }
46131 - name : Publish Test Results
47132 uses : EnricoMi/publish-unit-test-result-action@v2.23.0
48133 if : always()
0 commit comments