[ 
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]

Reply via email to