Coverage for /builds/svesch/cikit/src/cikit/macros/reports.py: 97%
61 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"""Generic reporting helpers for cikit.
4This module contains reusable helpers for generating reports from cikit run
5data. It provides badge generation and Sphinx RST reports for test and build
6results, including timelines, metrics, artifacts, memory usage, and logs.
8Test reporting operates on the standardized result fields produced by
9``read_junit_totals()`` and remains independent of the underlying test
10framework.
12Author:
13 Sven Schlender
14Date:
15 2026-09-13
16"""
18from pathlib import Path
19from tempfile import TemporaryDirectory
21from cikit.artifacts import Artifact, ArtifactType
22from cikit.badge import write_badge
23from cikit.env import write_file
24from cikit.errors import CiRecoverableError
25from cikit.rst_tools import (RstBlock, RstDocument, artifact_links_block, log_artifacts_block,
26 memory_usage_block, timeline_artifacts_block)
27from cikit.xml_tools import read_junit_totals
29from .common import resolve_name_label
32def badge(env, name: str = None, label: str = None) -> Artifact:
33 """Generate a badge for one test result.
35 Args:
36 env: Resolved CI run environment.
37 name: Basename of the badge file. If omitted, the run name is used.
38 label: Label of the badge. If omitted, the run name is used.
40 Returns:
41 Created and registered badge artifact.
42 """
43 name, label = resolve_name_label(env, name, label)
45 value = f"{env.data.get('succeeded', 0)} / {env.data.get('tests', 0)}"
47 color = "green"
48 if env.data.get("failures", 0) or env.data.get("errors", 0):
49 color = "red"
50 elif env.data.get("skipped"):
51 color = "yellow"
53 return write_badge(env=env, name=name, label=label, value=value, color=color)
56def write_test_report(env, name: str = None, report_title: str = None) -> Artifact:
57 """Create an RST file for a single test result page.
59 Args:
60 env: Resolved CI run environment.
61 name: Output file basename without extension. If omitted, the run name
62 is used.
63 report_title: Report title. If omitted, ``Test Report`` is used.
65 Returns:
66 Created and registered RST artifact.
67 """
68 if name is None: 68 ↛ 70line 68 didn't jump to line 70 because the condition on line 68 was always true
69 name = env.name
70 if report_title is None: 70 ↛ 73line 70 didn't jump to line 73 because the condition on line 70 was always true
71 report_title = "Test Report"
73 document = RstDocument(report_title)
74 document.add_block(timeline_artifacts_block(env.get_run_artifacts(ArtifactType.TIMELINE)))
76 summary = RstBlock("Summary")
77 summary.extend([
78 f"- Tests: {env.data.get('tests')}",
79 f"- Succeeded: {env.data.get('succeeded')}",
80 f"- Failures: {env.data.get('failures')}",
81 f"- Errors: {env.data.get('errors')}",
82 f"- Skipped: {env.data.get('skipped')}",
83 f"- Exit code: {env.data.get('exit_code')}",
84 ])
85 document.add_block(summary)
87 with TemporaryDirectory() as temp_dir:
88 output_file = Path(temp_dir) / f"{name}.rst"
89 write_file(output_file, document.text())
90 return env.copy_artifact(output_file, ArtifactType.RST)
93def write_build_report(env, name: str = None, report_title: str = None) -> Artifact:
94 """Create an RST file for a build result page.
96 The report contains available timeline charts, build metrics, compiler
97 metadata, memory usage, direct build artifacts, bundles, and command logs
98 of the current run.
100 Args:
101 env: Resolved CI run environment.
102 name: Output file basename without extension. If omitted, the run name
103 is used.
104 report_title: Report title. If omitted, ``Build Report`` is used.
106 Returns:
107 Created and registered RST artifact.
108 """
109 if name is None:
110 name = env.name
111 if report_title is None:
112 report_title = "Build Report"
114 document = RstDocument(report_title)
115 document.add_block(timeline_artifacts_block(env.get_run_artifacts(ArtifactType.TIMELINE)))
117 summary = RstBlock("Summary")
119 summary_keys = {
120 "cmake_version": ("CMake version", "%s"),
121 "compiler": ("Compiler", "%s"),
122 "compiler_version": ("Compiler version", "%s"),
123 "compiler_type": ("Compiler type", "%s"),
124 "optimization": ("Optimization", "%s"),
125 "lto": ("LTO", "%s"),
126 "build_files": ("Compiled files", "%s"),
127 "warnings": ("Warnings", "%s"),
128 "errors": ("Errors", "%s"),
129 "configure_time": ("Configure time", "%s s"),
130 "build_time": ("Build time", "%s s"),
131 "total_build_time": ("Total build time", "%s s"),
132 }
134 summary.extend([".. list-table::", " :widths: 35 65", ""])
136 for key, (label, value_format) in summary_keys.items():
137 if key in env.data:
138 summary.extend([f" * - {label}", f" - {value_format % env.data[key]}"])
140 document.add_block(summary)
142 document.add_block(memory_usage_block(env.data))
143 document.add_block(artifact_links_block(env.get_run_artifacts(ArtifactType.BUILD),
144 title="Build Artifacts"))
145 document.add_block(artifact_links_block(env.get_run_artifacts(ArtifactType.BUNDLE),
146 title="Bundles"))
147 document.add_block(log_artifacts_block(env.get_run_artifacts(ArtifactType.LOG)))
149 with TemporaryDirectory() as temp_dir:
150 output_file = Path(temp_dir) / f"{name}.rst"
151 write_file(output_file, document.text())
152 return env.copy_artifact(output_file, ArtifactType.RST)
155def process_test_result(env, test_alias: str, report_file: Path, returncode: int):
156 """Process and persist a JUnit-compatible test result.
158 The JUnit report is parsed and registered as an XUnit artifact. Extracted
159 test metrics and the process exit code are stored in the current run data.
160 Failed test execution is reported only after all available result data has
161 been persisted.
163 Args:
164 env: Resolved CI run environment.
165 test_alias: Logical test name used for result data.
166 report_file: Generated JUnit XML report.
167 returncode: Exit code returned by the test process.
169 Raises:
170 CiRecoverableError: If the test process returned a non-zero exit code.
171 """
172 result = read_junit_totals(test_alias, report_file)
173 result["exit_code"] = returncode
175 env.copy_artifact(report_file, ArtifactType.XUNIT)
177 env.data_update(result)
179 if returncode:
180 raise CiRecoverableError(f"Test has finished with errors (exit code {returncode})",
181 exit_code=returncode)