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.
  • cobertura-analysis - 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 cobertura-analysis.

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 extentreports (JVM/.NET per-test HTML narrative); for hosted cross-run flakiness analytics rather than a static per-run report use currents-integration.

cobertura-analysis

Parses Cobertura XML coverage reports (the JVM-canonical format originally from the cobertura-cobertura tool, also emitted by JaCoCo `--coverage-xml`, coverage.py `--xml`, Istanbul / Jest `cobertura` reporter, gocover-cobertura, and dotnet's `coverlet`). Walks the coverage-04 DTD structure (coverage → packages → classes → methods → lines + conditions), computes per-file deltas, and emits PR-time gating verdicts. Use when the existing CI emits Cobertura XML - typical for JVM-heavy stacks and tools that ship Cobertura as a default reporter.

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 Jira test management use zephyr-integration or xray-integration.

extentreports

Configures ExtentReports v5 for a JVM (or .NET via `extentreports-dotnet`) test run: wires `ExtentSparkReporter`, `attachReporter`, `createTest`, the `info`/`pass`/`warning`/`skip`/`fail` log chain, screenshots via `MediaEntityBuilder`, hierarchical `createNode` parent/child tests, and category/author/device labels, emitting a static HTML report alongside JUnit XML for CI artifact upload. Use when a suite on the Aventstack ExtentReports stack wants a richer per-test HTML narrative than JUnit XML gives; for code-coverage reporting use jacoco-analysis, and for hosted cross-run flakiness analytics use currents-integration.

jest-coverage-analysis

Configures Jest's built-in coverage (Istanbul-instrumented `babel` provider or V8-native `v8` provider), wires the right `coverageReporters` for downstream consumption (`lcov` for SaaS / cross-tool, `cobertura` for Jenkins, `text-summary` for terminal, `html` for human review), authors per-file `coverageThreshold` rules that focus the gate on critical paths (vs the global-only foot-gun), and parses the per-file JSON output for PR-time deltas. Use when the project tests with Jest (or Vitest, which uses the same Istanbul/V8 provider) and the team needs PR-time coverage signal that's both local-runnable and CI-gateable.

junit-xml-analysis

Parses JUnit-format XML reports (the de-facto interchange format every CI ingests - Jenkins, GitHub Actions, GitLab, Buildkite, CircleCI) into structured, machine-readable per-suite and per-case metrics tables (passed / failed / errored / skipped, time, classname, message, stack), groups failures by classname for trend analysis, and distinguishes "new failures vs flakes" by cross-referencing the `flakyFailure` and `rerunFailure` rerun elements. Use when the downstream consumer is a dashboard, script, or aggregator - not when the goal is a human-readable prose summary (use test-run-summary-author for that). Single-run, in-XML aggregation only; for cross-run cross-environment roll-ups, use a cross-run test-suite aggregator.

lcov-analysis

Parses LCOV `.info` text files (the de-facto coverage interchange format produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others). 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.

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). 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.

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.

testrail-integration

Syncs test runs / results / cases between an automated test suite and TestRail (Gurock / Idera) - opens a Test Run for the build (`add_run`), batches per-case results back via `add_results_for_cases` (preferred over per-test `add_result_for_case` - N+1 API calls vs 1), maps the test framework's pass/fail/skip to TestRail status IDs, and attaches build URL + version + elapsed time. Use when the team's test management is standalone TestRail (not a Jira app) and automated suites must update it without a human copy-paste step; when the TCM is instead a Jira app use xray-integration (Xray) or zephyr-integration (Zephyr Scale), and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.

xray-integration

Imports CI test results into Xray for Jira - authenticates via the `client_id` + `client_secret` → JWT exchange (Cloud) or PAT / Basic (Server), posts to the format-specific `/api/v2/import/execution/*` endpoint (`/junit` for JUnit XML, `/cucumber` for Cucumber JSON, `/nunit` / `/testng` / `/robot` for the others), and maps automated results to existing Xray Test issues via the `xray-junit-extensions` `@XrayTest(key="...")` annotation. Use when the team uses the Xray Jira app to manage Test, Test Set, and Test Execution issue types and CI must keep those execution issues in sync; for the other Jira TCM app use zephyr-integration (Zephyr Scale), for standalone non-Jira TestRail use testrail-integration, and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.

zephyr-integration

Syncs automated test results to Zephyr Scale for Jira (formerly TM4J / SmartBear / Adaptavist): picks the product variant (Scale Cloud / Squad / Enterprise), authenticates with a long-lived API token as a Bearer header, opens a Test Cycle per build, posts executions via `POST /testexecutions` (or bulk JUnit via `/automations/executions/junit`), and maps test methods to Zephyr Test Cases via `@TestCaseKey`-style annotations. Use when the team's Jira test management is Zephyr Scale; for the Xray Jira app use xray-integration, for standalone TestRail use testrail-integration, and for over-time flakiness analytics rather than TCM sync use currents-integration.