report:code-coverage
The report:code-coverage directive renders a code coverage report as a table: a row per directory and source
file, with the line and branch coverage. The hierarchy is shown by indentation, a directory by 📁 and a file by
📄, and each row is coloured by its coverage level. report:code-coverage-legend renders the coverage levels.
A report is a Cobertura XML file - written for any language, e.g. by
coverage.py or gcovr - or coverage.py’s JSON report. Report files are declared in conf.py, each with an
identifier the directives name in their report:code-coverage:reportid option - so a documentation
shows as many reports as it declares.
A report can get a page per directory and source file: a file’s page shows its
source, syntax highlighted, each line marked by its coverage state. The table links to the pages, and the role
cov refers to them.
The directives are part of pyTooling.Sphinx; they need the
extra reports.
.. grid:: 2
.. grid-item::
:columns: 8
.. report:code-coverage::
:reportid: example
.. grid-item::
:columns: 4
.. report:code-coverage-legend::
:reportid: example
:style: vertical-table
This is how the example renders, from coverage.py’s report of a Python package with one sub-package. The report is declared with pages, so every directory and file links to its page:
| Directory | Lines | Branches | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| File | Total | Excluded | Covered | Missing | Coverage | Total | Covered | Partial | Missing | Coverage |
| 📦myPackage | 27 | 2 | 22 | 5 | 81.5% | 10 | 5 | 3 | 5 | 50.0% |
| 📁myPackage | 27 | 2 | 22 | 5 | 81.5% | 10 | 5 | 3 | 5 | 50.0% |
| 📁Units | 8 | 0 | 5 | 3 | 62.5% | 6 | 3 | 1 | 3 | 50.0% |
| 📄Length.py | 8 | 0 | 5 | 3 | 62.5% | 6 | 3 | 1 | 3 | 50.0% |
| 📄__init__.py | 0 | 0 | 0 | 0 | 100.0% | 0 | 0 | 0 | 0 | 100.0% |
| 📄Shapes.py | 18 | 2 | 16 | 2 | 88.9% | 4 | 2 | 2 | 2 | 50.0% |
| 📄__init__.py | 1 | 0 | 1 | 0 | 100.0% | 0 | 0 | 0 | 0 | 100.0% |
| Overall (4 files): | 27 | 2 | 22 | 5 | 81.5% | 10 | 5 | 3 | 5 | 50.0% |
| Code Coverage | Coverage Level |
|---|---|
| ≤10 % | almost unused |
| ≤20 % | almost unused |
| ≤30 % | almost unused |
| ≤40 % | poorly used |
| ≤50 % | poorly used |
| ≤60 % | somehow used |
| ≤70 % | somehow used |
| ≤80 % | somehow used |
| ≤85 % | well used |
| ≤90 % | well used |
| ≤95 % | well used |
| ≤100 % | excellently used |
The same directive renders a Cobertura report of a VHDL design:
| Directory | Lines | Branches | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| File | Total | Excluded | Covered | Missing | Coverage | Total | Covered | Partial | Missing | Coverage |
| 📦myDesign | 16 | 0 | 12 | 4 | 75.0% | 12 | 8 | 2 | 4 | 66.7% |
| 📁src | 16 | 0 | 12 | 4 | 75.0% | 12 | 8 | 2 | 4 | 66.7% |
| 📁Utilities | 8 | 0 | 5 | 3 | 62.5% | 4 | 2 | 0 | 2 | 50.0% |
| 📄Functions.vhdl | 8 | 0 | 5 | 3 | 62.5% | 4 | 2 | 0 | 2 | 50.0% |
| 📄Counter.vhdl | 8 | 0 | 7 | 1 | 87.5% | 8 | 6 | 2 | 2 | 75.0% |
| Overall (2 files): | 16 | 0 | 12 | 4 | 75.0% | 12 | 8 | 2 | 4 | 66.7% |
The documentation’s own code coverage report is rendered by these directives too, with a page per directory and source file.
Configuration
The report files are declared in conf.py as a dictionary, keyed by the identifier a directive names in
report:code-coverage:reportid. A path is relative to the documentation’s source directory.
pyTooling_CodeCoverage_Packages→ The code coverage reports, by identifier. An entry has these fields:
name→ The name of the measured project, shown in the first row.
xml_reportorjson_report→ The report file: a Cobertura XML file, or coverage.py’s JSON report. Exactly one of both.
sources→ The directory the report’s file paths are relative to, from which a file’s source is read. Optional; required with
pages.pages→ The document name the pages per directory and source file are generated below, e.g.
coverage/src. Optional; without it, the report gets no pages.fail_below→ An integer in range 0..100: the coverage in percent below which the package fails.
levels→ The name of a palette in
pyTooling_CodeCoverage_Levels, e.g."default", or a palette of its own, written as there.
pyTooling_CodeCoverage_Levels→ The palettes of coverage levels, by name. A palette maps an upper limit in percent to a CSS class and a description, and needs a level
100and a level"error". Default: the palettedefault, of 12 levels from ≤10 % to ≤100 %, coloured from blue via red, orange and yellow to green.
# doc/conf.py
pyTooling_CodeCoverage_Packages = {
"src": {
"name": "myPackage",
"json_report": "../report/coverage/coverage.json",
"sources": "..",
"pages": "coverage/src",
"fail_below": 80,
"levels": "default"
},
"vhdl": {
"name": "myDesign",
"xml_report": "../report/coverage/cobertura.xml",
"sources": "../src",
"pages": "coverage/vhdl",
"fail_below": 80,
"levels": "default"
}
}
The first entry is the default report of the role cov.
A palette of its own:
# doc/conf.py
pyTooling_CodeCoverage_Levels = {
"coarse": {
50: {"class": "report-cov-below50", "desc": "poorly used"},
80: {"class": "report-cov-below80", "desc": "somehow used"},
100: {"class": "report-cov-below100", "desc": "well used"},
"error": {"class": "report-cov-error", "desc": "internal error"},
}
}
The configuration is checked when the build starts. A report file that doesn’t exist is logged as an error, and the entries after it aren’t read: an entry whose file is written later - e.g. by a test run in CI - goes last.
Report formats
A report is read with the code coverage data model of pyEDAA.Reports: directories and source files, built from the file paths the report names, and per file the coverage of its executable lines.
xml_report: Cobertura XML→ Written by many tools for any language, e.g. coverage.py (
coverage xml) for Python or gcovr (--cobertura) for C, C++ and other languages compiled by GCC. A file’s path is relative to one of the report’s<source>directories, sosourcesnames that directory. A line has hits - how often it ran -, and a branching line the number of its taken branches. The format has no excluded lines.json_report: coverage.py’s JSON report→ Written by
coverage json. A file’s path is relative to the directory coverage.py ran in. A line has no hits; excluded lines - e.g. marked# pragma: no cover- are listed.
Hint
coverage.py writes both formats from one measurement, naming the files differently: Cobertura relative to the
measured source directory - e.g. Shapes.py -, JSON relative to the directory coverage.py ran in - e.g.
myPackage/Shapes.py.
The table shows a directory’s and a file’s lines - executable, excluded, covered, missing - and branches - all,
covered, partially covered lines, missing -, each with its coverage. A chain of directories holding no file and one
directory each - e.g. pyTooling/Sphinx - is one row.
Pages per directory and source file
A report declared with the key pages gets a page per directory and per source file. They are generated when the
documentation is built - no source file is written - below the document name the key states, named by the path in
the report, e.g. coverage/vhdl/src/Counter.vhdl. A chain of directories holding no file and one directory each is
one page, e.g. coverage/src/pyTooling_Sphinx.
A directory’s page shows its line and branch coverage and its parent directory; then a table of its directories and a table of its files, each linked to its page.
A source file’s page shows its line and branch coverage, its parent directory and its missed lines, e.g.
8, 14-17; then its listing. The source is read from the report’s sources directory, and syntax highlighted
with the Pygments lexer of the file’s name - every language Pygments knows, e.g. Python, VHDL, Verilog, C, C++,
Java, Rust, Go, Tcl or Bash; a file without a lexer is shown as plain text. The colors are those of Sphinx’
Pygments style, as of every code block. Each line is marked by its coverage state:
State |
Line |
|---|---|
|
The line ran, and all of its branches - if it has any - were taken. |
|
The line ran, but not all of its branches were taken; on hover, it says how many were. |
|
The line never ran. |
|
The line is excluded from the measurement. |
(none) |
The line isn’t executable: a comment, a declaration, an empty line. |
A header above the listing shows the file’s path and a legend of these states. A line’s number and - if the report
has them - its hits are written in front of it. A line has the ID L<number>, so the role cov links to
it.
The report must have been measured on the source as it is: a report naming a line beyond a file’s end is warned about.
The report:code-coverage directive lists the top-level directories and files in a hidden table of contents,
and a directory’s page lists its directories and files, so the pages are in the navigation below the table’s
document. LaTeX writes the pages too, below the table’s section; a listing’s line numbers are colored by the coverage
state, followed by a marker: + covered, ~ partial, - uncovered, x excluded; a line above the
listing shows the file’s path and the markers’ legend.
Sphinx regards the report file as the source of its pages, so a changed report regenerates its pages.
Hint
A file is as many levels below the table’s document as its path has directories. A theme limits how deep its
navigation goes, e.g. the option navigation_depth of the Read the Docs theme (default 4; -1 =
unlimited).
Role
- :cov:
Refers to a directory’s or source file’s page, showing its name, or the role’s title:
:cov:`<target>`or:cov:`title <<target>>`. It is registered in domainreport- as:report:cov:- and without the domain’s prefix.The target is
[<report ID>:]<path>[#<line>]. The path is the one in the report, e.g.src/Counter.vhdl. A suffix of it starting at a name -Counter.vhdl- is accepted, if it’s unique. Without a report ID, the first report ofpyTooling_CodeCoverage_Packagesis meant. A line number links to the line of a file’s listing, and is shown behind the file’s name.A target matching nothing or several directories and files is warned about.
* :cov:`Shapes.py`
* :cov:`myPackage/Units`
* :cov:`vhdl:Counter.vhdl`
* :cov:`vhdl:Counter.vhdl#27`
* :cov:`the reset branch <vhdl:src/Counter.vhdl#26>`
The directories and files are objects of domain report of type source, named <report ID>:<path> in the
documentation’s inventory (objects.inv), so another documentation refers to them with intersphinx.
report:file-coverage
The report:file-coverage directive writes a source file’s listing where it is, as a file’s page does - also for a
report without pages. Its argument is the file’s path in the report:
.. report:file-coverage:: myPackage/Units/Length.py
:reportid: example
1"""Conversion of lengths to meters."""
2
3
4def ToMeters(value: float, unit: str) -> float:
5 """
6 Convert a length to meters.
7
8 The doc-string spans lines, as a lexer has to see it whole.
9 """
10 if unit == "m":
11 return value
12 elif unit == "cm":
13 return value / 100
14 elif unit == "mm":
15 return value / 1000
16
17 raise ValueError(f"Unknown unit '{unit}'.")
Options
- .. report:code-coverage::
Renders a code coverage report as a table, on landscape pages in LaTeX.
- :reportid: <identifier>
The report, as declared in
pyTooling_CodeCoverage_Packages. Required.
- :no-branch-coverage:
Leaves out the branch coverage columns.
- :class: <CSS classes>
Additional CSS classes on the table.
- .. report:code-coverage-legend::
Renders the coverage levels of a report as a table.
- :reportid: <identifier>
The report, as declared in
pyTooling_CodeCoverage_Packages, whose palette is shown. Required.
- :style: horizontal-table | vertical-table
A column per level, or a row per level. Default:
horizontal-table.
- :class: <CSS classes>
Additional CSS classes. Accepted, but not yet put on the table.
Styling
The table carries the classes report-codecov-table and report-codecov-<identifier>, a legend
report-codecov-legend. A row carries its kind - report-package for the root, report-directory,
report-file or report-summary - and the CSS class of its coverage level, e.g. report-cov-below80. The
stylesheet of pyTooling.Sphinx colours the rows of the default palette; a project overrides these rules, or
adds rules for a palette of its own, in a stylesheet listed in html_css_files:
table.report-codecov-table > tbody > tr.report-cov-below80,
table.report-codecov-legend > tbody > tr.report-cov-below80 {
background: hsl(45 75% 80%);
}
A name is indented by its depth, one step per level: it is a <span> with class report-indentation, which sets
the custom property --pyTooling-report-level to its number of steps. A wrapped name stays at its indentation. The
width of a step is a custom property, in LaTeX the length \pyToolingReportIndentationStep:
:root {
--pyTooling-report-indentation: 1em;
}
# doc/conf.py
latex_elements = {
"preamble": r"\setlength{\pyToolingReportIndentationStep}{1em}",
}
A listing is a <div> with class report-coverage-listing, its header a <div> with class
report-coverage-header holding the path and the legend, a line a <span> with class report-line and
report-line-<state>. Its colors and widths are custom properties:
:root {
--pyTooling-coverage-covered: rgba(46, 160, 88, 0.14); /* line background */
--pyTooling-coverage-covered-marker: #2e9e58; /* left border */
--pyTooling-coverage-partial: rgba(218, 160, 24, 0.24);
--pyTooling-coverage-partial-marker: #b88910;
--pyTooling-coverage-uncovered: rgba(212, 58, 58, 0.16);
--pyTooling-coverage-uncovered-marker: #c63c3c;
--pyTooling-coverage-excluded: rgba(110, 120, 130, 0.10);
--pyTooling-coverage-excluded-marker: #8a949c;
--pyTooling-coverage-none-marker: #c8c8c8; /* legend of a line, which isn't executable */
--pyTooling-coverage-header-border: 1px solid #e1e4e5;
--pyTooling-coverage-marker-width: 4px;
--pyTooling-coverage-tab-size: 4;
}
Errors
A mistake in the options, or a report that isn’t configured, is reported on the page where the table would be, and in the build’s log:
Caught ReportExtensionError when checking options for directive 'code-coverage'.
ReportExtensionError: No configuration for 'src'
A report that can’t be read - e.g. without pyEDAA.Reports installed - is reported where its table would be:
Caught ReportsPackageMissingError when reading code coverage report 'src'.