[
https://issues.apache.org/jira/browse/HADOOP-15190?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18096807#comment-18096807
]
Ferenc Erdelyi commented on HADOOP-15190:
-----------------------------------------
h1. HADOOP-15190 — Replace Clover with JaCoCo for code coverage
h2. Summary
Hadoop's code-coverage tooling is built around OpenClover (the open-sourced
fork of Atlassian Clover). This does not work on JDK 17+, which is now the
build baseline. This change removes Clover entirely and replaces it with
*JaCoCo*, wired up so that a single full-reactor build produces one aggregate
HTML/XML coverage report across all modules.
h2. Problem
* OpenClover instruments code at the *source level* via a {{javac}} annotation
processor. Under the JDK 17 module system this triggers split-package
violations, so a Clover-enabled build fails on the supported JDK.
* The Clover integration was spread across many poms (a {{clover}} profile in
the root pom and sub-projects, {{clover-maven-plugin}} skip overrides, a
managed {{com.cenqua.clover}} dependency, and a {{CLOVER_JAR}} hook in the
libhdfs test script), all of which are now dead weight.
h2. Goals
* Remove all Clover configuration and dependencies.
* Provide coverage on JDK 17+ using JaCoCo's Java-agent (bytecode)
instrumentation.
* Produce a *single aggregate report* spanning all modules, not per-module
reports.
* Keep coverage *opt-in* so normal builds and CI are unaffected in cost and
behavior.
h2. Design
h3. 1. Opt-in switch
A property {{hadoop.skip-jacoco}} (default {{true}}) gates all JaCoCo activity.
It is declared in both the root {{pom.xml}} and {{hadoop-project/pom.xml}} so
that every module — including {{hadoop-coverage}}, whose parent is
{{hadoop-main}} rather than {{hadoop-project}} — resolves it. Coverage is
enabled by passing {{-Dhadoop.skip-jacoco=false}}.
h3. 2. Agent injection (instrumentation)
{{hadoop-project/pom.xml}} adds a {{jacoco:prepare-agent}} execution. JaCoCo's
{{prepare-agent}} sets the late-bound {{argLine}} property to the
{{-javaagent:...}} JVM argument; Surefire/Failsafe pick it up because their
{{argLine}} now ends with {{@\{argLine\}}}.
* The shared Surefire and Failsafe configs in {{hadoop-project/pom.xml}} were
updated to append {{@\{argLine\}}}.
* Every module that overrides {{argLine}} locally (e.g. {{hadoop-common}},
{{hadoop-hdfs}}, {{hadoop-hdfs-rbf}}, {{hadoop-registry}}, {{hadoop-aws}},
{{hadoop-azure}}, {{hadoop-distcp}}, {{hadoop-federation-balance}},
{{hadoop-gcp}}, {{hadoop-tos}}, {{hadoop-yarn-applications-catalog-webapp}})
was also updated; otherwise those modules would silently lose instrumentation.
* When coverage is off, {{prepare-agent}} is skipped and {{argLine}} is never
set; Surefire resolves an undefined {{@\{argLine\}}} to empty, so the default
build is unaffected.
* Generated / proto / example / hamlet classes are excluded from
instrumentation so the agent does not weave them into every forked JVM.
h3. 3. Aggregate report (hadoop-coverage, new module)
A new {{pom}}-packaging module {{hadoop-coverage}} depends on all test-bearing
modules and runs {{jacoco:report-aggregate}} at the {{verify}} phase. Because
it depends on everything, the reactor schedules it *last*, so it aggregates the
{{jacoco.exec}} files produced by every module's test run into one report at:
{noformat}
hadoop-coverage/target/site/jacoco-aggregate/index.html
{noformat}
Report-side exclusions (generated/proto/example/hamlet) are applied here as
well, so those classes do not distort the coverage numbers. The module sets
{{maven.deploy.skip}} / {{maven.install.skip}} so this coverage-only pom is
never published to a Maven repository.
{panel:title=Reactor constraint (important for reviewers)|borderStyle=solid}
{{report-aggregate}} only sees modules that are part of the *same* Maven
reactor. It must therefore be run as a full-reactor build. Running it with
{{-pl hadoop-coverage}} alone would leave every other module out of the reactor
and produce an empty report, so that form is intentionally *not* used.
{panel}
h2. How to generate coverage
Single full-reactor pass — runs instrumented tests across all modules and then
builds the aggregate report:
{code:bash}
mvn verify -Dhadoop.skip-jacoco=false -Dmaven.test.failure.ignore=true
{code}
* {{-Dhadoop.skip-jacoco=false}} turns instrumentation and reporting on.
* {{-Dmaven.test.failure.ignore=true}} lets the report be produced even if some
tests fail.
* Report: {{hadoop-coverage/target/site/jacoco-aggregate/index.html}}
h2. Scoping the report (e.g. a single project such as YARN)
The "must be a full-reactor build" rule is about the *reactor* — the set of
modules included in one {{mvn}} invocation. {{report-aggregate}} reports on the
modules that are both (a) declared as dependencies of {{hadoop-coverage}} and
(b) present in the current reactor. Modules outside the reactor are invisible
to it, which is why {{-pl hadoop-coverage}} on its own yields an empty report.
It does *not* mean you are limited to a whole-project report. To focus on one
area you have three options:
*Option 1 — browse the full report.* The full-reactor build produces one
navigable HTML report; drill into the {{org.apache.hadoop.yarn.*}} packages /
YARN modules. No rebuild needed.
*Option 2 — a scoped aggregate (smaller reactor).* Restrict the reactor to the
modules of interest plus {{hadoop-coverage}}. Since {{hadoop-coverage}} already
lists them as dependencies, the aggregate then covers only them:
{code:bash}
# one-time, so upstream jars (common, hdfs, ...) resolve without rebuilding
mvn install -DskipTests
# YARN-only aggregate: list the modules you want + hadoop-coverage
mvn verify -Dhadoop.skip-jacoco=false -Dmaven.test.failure.ignore=true \
-pl hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common,\
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager,\
hadoop-coverage
{code}
Only the listed modules are in the reactor, so only they appear in the
aggregate report. Avoid {{-am}} here — it would pull upstream modules back into
the reactor and they would show up in the report too.
*Option 3 — a single-module report (no aggregation).* Each module produces its
own {{target/jacoco.exec}} when tested with instrumentation; render it directly
with the {{report}} goal:
{code:bash}
mvn test -Dhadoop.skip-jacoco=false -pl
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common
mvn org.jacoco:jacoco-maven-plugin:report -pl
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common
# report at: <that module>/target/site/jacoco/index.html
{code}
h2. Running a subset of tests (partial coverage)
Coverage reflects exactly the tests you run — you do *not* need to run the full
suite, which is not practical for the whole project. JaCoCo records hits into
each module's {{target/jacoco.exec}} for whatever tests execute; the report is
built from that data (everything not exercised simply shows as uncovered).
Recommended workflow that avoids a full test build:
# Build all jars once, without tests, so a single module can be tested without
rebuilding (and re-testing) its dependencies:
{code:bash}
mvn install -DskipTests
{code}
# Run only the tests you want, with instrumentation on:
{code:bash}
# a whole module
mvn test -Dhadoop.skip-jacoco=false -Dmaven.test.failure.ignore=true \
-pl
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager
# or specific classes / methods
mvn test -Dhadoop.skip-jacoco=false \
-pl
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager
\
-Dtest=TestCapacityScheduler,TestLeafQueue#testSomething
{code}
# Produce the report — single-module HTML from that module's exec data:
{code:bash}
mvn org.jacoco:jacoco-maven-plugin:report \
-pl
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager
# -> <module>/target/site/jacoco/index.html
{code}
For an aggregate across just the modules you tested (the exec data from the
previous step is reused; {{-DskipTests}} means tests are not re-run):
{code:bash}
mvn verify -Dhadoop.skip-jacoco=false -DskipTests \
-pl <module-a>,<module-b>,hadoop-coverage
{code}
Notes:
* Do *not* run {{mvn clean}} between the test run and the report — it deletes
{{target/jacoco.exec}}. Instrumentation appends by default, so repeated runs
accumulate into the same exec file; use {{mvn clean}} to reset a measurement.
* Cross-module hits (e.g. a YARN test exercising {{hadoop-common}} code) are
recorded, but only appear in the report if that module is part of the report's
reactor.
h2. Files changed
||Area||Change||
|{{pom.xml}} (root)|Drop {{com.cenqua.clover}} dependency management; swap
{{clover-maven-plugin}} -> {{jacoco-maven-plugin}} 0.8.15 in pluginManagement;
delete the {{clover}} profile; add {{hadoop-coverage}} module; declare
{{hadoop.skip-jacoco=true}}|
|{{hadoop-project/pom.xml}}|Add {{hadoop.skip-jacoco}} property; append
{{@\{argLine\}}} to Surefire and Failsafe; add {{jacoco:prepare-agent}} with
instrumentation excludes|
|{{hadoop-coverage/pom.xml}} (new)|Aggregate module; {{report-aggregate}} at
{{verify}}; deploy/install skipped|
|Per-module poms|Append {{@\{argLine\}}} to modules that override {{argLine}}|
|{{hadoop-maven-plugins/pom.xml}},
{{hadoop-yarn-server-nodemanager/pom.xml}}|Remove stale {{clover-maven-plugin}}
skip overrides|
|{{test-libhdfs.sh}}, {{hadoop-mapreduce-client-jobclient/pom.xml}},
{{hadoop-yarn-applications/pom.xml}}|Remove remaining Clover references|
|{{BUILDING.txt}}|Update coverage build instructions|
h2. Backward compatibility & risk
* *Default builds are unchanged.* Coverage is off unless
{{-Dhadoop.skip-jacoco=false}} is passed; when off, no agent is attached and no
report module work runs.
* No production/runtime code is touched — this is a build/tooling change only.
* The Clover profile and the {{-Pclover}} invocation are removed; any external
tooling that invoked {{mvn ... -Pclover}} must switch to the JaCoCo command
above.
h2. Validation
* A full-closure build was run with {{mvn verify -pl hadoop-coverage -am
-DskipTests -Dhadoop.skip-jacoco=false}}: all upstream modules compiled,
{{hadoop-coverage}} resolved every dependency, the {{DependencyConvergence}}
enforcer passed for it, and {{jacoco:report-aggregate}} ran over the full
reactor and wrote the aggregate report — BUILD SUCCESS.
* The end-to-end coverage path was exercised on a single module: running one
test class ({{TestStringUtils}}) in {{hadoop-common}} with instrumentation
produced {{target/jacoco.exec}}, and {{jacoco:report}} rendered a report
showing that class covered while untouched classes showed zero — confirming
coverage reflects exactly the tests run.
* Every module listed in {{hadoop-coverage}} was verified to exist and to be
built unconditionally (no profile-gated modules were added as hard deps).
h2. Notes / follow-ups
* Only jar-packaged modules may be listed as {{hadoop-coverage}} dependencies.
A plain dependency on a war module (e.g.
{{hadoop-yarn-applications-catalog-webapp}}) resolves to a non-existent jar
artifact and breaks this module's build, so such modules are intentionally
excluded from the aggregate.
* Modules whose classes are excluded by design (e.g.
{{hadoop-mapreduce-examples}}, shaded-client test-only modules) are
intentionally not aggregated.
* The JaCoCo plugin version is pinned via {{jacoco-maven-plugin.version}}
(0.8.15) and can be bumped centrally in the root pom.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
> Use Jacoco to generate Unit Test coverage reports
> -------------------------------------------------
>
> Key: HADOOP-15190
> URL: https://issues.apache.org/jira/browse/HADOOP-15190
> Project: Hadoop Common
> Issue Type: Improvement
> Components: build
> Reporter: Duo Xu
> Assignee: Duo Xu
> Priority: Minor
> Labels: pull-request-available
> Attachments: HADOOP-15190-design_2026_July_16.txt,
> HADOOP-15190.01.patch, jacoco_report_2018_01_25.JPG
>
>
> Currently Hadoop is using maven-clover2-plugin for code coverage, which is
> outdated. And Atlassian open-sourced clover last year so license cannot be
> purchased although we can switch to use the license-free version called
> "openclover".
> This Jira is to replace clover with Jacoco, which is actively maintained by
> the community.
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]