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";
