Testland
Browse all skills & agents

jacoco-analysis

Configures JaCoCo for JVM projects (Java / Kotlin / Scala / Groovy) - wires the runtime agent via `jacoco-maven-plugin` `prepare-agent`, generates per-build reports (HTML / XML / CSV) via the `report` goal, gates the build via the `check` goal with element / limit / minimum rules, parses the six native counters (instructions, branches, lines, methods, classes, cyclomatic complexity), and converts JaCoCo XML to LCOV / Cobertura when downstream tools need a different format. Use when the JVM build is Maven / Gradle and the team wants the canonical JVM coverage tool - or to convert JaCoCo output for cross-language coverage aggregation.

Install with skills.sh (any agent)

npx skills add testland/qa --skill jacoco-analysis
View source

jacoco-analysis

Overview

JaCoCo (the canonical JVM coverage tool) instruments Java bytecode at runtime via a Java agent, then aggregates execution data into per-class / per-method / per-line reports.

Per jacoco-counters (opens in new window), JaCoCo measures six counters:

CounterDefinition
Instructions (C0)"The smallest unit JaCoCo counts are single Java byte code instructions."
Branches (C1)Coverage "for all if and switch statements."
Lines"A source line is considered executed when at least one instruction that is assigned to this line has been executed."
Methods"A method is considered as executed when at least one instruction has been executed."
Classes"A class is considered as executed when at least one of its methods has been executed."
Cyclomatic Complexity"v(G) = B - D + 1 (branches minus decision points plus one)" - minimum number of paths needed to cover all paths through a method.

This skill covers Maven (the primary deployment), with the bytecode-only key insight: "All counters work at bytecode level, making them available regardless of debug information presence" (jacoco-counters (opens in new window)). Gradle, the XML parser, and the cross-language conversions live in the reference file.

When to use

  • A JVM project (Java / Kotlin / Scala / Groovy) needs coverage and the team is on Maven or Gradle.
  • A Cobertura-based pipeline is migrating to JaCoCo (typical: Cobertura is unmaintained for JDK 11+; JaCoCo handles modern bytecode).
  • Cross-language aggregation needs JaCoCo XML converted to LCOV / Cobertura.

How to use

  1. Confirm the JVM build is Maven / Gradle (see When to use).
  2. Wire the jacoco-maven-plugin with the three primary goals - prepare-agent, report, check (below); Gradle's equivalent is in references/gradle-parsing-and-ci.md.
  3. Run mvn verify; read the check verdict and the HTML at target/site/jacoco/index.html.
  4. Gate on INSTRUCTION + BRANCH ratios, then parse jacoco.xml for PR comments and convert to LCOV / Cobertura for cross-language aggregation - the parser, the counter-aware gating table, the format conversions, and the full Maven verify CI job are in references/gradle-parsing-and-ci.md.

Wire the Maven plugin

Per jacoco-maven (opens in new window), the plugin defines several goals; the three primary ones are prepare-agent, report, and check (jacoco-maven (opens in new window)):

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.13</version>
  <executions>
    <execution>
      <id>prepare-agent</id>
      <goals>
        <goal>prepare-agent</goal>
      </goals>
    </execution>
    <execution>
      <id>report</id>
      <phase>verify</phase>
      <goals>
        <goal>report</goal>
      </goals>
    </execution>
    <execution>
      <id>check</id>
      <phase>verify</phase>
      <goals>
        <goal>check</goal>
      </goals>
      <configuration>
        <rules>
          <rule>
            <element>BUNDLE</element>
            <limits>
              <limit>
                <counter>INSTRUCTION</counter>
                <value>COVEREDRATIO</value>
                <minimum>0.80</minimum>
              </limit>
              <limit>
                <counter>BRANCH</counter>
                <value>COVEREDRATIO</value>
                <minimum>0.70</minimum>
              </limit>
            </limits>
          </rule>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

prepare-agent configures the JaCoCo runtime agent for the forked test JVM; report generates HTML / XML / CSV from target/jacoco.exec; check enforces thresholds.

Per jacoco-maven (opens in new window): "Maven 3.0 or higher and Java 1.8 or higher for the Maven runtime, Java 1.5 or higher for the test executor."

Report formats

The report goal emits three artifacts under target/site/jacoco/:

FileFormatUse
index.html + assetsHTML drill-downHuman review.
jacoco.xmlXML (with DTD)CI parsing; Cobertura-shape conversion.
jacoco.csvCSV per-classSpreadsheets; quick scripting.

For Maven multi-module projects, the per-module reports go under each module's target/site/jacoco/. For an aggregated report, use the report-aggregate goal in a parent module.

Gating with the check goal

The check goal accepts a list of rules; each rule scopes by element (BUNDLE / PACKAGE / CLASS / SOURCEFILE / METHOD) and constrains via limits with counter / value / minimum or maximum:

<rules>
  <rule>
    <element>BUNDLE</element>
    <limits>
      <limit>
        <counter>INSTRUCTION</counter>
        <value>COVEREDRATIO</value>
        <minimum>0.80</minimum>
      </limit>
    </limits>
  </rule>
  <rule>
    <element>CLASS</element>
    <excludes>
      <exclude>*Test</exclude>
      <exclude>*IT</exclude>
    </excludes>
    <limits>
      <limit>
        <counter>METHOD</counter>
        <value>MISSEDCOUNT</value>
        <maximum>0</maximum>
      </limit>
    </limits>
  </rule>
</rules>

The two-rule pattern: a BUNDLE (whole-project) floor + a per-CLASS strict rule for production code (excluding tests).

value options (per the JaCoCo Maven plugin's check-mojo docs):

valueMeaning
COVEREDRATIOHit / total (0.0 to 1.0).
MISSEDRATIOMissed / total (0.0 to 1.0).
COVEREDCOUNTAbsolute number hit.
MISSEDCOUNTAbsolute number missed.
TOTALCOUNTAbsolute total.

minimum floors the value; maximum caps it. Counter options are INSTRUCTION / BRANCH / LINE / METHOD / CLASS / COMPLEXITY (per jacoco-counters (opens in new window)). Gate on INSTRUCTION for the whole-repo floor and BRANCH separately, with a bounded METHOD miss:

<limit><counter>INSTRUCTION</counter><value>COVEREDRATIO</value><minimum>0.80</minimum></limit>
<limit><counter>BRANCH</counter><value>COVEREDRATIO</value><minimum>0.70</minimum></limit>
<limit><counter>METHOD</counter><value>MISSEDCOUNT</value><maximum>5</maximum></limit>

Whole-bundle 80% instruction; 70% branch; allow up to 5 untested methods. The per-counter "when to gate on which" table is in references/gradle-parsing-and-ci.md.

Worked example

A Maven service wiring JaCoCo for the first time:

  1. Add the plugin block above to pom.xml (all three executions: prepare-agent, report, check).
  2. Run the build:
./mvnw -B verify

prepare-agent instruments the forked test JVM; the tests write target/jacoco.exec; report renders target/site/jacoco/index.html; check evaluates the rules.

If instruction coverage lands at 82% but branch coverage is 66%, the build fails on the BRANCH rule (minimum 0.70), naming the bundle and the missed limit. Open target/site/jacoco/index.html, drill into the red (missed) branches, add the tests that exercise them, and re-run ./mvnw -B verify until check passes.

Operating in CI

Run ./mvnw -B verify so check gates the build, then upload target/site/jacoco/ with if: always() (coverage matters most on runs that failed the gate). For a coverage SaaS or cross-language aggregation, convert jacoco.xml to LCOV in the same job and upload that. The full GitHub Actions workflow (verify, artifact upload, xml2lcov conversion, SaaS upload) is in references/gradle-parsing-and-ci.md.

Anti-patterns

Anti-patternWhy it failsFix
Skipping prepare-agentTests run without instrumentation; coverage data empty.Always include prepare-agent (see Wire the Maven plugin).
Whole-project rule with INSTRUCTION onlyBranch regressions invisible - line% looks fine while branch% drops.Separate INSTRUCTION + BRANCH rules (see Gating with the check goal).
<element>BUNDLE</element> on every rulePer-class violations bury inside an aggregate that passes.Add a per-CLASS rule with excludes for tests (see Gating with the check goal).
Using JaCoCo report HTML as PR-comment inputHTML is for humans; PR comments need machine-readable.Parse jacoco.xml (see references); generate the comment from there.
Running JaCoCo on test code"Coverage" of tests is meaningless and inflates aggregate.excludes for *Test, *IT, *Spec (see Gating with the check goal).
Aggregating LINE coverage when methods are shortInflated; the coarse-grained measure looks fine.Pair LINE with BRANCH always (see the counter-aware table in references).
Failing the build on COMPLEXITY without a tested floorForces refactors of legitimately complex code (algorithms).Use COMPLEXITY as informational; gate on INSTRUCTION + BRANCH.

Limitations

  • Bytecode-level only. Per jacoco-counters (opens in new window), all counters work at bytecode level - useful for source-format independence but means kotlinc / scalac inlining can produce surprising line-coverage shapes.
  • Per-method desc= is JVM signature. Cross-source-language reports (Kotlin generates JVM methods) need the source file as the pivot, not the method descriptor.
  • prepare-agent only instruments forked test JVMs. Surefire's forkCount=0 (run-in-Maven-JVM mode) bypasses the agent; coverage data is empty. Use the default fork mode.
  • No PR-context awareness. Pair with coverage-diff-reporter for the diff vs main.

References

  • jacoco-counters (opens in new window) - six counters (instructions, branches, lines, methods, classes, cyclomatic complexity), bytecode-level measurement, formula for cyclomatic complexity.
  • jacoco-maven (opens in new window) - prepare-agent / report / check goals; Maven + JVM version requirements; HTML report output path.
  • references/gradle-parsing-and-ci.md - the Gradle plugin config, the jacoco.xml parser, the counter-aware gating table, JaCoCo-to-Cobertura / LCOV conversion, and the Maven verify CI job.
  • lcov-analysis (references/cobertura.md) - sister parser; JaCoCo XML can convert to Cobertura XML for sibling tooling.
  • lcov-analysis - sister parser; JaCoCo can convert to LCOV for cross-language aggregation.
  • coverage-diff-reporter, test-coverage-targeter - downstream skills consuming the parsed JaCoCo output.

JaCoCo Gradle config, XML parsing, counter-aware gating, and CI

View source (opens in new window)

JaCoCo Gradle config, XML parsing, counter-aware gating, and CI

Deep reference for the jacoco-analysis SKILL.md. Consult for the Gradle plugin equivalent, the jacoco.xml parser, the per-counter gating table, the JaCoCo-to-Cobertura / LCOV conversions, and the Maven verify CI job.

Gradle equivalent

plugins {
  id 'java'
  id 'jacoco'
}

jacoco {
  toolVersion = '0.8.13'
}

test {
  finalizedBy jacocoTestReport
}

jacocoTestReport {
  dependsOn test
  reports {
    xml.required = true
    html.required = true
    csv.required = false
  }
}

jacocoTestCoverageVerification {
  violationRules {
    rule {
      limit {
        counter = 'INSTRUCTION'
        value = 'COVEREDRATIO'
        minimum = 0.80
      }
      limit {
        counter = 'BRANCH'
        value = 'COVEREDRATIO'
        minimum = 0.70
      }
    }
  }
}

check.dependsOn jacocoTestCoverageVerification

Parse jacoco.xml

The JaCoCo XML format mirrors the report tree (report -> package -> class -> method -> counter):

<report name="my-app">
  <package name="com/example/checkout">
    <class name="com/example/checkout/Cart" sourcefilename="Cart.java">
      <method name="addItem" desc="(LItem;)V" line="12">
        <counter type="INSTRUCTION" missed="0" covered="15"/>
        <counter type="BRANCH" missed="1" covered="3"/>
        <counter type="LINE" missed="0" covered="3"/>
        <counter type="METHOD" missed="0" covered="1"/>
        <counter type="COMPLEXITY" missed="0" covered="2"/>
      </method>
      <counter type="INSTRUCTION" missed="0" covered="42"/>
      ...
    </class>
    <counter type="INSTRUCTION" missed="0" covered="100"/>
    ...
  </package>
  <counter type="INSTRUCTION" missed="20" covered="500"/>
  ...
</report>
# scripts/parse_jacoco.py
import xml.etree.ElementTree as ET

def parse_jacoco(path):
    root = ET.parse(path).getroot()
    files = []
    for pkg in root.findall('package'):
        for cls in pkg.findall('class'):
            counters = {c.get('type'): {
                            'missed': int(c.get('missed')),
                            'covered': int(c.get('covered')),
                        } for c in cls.findall('counter')}
            files.append({
                'package': pkg.get('name'),
                'name': cls.get('name'),
                'sourcefile': cls.get('sourcefilename'),
                'counters': counters,
            })
    return files

Counter-aware metrics

Per jacoco-counters (opens in new window), the six counters have different semantics. Don't aggregate naively:

CounterWhen to gate on it
INSTRUCTIONThe most granular; least sensitive to source formatting. Best whole-repo gate.
LINEMost intuitive for reviewers; aggregates per source line.
BRANCHCritical for control flow correctness; gate separately from line.
METHODCoarse-grained; "any test touched this method" - useful as a "no dead code" floor.
CLASSEven coarser; "any test touched this class" - proves the test suite at least loads it.
COMPLEXITYPair with branch coverage; high-complexity uncovered methods are the highest-risk.

Cross-language: convert JaCoCo XML

For projects that mix JVM with other languages and want one coverage UI, convert JaCoCo XML to a sibling format.

To Cobertura

# Use the cover2cover.py script (community-maintained):
python cover2cover.py target/site/jacoco/jacoco.xml src/main/java > target/cobertura.xml

Then feed lcov-analysis (references/cobertura.md).

To LCOV

# Use the xml2lcov converter (per LCOV's language-agnostic converter family):
xml2lcov target/site/jacoco/jacoco.xml > target/jacoco.info

Then feed lcov-analysis. LCOV is the language-agnostic interchange format; LCOV's own documentation lists JaCoCo conversion as a supported path.

CI shape

- name: Run Maven verify
  run: ./mvnw -B verify

- name: Upload JaCoCo report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: jacoco
    path: target/site/jacoco/

- name: Convert to LCOV for SaaS
  run: xml2lcov target/site/jacoco/jacoco.xml > target/jacoco.info

- name: Upload to coverage SaaS
  uses: codecov/codecov-action@v5
  with:
    files: target/jacoco.info

Related skills

allure-reports

Configures Allure Report (test-runner adapter install, `allure-results` directory wiring, `categories.json` for failure classification, `history-trend.json` retention via the copy-history-between-runs pattern), runs the Allure CLI to convert `allure-results` to a static HTML site, and uploads the report as a CI artifact. Use when the team needs richer test reporting than JUnit XML - step-level attachments, per-test history, retry tracking, and severity / epic / feature labeling across framework-agnostic adapters (pytest, Jest, JUnit, TestNG, NUnit, Mocha). As a rich static HTML report generator, it is the open-source alternative to the sunset ExtentReports (JVM/.NET per-test HTML narrative); for hosted cross-run flakiness analytics rather than a static per-run report use currents-integration.

coverage-diff-reporter

Builds a per-PR coverage delta report from any pair of LCOV / Cobertura / JSON coverage outputs (current run + baseline from the merge target) - emits a per-file table with line% / branch% deltas, called-out new files, hidden drops (overall +0.1pp but one file -8pp), and a single-line PR-comment summary. Use when the team has coverage in CI but needs human-readable PR feedback that points at the specific file the reviewer should focus on, not just an aggregate number.

coverage-py-analysis

Configures coverage.py for Python projects - wires `coverage run` (replacing `python` for instrumentation), enables branch coverage via the `--branch` flag or `branch = True` config, manages the `.coverage` data file (single-process and `combine` for parallel pytest-xdist runs), authors `.coveragerc` with `source` / `omit` / `fail_under`, and emits the format the downstream tool needs (`coverage report` for terminal, `coverage xml` for Cobertura, `coverage html` for human review, `coverage lcov` for SaaS, `coverage json` for programmatic post-processing). Use for any Python test stack (pytest, unittest, nose) that needs PR-time coverage signal.

currents-integration

Wires Currents.dev cross-run test analytics into a Playwright suite: installs `@currents/playwright`, authors `currents.config.ts` (env-sourced `recordKey` + `projectId`), registers `currentsReporter()`, enables trace/video/screenshot artifacts, and runs via `npx pwc` so per-test traces stream to the Currents dashboard with over-time flakiness, slowest-test, and pass-rate trends. Use when a Playwright suite needs hosted cross-run suite-health analytics; for a static per-run report use extentreports or allure-reports, and to sync results into TestRail / Xray / Zephyr test management use test-management-sync.

junit-xml-analysis

Explains CI test numbers that disagree with what the suite actually did - a 'slowest tests' list dominated by the wrong suite, a release gate or dashboard reading only the summary attributes on the suite element and never the cases below them, or a pass rate that quietly counts skipped tests as passes. Parses JUnit-format XML (the interchange format Jenkins, GitHub Actions, GitLab, Buildkite, and CircleCI all ingest) into per-suite and per-case metrics tables - passed / failed / errored / skipped, time, classname, message, stack - groups failures by classname for trend analysis, and separates new failures from flakes by cross-referencing the `flakyFailure` and rerun elements. Use when a report, gate, or metric derived from test results cannot be trusted.

lcov-analysis

Parses both mainstream coverage interchange formats: LCOV `.info` text files (produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others) and Cobertura XML (coverage-04.dtd - emitted by JaCoCo, coverage.py `--xml`, Jest's `cobertura` reporter, coverlet, gocover-cobertura; full parser in references/cobertura.md). Extracts per-file line / function / branch metrics from the canonical record keywords (TN/SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LH/LF), computes the diff vs a baseline, and emits per-file gating verdicts. Use for PR coverage gates that don't depend on a specific language runtime, whichever of the two formats the CI emits.

test-coverage-targeter

Builds a "what to test next" recommendation by combining a coverage report (LCOV / Cobertura / coverage.py JSON / Jest JSON / JaCoCo XML) with the PR's `git diff`, ranking uncovered branches by risk × cost - risk weighted by McCabe cyclomatic complexity and code-churn frequency, cost weighted by the unit-test pyramid layer (unit tests cheaper than integration than E2E). Also carries the coverage debt ledger: a weekly per-file drift report over N historical main runs flagging `falling` (line% slid >M pp from peak), `stale` (flat coverage + high churn), and `orphan` (lost last covering test) files, whose rows feed the same targeting. Emits a prioritized list with concrete file:line targets and the test layer recommended for each. Use when a team has the budget to write 5 - 10 new tests and needs help picking which uncovered code to target first instead of blindly chasing 100% coverage, or when specific modules are eroding silently while whole-repo coverage looks fine.

test-management-sync

Syncs automated test results into test management tools - TestRail (standalone, `add_run` + batched `add_results_for_cases`), Xray for Jira (JWT auth + `/api/v2/import/execution/*`), and Zephyr Scale (Bearer token + `/testexecutions`) - from CI. The body carries the vendor-independent push-results workflow (map tests to case IDs, open a run / execution / cycle per build, batch results back, close on main only, run as an `if: always()` step) with TestRail as the worked example; full vendor specifics live in references/ (testrail.md, xray.md, zephyr.md). Use when automated suites must keep the team's test management view in sync without a human copy-paste step; for hosted cross-run flakiness analytics rather than TCM sync use currents-integration, and for authoring / migrating test CASES rather than pushing results see qa-test-management's tcm-case-management.

test-run-summary-author

Build-an-X workflow that turns a structured test-run artifact (JUnit XML, Allure JSON, TestRail / Xray / Zephyr export) plus optional release context (version, build URL, deploy target) into a narrative markdown summary for release notes, an exec status update, or a stand-up Slack post. Distinct from the per-framework parsers junit-xml-analysis / allure-reports / coverage-diff-reporter, which emit structured tabular reports: this skill takes the same data and writes the human-readable narrative. Use when a manager needs a draft release note or stand-up summary from a single run; for cross-run trend analytics use currents-integration.