A Cloverage custom reporter plugin that outputs Cobertura XML coverage reports.
Cobertura XML is the de-facto standard consumed by CI/CD systems such as GitLab CI, Jenkins (Cobertura Plugin), GitHub Actions (coverage summary actions), Codecov, and SonarQube.
| Tool | Version |
|---|---|
| Java | 8+ |
| Clojure | 1.10+ |
| Leiningen | 2.x (optional) |
Clojure CLI (clj) | 1.11+ (optional) |
| cloverage / lein-cloverage | 1.2.x |
Add the library to your project's :dependencies (or :dev profile):
;; project.clj
:dependencies [[org.clojars.tooooolong/clojure-cobertura-coverage "0.1.0"]]
:profiles {:dev {:plugins [[lein-cloverage "1.2.4"]]}}
Add a :coverage alias to your deps.edn:
;; deps.edn
{:aliases
{:coverage
{:extra-paths ["test"]
:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}
org.clojars.tooooolong/clojure-cobertura-coverage
{:mvn/version "0.1.0"}}
:main-opts ["-m" "cloverage.coverage"
"--custom-report" "cloverage.coverage.cobertura/report"
"--output" "target/coverage"]}}}
lein cloverage --custom-report cloverage.coverage.cobertura/report
The report is written to target/coverage/cobertura.xml (or whatever
directory you have configured as cloverage's :output).
project.clj:cloverage {:output "target/coverage"
:custom-report cloverage.coverage.cobertura/report}
Then run the normal coverage command:
lein cloverage
clj -M:coverageWith the alias defined in deps.edn (see Installation above):
clj -M:coverage
To override options on the command line, append them after --:
# Custom output directory
clj -M:coverage --output target/my-coverage
# Also generate the built-in HTML report
clj -M:coverage --html
The --custom-report flag is additive — Cloverage still runs all the
reporters you enable (HTML, text, etc.) in addition to the custom one:
# Leiningen
lein cloverage --html --custom-report cloverage.coverage.cobertura/report
# Clojure CLI
clj -M:coverage --html
The generated cobertura.xml follows the Cobertura 4 DTD schema:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage line-rate="0.7500" branch-rate="0.0"
lines-covered="6" lines-valid="8"
branches-covered="0" branches-valid="0"
complexity="0" version="1" timestamp="1712134567">
<sources>
<source>src</source>
</sources>
<packages>
<package name="example" line-rate="0.7500" branch-rate="0.0" complexity="0">
<classes>
<class name="example.core" filename="example/core.clj"
line-rate="0.7500" branch-rate="0.0" complexity="0">
<methods/>
<lines>
<line number="7" hits="3" branch="false"/>
<line number="12" hits="2" branch="false"/>
<line number="18" hits="0" branch="false"/>
...
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
| Clojure namespace | Cobertura package | Cobertura class |
|---|---|---|
example.core | example | example.core |
example.util.str | example.util | example.util.str |
core (top-level) | (default) | core |
This repository includes an example namespace (example.core) that
deliberately leaves two functions (divide, factorial) untested so you
can see partial coverage in the report.
# Leiningen
lein cloverage --custom-report cloverage.coverage.cobertura/report
# Clojure CLI (uses the :coverage alias in this repo's deps.edn)
clj -M:coverage
After the run, inspect target/coverage/cobertura.xml.
test:
script:
# Leiningen
- lein cloverage --custom-report cloverage.coverage.cobertura/report
# — or Clojure CLI —
# - clj -M:coverage
coverage: '/Coverage: (\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: target/coverage/cobertura.xml
stage('Test') {
steps {
// Leiningen
sh 'lein cloverage --custom-report cloverage.coverage.cobertura/report'
// — or Clojure CLI —
// sh 'clj -M:coverage'
}
post {
always {
cobertura coberturaReportFile: 'target/coverage/cobertura.xml'
}
}
}
- name: Run tests with coverage (Leiningen)
run: lein cloverage --custom-report cloverage.coverage.cobertura/report
# — or Clojure CLI —
# - name: Run tests with coverage (Clojure CLI)
# run: clj -M:coverage
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage
path: target/coverage/cobertura.xml
Cloverage's --custom-report option accepts a fully-qualified symbol
(namespace/function). After running all instrumented tests, Cloverage
calls the function with a single map:
{:output "target/coverage" ; output directory
:forms [...] ; raw coverage forms collection
:args {...} ; parsed CLI options
:project {...}} ; Leiningen project map
This reporter:
cloverage.report/file-stats on :forms to get per-file metrics
(covered lines, instrumented lines, etc.)cloverage.report/line-stats
per file to obtain per-line hit countscobertura.xml to the output directoryThis project uses a GitHub Actions release workflow that
deploys to Clojars automatically when you push a v* tag.
Add this repository secret in Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
CLOJARS_DEPLOY_TOKEN | A Clojars deploy token for the publishing account |
The workflow uses the fixed Clojars username tooooolong; the token replaces the
password when deploying.
# 1. Ensure main is clean and tests pass
git checkout main
git pull
# 2. Tag the release (the workflow strips the leading 'v')
git tag v0.2.0
git push origin v0.2.0
The workflow will:
v0.2.0 → 0.2.0)project.clj with that version in the ephemeral CI workspacelein deploy clojarsThe project.clj in the repository always stays at the development version (0.1.0).
Bump it manually before tagging if you want the version shown in editor tooling to match.
Copyright © 2024 tooooolong
Distributed under the Eclipse Public License 2.0.
Can you improve this documentation? These fine people already did:
Ethan YU & tooooolongEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |