pycobertura
pycobertura is a Python library and command-line tool for parsing Cobertura XML coverage reports. It can display, filter, and diff coverage reports, highlighting changes in coverage metrics between two reports. The current version is 4.1.0, and it maintains an active, moderate release cadence, with major versions typically aligning with Python version support or significant API changes.
Warnings
- breaking Python 3.6 support was dropped in pycobertura v4.0.0. Users on Python 3.6 must remain on pycobertura < 4.0.0 or upgrade their Python interpreter.
- breaking The `Cobertura` class constructor API changed significantly in v3.0.0. Previously, it expected a pre-parsed `lxml.etree._Element` object. Since v3.0.0, it expects either a filename string or a string containing the XML content directly.
- gotcha When diffing reports, ensure that the file paths referenced within the Cobertura XML (e.g., `<class filename="my_module/my_file.py">`) are consistent between the two reports, or pycobertura may not correctly match files for comparison.
Install
-
pip install pycobertura
Imports
- Cobertura
from pycobertura import Cobertura
- diff
from pycobertura import diff
- HtmlReporter
from pycobertura.reporters import HtmlReporter
Quickstart
import io
from pycobertura import Cobertura, diff
# Example Cobertura XML content
cobertura_xml_baseline = '''<?xml version="1.0" ?>
<coverage branches-covered="0" branches-valid="0" complexity="0" line-rate="0.5" lines-covered="1" lines-valid="2" timestamp="1678886400" version="coverage 6.0.0">
<sources>
<source>/path/to/project</source>
</sources>
<packages>
<package line-rate="0.5" branch-rate="0" complexity="0" name="my_module">
<classes>
<class name="my_file.py" filename="my_module/my_file.py" line-rate="0.5" branch-rate="0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="0"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>'''
cobertura_xml_current = '''<?xml version="1.0" ?>
<coverage branches-covered="0" branches-valid="0" complexity="0" line-rate="1.0" lines-covered="2" lines-valid="2" timestamp="1678886500" version="coverage 6.0.0">
<sources>
<source>/path/to/project</source>
</sources>
<packages>
<package line-rate="1.0" branch-rate="0" complexity="0" name="my_module">
<classes>
<class name="my_file.py" filename="my_module/my_file.py" line-rate="1.0" branch-rate="0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>'''
# Parse the Cobertura reports
# Cobertura can take a filename (string) or a string containing the XML
baseline_report = Cobertura(cobertura_xml_baseline)
current_report = Cobertura(cobertura_xml_current)
print(f"Baseline report line rate: {baseline_report.line_rate:.2f}")
print(f"Current report line rate: {current_report.line_rate:.2f}")
# Calculate the diff between the two reports
differences = diff(baseline_report, current_report)
print("\n--- Coverage Differences ---")
for file_path, file_diff in differences.files.items():
print(f"File: {file_path}")
print(f" Line rate change: {file_diff.line_rate_change:.2f}")
print(f" Lines covered change: {file_diff.lines_covered_change}")
print(f" Lines valid change: {file_diff.lines_valid_change}")
# Access changes for specific lines if available
if 'my_module/my_file.py' in differences.files:
file_lines_changes = differences.files['my_module/my_file.py'].lines
for line_number, status in file_lines_changes.items():
print(f" Line {line_number}: {status.name}") # e.g., NO_CHANGE, COVERED, UNCOVERED_NEW