skip to content

Your testCodeCoverageReport runs successfully but the merged report shows 0% or is missing whole modules. How do you debug it?

level: middleimportance: must knowfreq 40%

answer

  1. module must apply jacoco -> exposes variant
  2. tests must run -> .exec non-empty
  3. reachable via jacocoAggregation/transitive
  4. testSuiteName must match producing suite
  5. check excludes; --info; log resolved inputs

basics

~10 s

Check that each module applies jacoco/jvm-test-suite (so it exposes coverage variants), that its tests actually ran and produced .exec data, that it's reachable via jacocoAggregation, and that the right testSuiteName is aggregated.

solid answer

~40 s

Work the resolution chain. (1) **Variant producers**: every module you expect must apply `jacoco` (and ideally `jvm-test-suite`); without it the module exposes no coverage variant and is silently skipped — explaining missing modules. (2) **Data exists**: tests must run and write `.exec`; if a module has no tests or they were `UP-TO-DATE` with no execution data, it contributes 0%. (3) **Reachability**: the module must be listed in `jacocoAggregation` or be a transitive project dependency of one. (4) **Suite match**: the report's `testSuiteName` must match the suite that produced the data (aggregating `test` when coverage came from an `integrationTest` suite yields empty). (5) **Excludes**: an over-broad `classDirectories` exclude filter can zero things out. Use `--info` and inspect the resolved `executionData`/`classDirectories` to see what was actually collected.

code

bash · 5 lines
bash
# See which test tasks actually ran for the aggregation
./gradlew :coverage:testCodeCoverageReport --info | grep -i test

# Confirm a module produced exec data
ls -la lib/build/jacoco/   # expect test.exec, non-empty

go deeper

for a junior

Check that the module applied jacoco and that its tests ran.

for a middle

Walk the producer/exec/reachability/suite/excludes pipeline and use --info plus inspecting resolved inputs.

for a senior

Reason about silent skipping of non-producer modules and suite/variant mismatches as the subtle failure modes.

for a principal

Add a convention plugin + CI check that asserts expected modules contribute, so silent gaps fail fast.

## Debug as a pipeline The merged report is `exec data x classes x sources`. A failure at any stage yields gaps. Walk them in order. ### 1. Is the module a coverage *producer*? The aggregation plugin only collects from projects that **expose** the coverage variants — which means they applied `jacoco` (directly, or via `jvm-test-suite` defaults). A module compiled but never given `jacoco` exposes nothing and is **silently skipped** (no error). Symptom: whole module absent. Fix: apply jacoco there. ### 2. Did tests actually run and emit .exec? Coverage data only exists if the test task executed and was instrumented. Causes of empty exec: - The module has no tests. - The test task was `NO-SOURCE`/skipped. - Instrumentation disabled. Symptom: module present but 0%. Check `build/jacoco/<suite>.exec` exists and is non-empty. ### 3. Is the module reachable from the aggregation set? It must be in `jacocoAggregation(project(...))` or pulled transitively. If you removed a dependency edge, a module can drop out. Symptom: previously-present module vanishes. ### 4. Does the suite name line up? If your coverage comes from a custom suite (e.g. `integrationTest`) but the report aggregates the default `test` suite, the report selects the wrong variant and looks empty. Match `testSuiteName` to the producing suite (or declare one report per suite). ### 5. Are excludes nuking the classes? A `classDirectories.setFrom(... exclude("**"))`-style mistake or an aggressive package exclude zeros coverage even when exec data is fine. ## Tools - `./gradlew :coverage:testCodeCoverageReport --info` — see which test tasks ran. - Inspect the resolved inputs: ```kotlin tasks.named<JacocoReport>("testCodeCoverageReport") { doFirst { logger.lifecycle("exec: ${executionData.files}") logger.lifecycle("classes: ${classDirectories.files}") } } ``` - Open the HTML report and confirm which packages appear; absent packages point back to step 1 or 3. ## Most common single cause In practice: a module that **didn't apply jacoco** (so it produced no `.exec` variant) — the report omits it without any warning.

  • A module compiles and has tests but is entirely absent from the report. Most likely cause?
    It didn't apply the `jacoco` (or jvm-test-suite) plugin, so it exposes no coverage variant and is silently skipped by aggregation.
  • The module appears but at 0% even though tests pass. What now?
    Check the `.exec` file is non-empty and that the report's `testSuiteName` matches the suite those tests belong to; mismatched suites select the wrong (empty) variant.

saying these in an interview costs you the question

  • Assuming aggregation errors out when a module lacks coverage — it skips silently, so absence is easy to miss.
  • Blaming the report renderer when the real issue is upstream (no jacoco plugin / wrong suite / empty exec).

context