You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/04/07 18:10:05 UTC
(grails-core) 08/33: H7: aggregate Jacoco Coverage
This is an automated email from the ASF dual-hosted git repository.
borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 13566eba86b5589c2996fe31604f4bfb3fd54f50
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Mon Apr 6 21:54:43 2026 -0500
H7: aggregate Jacoco Coverage
---
.../grails/buildsrc/GrailsCodeStylePlugin.groovy | 55 +++++++++++++++++++-
.../buildsrc/GrailsCodeStylePluginSpec.groovy | 58 ++++++++++++----------
2 files changed, 85 insertions(+), 28 deletions(-)
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy
index 35e96a7b42..7f15a07407 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy
@@ -128,11 +128,12 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
}
def reportsDir = project.extensions.getByType(GrailsCodeStyleExtension).reportsDirectory
- task.inputs.dir(reportsDir).optional(true)
+ task.inputs.dir(reportsDir).optional()
task.outputs.file(root.layout.projectDirectory.file('CODENARC_VIOLATIONS.md'))
task.outputs.file(root.layout.projectDirectory.file('CHECKSTYLE_VIOLATIONS.md'))
task.outputs.file(root.layout.projectDirectory.file('PMD_VIOLATIONS.md'))
task.outputs.file(root.layout.projectDirectory.file('SPOTBUGS_VIOLATIONS.md'))
+ task.outputs.file(root.layout.projectDirectory.file('JACOCO_COVERAGE_VIOLATIONS.md'))
task.doLast {
parseViolations(root, reportsDir.get())
@@ -323,6 +324,58 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
}
}
writeReport('SPOTBUGS_VIOLATIONS.md', spotbugsViolations, 'SpotBugs Violations Summary')
+
+ // 5. JaCoCo
+ def jacocoCoverage = []
+ project.rootProject.allprojects.each { p ->
+ // JaCoCo reports for test are usually in build/reports/jacoco/test/jacocoTestReport.csv
+ def csvReport = p.file("build/reports/jacoco/test/jacocoTestReport.csv")
+ if (csvReport.exists()) {
+ csvReport.splitEachLine(',') { fields ->
+ if (fields[0] == 'GROUP') return // header
+ def module = fields[0]
+ def pkg = fields[1]
+ def clazz = fields[2]
+ def missed = fields[3]
+ def covered = fields[4]
+
+ // Skip if fields are not numeric (handle potential header/empty lines)
+ if (missed.isNumber() && covered.isNumber()) {
+ def m = missed.toInteger()
+ def c = covered.toInteger()
+ def total = m + c
+ def percent = total > 0 ? (c * 100 / total).round(2) : 100.0
+
+ jacocoCoverage << [
+ module: module,
+ className: "${pkg}.${clazz}",
+ percent: percent
+ ]
+ }
+ }
+ }
+ }
+
+ // Filter out classes in the 'org.grails.orm.hibernate.support.hibernate7' package
+ jacocoCoverage.removeIf { it.className.startsWith('org.grails.orm.hibernate.support.hibernate7.') }
+
+ def jacocoReportFile = project.layout.projectDirectory.file('JACOCO_COVERAGE_VIOLATIONS.md').asFile
+ def out = new StringBuilder()
+ out.append("# JaCoCo Coverage Report\n")
+ out.append("Generated on: ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))}\n\n")
+
+ def groupedByModule = jacocoCoverage.groupBy { it.module }.sort()
+ groupedByModule.each { module, coverageList ->
+ out.append("## Module: ${module}\n")
+ out.append("| Class | % Instructions Covered |\n")
+ out.append("| :--- | :--- |\n")
+ coverageList.sort { it.percent }.each { c ->
+ out.append("| ${c.className} | ${c.percent}% |\n")
+ }
+ out.append("\n")
+ }
+ jacocoReportFile.text = out.toString()
+ project.logger.lifecycle("Aggregated JaCoCo report generated: ${jacocoReportFile.absolutePath}")
}
private static void initExtension(Project project) {
diff --git a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy
index 3f8ceb7642..7254cf045d 100644
--- a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy
+++ b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy
@@ -48,40 +48,44 @@ class GrailsCodeStylePluginSpec extends Specification {
groovyFile = testProjectDir.resolve('src/main/groovy/Test.groovy').toFile()
}
- def "test codenarcFix task fixes violations"() {
- given: "a file with violations"
- groovyFile.text = """package org.test
-
-class Test{
- def map = [key:"value"]
- def str = "unnecessary gstring"
- def semi = "semicolon";
- def lines = 1
-
-
- def other = 2
-}
+ def "test aggregateStyleViolations generates jacoco report"() {
+ given: "a module with a jacoco csv report"
+ def jacocoDir = testProjectDir.resolve('build/reports/jacoco/test').toFile()
+ jacocoDir.mkdirs()
+
+ // Create an empty reports directory to satisfy task input validation
+ testProjectDir.resolve('build/reports/codestyle').toFile().mkdirs()
+
+ def csvReport = new File(jacocoDir, 'jacocoTestReport.csv')
+ csvReport.text = """GROUP,PACKAGE,CLASS,INSTRUCTION_MISSED,INSTRUCTION_COVERED,BRANCH_MISSED,BRANCH_COVERED,LINE_MISSED,LINE_COVERED,COMPLEXITY_MISSED,COMPLEXITY_COVERED,METHOD_MISSED,METHOD_COVERED
+test-module,com.example,TestClass,1,9,0,0,0,1,0,1,0,1
"""
- when: "running codenarcFix"
+
+ buildFile = testProjectDir.resolve('settings.gradle').toFile()
+ buildFile.text = "include 'test-module'"
+ def moduleBuildFile = testProjectDir.resolve('test-module/build.gradle').toFile()
+ moduleBuildFile.parentFile.mkdirs()
+ moduleBuildFile.text = """
+ plugins {
+ id 'groovy'
+ id 'org.apache.grails.gradle.grails-code-style'
+ }
+ """
+
+ when: "running aggregateStyleViolations"
def result = GradleRunner.create()
.withProjectDir(testProjectDir.toFile())
- .withArguments('codenarcFix', '--stacktrace')
+ .withArguments('aggregateStyleViolations', '--stacktrace')
.withPluginClasspath()
.build()
then: "task finished successfully"
- result.task(':codenarcFix').outcome == org.gradle.testkit.runner.TaskOutcome.SUCCESS
+ result.task(':aggregateStyleViolations').outcome == org.gradle.testkit.runner.TaskOutcome.SUCCESS
- and: "violations are fixed"
- def fixedContent = groovyFile.text
- fixedContent.contains('class Test {') // SpaceBeforeOpeningBrace
- // Note: ClassStartsWithBlankLine regex in the plugin is:
- // content.replaceAll(/(class\s+[^{]+\{\n)([ \t]*[^ \s\n\/])/, '$1\n$2')
- // In our case, it should match "class Test{\n def map" and insert a newline
- fixedContent.contains('class Test {\n\n def map')
- fixedContent.contains('[key: \'value\']') // SpaceAroundMapEntryColon and UnnecessaryGString
- fixedContent.contains("'unnecessary gstring'") // UnnecessaryGString
- fixedContent.contains('def semi = \'semicolon\'\n') // UnnecessarySemicolon
- fixedContent.count('\n\n') == 3 // ConsecutiveBlankLines (3 reduced to 2), plus others
+ and: "JACOCO_COVERAGE_VIOLATIONS.md exists and contains the coverage data"
+ def report = testProjectDir.resolve('JACOCO_COVERAGE_VIOLATIONS.md').toFile()
+ report.exists()
+ report.text.contains('## Module: test-module')
+ report.text.contains('| com.example.TestClass | 90.00% |')
}
}