Coverage for /builds/svesch/cikit/src/cikit/macros/coverage.py: 97%
63 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-09 14:17 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-09 14:17 +0000
1# SPDX-License-Identifier: GPL-3.0-or-later
2"""Coverage report helpers for cikit.
4This module provides helpers for generating Python and C coverage reports,
5registering coverage artifacts, creating badges and writing Sphinx RST
6summary pages.
8Author:
9 Sven Schlender
10Date:
11 2026-05-28
12"""
14import logging
15from pathlib import Path
16from tempfile import TemporaryDirectory
18from cikit.badge import write_badge
19from cikit.command import Command, run_command
20from cikit.artifacts import Artifact, ArtifactType
21from cikit.env import write_file
22from cikit.rst_tools import RstBlock, RstDocument, get_sphinx_paths, timeline_artifacts_block
23from cikit.xml_tools import read_cobertura_coverage
25from .common import resolve_name_label
28LOGGER = logging.getLogger(__name__)
31def _register_coverage_outputs(env, xml_file: Path, html_dir: Path):
32 """Register coverage report outputs and update cikit data.
34 Coverage metrics are extracted from the generated Cobertura XML file
35 and stored in the current run data. The XML report and HTML report
36 directory are copied to their public artifact directories and registered
37 for later processing.
39 Args:
40 env: Resolved CI run environment.
41 xml_file: Generated Cobertura XML report.
42 html_dir: Generated HTML coverage report directory.
43 """
44 env.data_update(read_cobertura_coverage(xml_file))
45 env.copy_artifact(xml_file, ArtifactType.XUNIT)
46 env.copy_artifact(html_dir, ArtifactType.FOLDER)
49def pycoverage(env, coverage_dir: Path):
50 """Create Python coverage XML and HTML reports.
52 Args:
53 env: Resolved CI run environment.
54 coverage_dir: Directory containing parallel coverage data files.
56 Raises:
57 CiToolsError: If coverage report generation fails.
58 """
59 name = env.name
61 coverage_dir = env.resolve_path(coverage_dir)
63 with TemporaryDirectory() as temp_dir:
64 temp_dir = Path(temp_dir)
65 xml_file = temp_dir / f"{name}.xml"
66 html_dir = temp_dir / name
68 coverage_file_count = sum(1 for _ in coverage_dir.glob(".coverage.*"))
69 LOGGER.info("Combining %d coverage data files.", coverage_file_count)
70 env.data_set("coverage_files", coverage_file_count)
72 cmd = Command("coverage", use_exec_tool=True)
73 cmd.add_subcommand("combine", ["combine", "--keep", str(coverage_dir)])
74 cmd.add_subcommand("xml", ["xml", "-o", str(xml_file)])
75 cmd.add_subcommand("html", ["html", "-d", str(html_dir)])
77 run_command(env.argv(cmd, "combine"), cwd=temp_dir, check_exit_code=True)
78 run_command(env.argv(cmd, "xml"), cwd=temp_dir, check_exit_code=True)
79 run_command(env.argv(cmd, "html"), cwd=temp_dir, check_exit_code=True)
81 _register_coverage_outputs(env, xml_file, html_dir)
84def ccoverage(env, build_dir: Path):
85 """Create C coverage XML and HTML reports with gcovr.
87 Args:
88 env: Resolved CI run environment.
89 build_dir: Build directory containing gcovr coverage data.
91 Raises:
92 CiToolsError: If coverage report generation fails.
93 """
94 name = env.name
96 with TemporaryDirectory() as temp_dir:
97 temp_dir = Path(temp_dir)
99 xml_file = temp_dir / f"{name}.xml"
100 html_dir = temp_dir / name
101 html_dir.mkdir(parents=True, exist_ok=True)
103 args = ["--object-directory", str(build_dir), "--xml-pretty", "--xml", str(xml_file),
104 "--html-details", str(html_dir / "index.html")]
105 cmd = Command("gcovr", common_args=args, use_exec_tool=True)
107 run_command(env.argv(cmd), cwd=env["PROJECT_ROOT"], check_exit_code=True)
109 _register_coverage_outputs(env, xml_file, html_dir)
112def badge(env, name: str = None, label: str = None) -> Artifact:
113 """Generate the coverage badge.
115 Args:
116 env: Resolved CI run environment.
117 name: Basename of the badge file. If omitted, the run name is used.
118 label: Label of the badge. If omitted, the run name is used.
120 Returns:
121 Created and registered badge artifact.
122 """
123 name, label = resolve_name_label(env, name, label)
125 coverage_value = env.data.get("coverage", 0)
126 return write_badge(env, name=name, label=label, value=f"{coverage_value}%",
127 thresholds={50: "red", 70: "orange", 85: "yellow", 95: "green"})
130def write_rst(env, report_title: str = None) -> Artifact:
131 """Create an RST page for a coverage report.
133 The generated page contains a summary of the collected coverage metrics
134 and a link to the corresponding HTML coverage report.
136 Args:
137 env: Resolved CI run environment.
138 report_title: Title of the RST page. If omitted, ``Coverage Report`` is used.
140 Returns:
141 Created and registered RST artifact.
143 Raises:
144 CiToolsError: If the corresponding HTML coverage artifact is not found or is ambiguous.
145 """
146 name = env.name
148 if report_title is None: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true
149 report_title = "Coverage Report"
151 paths = get_sphinx_paths()
152 html_artifact = env.get_run_artifact(name, ArtifactType.FOLDER)
154 document = RstDocument(report_title)
155 document.add_block(timeline_artifacts_block(env.get_run_artifacts(ArtifactType.TIMELINE)))
157 summary = RstBlock("Summary")
158 summary.extend([
159 f"- Coverage: {env.data.get('coverage')}",
160 f"- Lines covered: {env.data.get('lines_covered')}",
161 f"- Lines valid: {env.data.get('lines_valid')}",
162 "",
163 f"`HTML coverage report <{paths.rst_artifact(html_artifact, 'index.html')}>`_",
164 ])
165 document.add_block(summary)
167 with TemporaryDirectory() as temp_dir:
168 source_file = Path(temp_dir) / f"{name}.rst"
169 write_file(source_file, document.text())
170 return env.copy_artifact(source_file, ArtifactType.RST)