This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 9c8d4666b09b5989a629724d99ccb7690240bc4d
Author: Jan Høydahl <[email protected]>
AuthorDate: Fri Aug 7 19:41:02 2026 +0200

    SOLR-18338 Enable jacoco for code coverage (#4712)
    
    Co-authored-by: David Smiley <[email protected]>
    (cherry picked from commit db49e40825a5350283a7f6d8d660cd3e942cd0fc)
---
 build.gradle                                       |  8 +++
 changelog/unreleased/jacoco-test-coverage.yml      | 12 ++++
 dev-docs/gradle-help/tests.txt                     | 24 +++++++
 gradle/libs.versions.toml                          |  2 +
 gradle/testing/coverage.gradle.kts                 | 83 ++++++++++++++++++++++
 gradle/testing/defaults-tests.gradle               |  3 +
 .../randomization/policies/solr-tests.policy       |  2 +
 7 files changed, 134 insertions(+)

diff --git a/build.gradle b/build.gradle
index 6afc40bca01..03243fb6be1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,6 +23,7 @@ plugins {
   id 'solr.build-infra'
 
   alias(libs.plugins.owasp.dependencycheck)
+  alias(libs.plugins.barfuin.jacocolog) apply false
   alias(libs.plugins.benmanes.versions)
   alias(libs.plugins.kotlin.multiplatform) apply false
   alias(libs.plugins.littlerobots.versioncatalogupdate) apply false
@@ -155,6 +156,13 @@ apply from: 
file('gradle/testing/alternative-jdk-support.gradle')
 apply from: file('gradle/java/jar-manifest.gradle')
 apply from: file('gradle/testing/retry-test.gradle')
 
+def withCoverage = gradle.startParameter.taskNames.any {
+  it == 'coverage' || it.endsWith(':coverage')
+} || providers.gradleProperty('tests.coverage').getOrElse('false').toBoolean()
+if (withCoverage) {
+  apply from: file('gradle/testing/coverage.gradle.kts')
+}
+
 // Publishing and releasing
 apply from: file('gradle/maven/defaults-maven.gradle')
 
diff --git a/changelog/unreleased/jacoco-test-coverage.yml 
b/changelog/unreleased/jacoco-test-coverage.yml
new file mode 100644
index 00000000000..f66852b1b70
--- /dev/null
+++ b/changelog/unreleased/jacoco-test-coverage.yml
@@ -0,0 +1,12 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+
+title: >
+  Add opt-in jacoco test coverage reporting to the Gradle build. Run the 
"coverage" task
+  (or pass -Ptests.coverage=true) to record per-module coverage reports, and 
run
+  "gradlew coverage" at the root for an aggregated report across all modules.
+type: other
+authors:
+  - name: Jan Høydahl
+links:
+  - name: SOLR-18338
+    url: https://issues.apache.org/jira/browse/SOLR-18338
diff --git a/dev-docs/gradle-help/tests.txt b/dev-docs/gradle-help/tests.txt
index c1f79cfebc9..c0c354bfed1 100644
--- a/dev-docs/gradle-help/tests.txt
+++ b/dev-docs/gradle-help/tests.txt
@@ -155,3 +155,27 @@ Using these additional options will make the results more 
sparse, so it may be u
 to increase the top-N count:
 
 gradlew -p solr/core test -Ptests.profile=true -Ptests.profile.count=100
+
+
+Test coverage
+-------------
+
+Coverage is opt-in. Running the "coverage" task (or passing 
-Ptests.coverage=true)
+will run the tests with jacoco instrumentation to record code coverage.
+
+Example (single module):
+
+gradlew -p solr/core coverage
+open solr/core/build/reports/jacoco/test/html/index.html
+
+If you want to use test filtering to just check a particular test, specify
+the "test" task explicitly before "coverage":
+
+gradlew -p solr/core test --tests TestDemo coverage
+open solr/core/build/reports/jacoco/test/html/index.html
+
+Running the "coverage" task on the root project runs all tests and produces
+an aggregated report across all modules:
+
+gradlew coverage
+open build/reports/jacoco/testCodeCoverageReport/html/index.html
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 10186a7e4bf..10f706e3201 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -52,6 +52,7 @@ asciidoctor-mathjax = "0.0.9"
 # @keep Asciidoctor tabs version used in ref-guide
 asciidoctor-tabs = "1.0.0-beta.6"
 azagniotov-langdetect = "12.5.2"
+barfuin-jacocolog = "4.0.2"
 # @keep bats-assert version used in packaging
 bats-assert = "2.0.0"
 # @keep bats-core version used in packaging
@@ -201,6 +202,7 @@ threeten-bp = "1.7.3"
 xerial-snappy = "1.1.10.8"
 
 [plugins]
+barfuin-jacocolog = { id = "org.barfuin.gradle.jacocolog", version.ref = 
"barfuin-jacocolog" }
 benmanes-versions = { id = "com.github.ben-manes.versions", version.ref = 
"benmanes-versions" }
 compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = 
"kotlin" }
 diffplug-spotless = { id = "com.diffplug.spotless", version.ref = 
"diffplug-spotless" }
diff --git a/gradle/testing/coverage.gradle.kts 
b/gradle/testing/coverage.gradle.kts
new file mode 100644
index 00000000000..cb3487f6237
--- /dev/null
+++ b/gradle/testing/coverage.gradle.kts
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This adds jacoco code coverage to test tasks, along with an aggregated
+// coverage report for all projects.
+//
+// Coverage is opt-in and this script is only applied (from build.gradle, to
+// the root project) when the 'coverage' task is explicitly requested or
+// -Ptests.coverage=true is passed; see settings.gradle.
+
+// Set up an aggregated coverage report over all projects with tests.
+// Logs a coverage summary to the console after report tasks.
+plugins.apply("org.barfuin.gradle.jacocolog")
+plugins.apply("jacoco-report-aggregation")
+
+configure<ReportingExtension> {
+  reports.create<JacocoCoverageReport>("testCodeCoverageReport") {
+    testSuiteName.set("test")
+  }
+}
+
+subprojects.filter { it.file("src/test").exists() }.forEach { subproject ->
+  dependencies.add("jacocoAggregation", subproject)
+}
+
+val aggregatedReport = tasks.named<JacocoReport>("testCodeCoverageReport") {
+  // XML report is consumed by CI tooling.
+  reports.xml.required.set(true)
+  doLast {
+    logger.lifecycle("Aggregated code coverage report at: 
${reports.html.entryPoint}\n")
+  }
+}
+
+tasks.register("coverage") {
+  dependsOn(aggregatedReport)
+}
+
+allprojects {
+  plugins.withType<JavaPlugin> {
+    // Applies the jacoco plugin and logs a coverage summary to the console.
+    plugins.apply("org.barfuin.gradle.jacocolog")
+
+    val jacocoTestReport = tasks.named<JacocoReport>("jacocoTestReport") {
+      // XML report is consumed by CI tooling.
+      reports.xml.required.set(true)
+      doLast {
+        logger.lifecycle("Code coverage report at: 
${reports.html.entryPoint}\n")
+      }
+    }
+
+    val testTasks = tasks.withType<Test>()
+
+    tasks.register("coverage") {
+      dependsOn(testTasks)
+      dependsOn(jacocoTestReport)
+    }
+
+    testTasks.configureEach {
+      // Configure the jacoco data file to be within the test task's
+      // working directory.
+      extensions.configure(JacocoTaskExtension::class) {
+        setDestinationFile(providers.provider { 
workingDir.resolve("jacoco.exec") })
+      }
+
+      // Test reports run after the test task, if it's run at all.
+      finalizedBy(jacocoTestReport)
+    }
+  }
+}
diff --git a/gradle/testing/defaults-tests.gradle 
b/gradle/testing/defaults-tests.gradle
index 06e22fdbc6b..fbe118548d5 100644
--- a/gradle/testing/defaults-tests.gradle
+++ b/gradle/testing/defaults-tests.gradle
@@ -55,6 +55,9 @@ allprojects {
           // Other settings.
           [propName: 'tests.neverUpToDate', value: true,
               description: "Make test tasks always fail the up-to-date checks 
(rerun) even if the inputs have not changed."],
+          // The coverage flag is acted upon in settings.gradle and 
gradle/testing/coverage.gradle.kts;
+          // it is registered here so that it is always listed by 'gradlew 
testOpts'.
+          [propName: 'tests.coverage', value: false, description: "Enables 
jacoco test coverage.", includeInReproLine: false],
       ]
 
       // Resolves test option's value.
diff --git a/gradle/testing/randomization/policies/solr-tests.policy 
b/gradle/testing/randomization/policies/solr-tests.policy
index fe21f45a04c..8709e2c0127 100644
--- a/gradle/testing/randomization/policies/solr-tests.policy
+++ b/gradle/testing/randomization/policies/solr-tests.policy
@@ -79,6 +79,8 @@ grant {
   permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
   // needed by jacoco to dump coverage
   permission java.lang.RuntimePermission "shutdownHooks";
+  // needed by jacoco to write the per-jvm code coverage output
+  permission java.io.FilePermission "${user.dir}${/}jacoco.exec", "write";
   // needed by org.apache.logging.log4j
   permission java.lang.RuntimePermission "getenv.*";
   permission java.lang.RuntimePermission "getClassLoader";

Reply via email to