ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
suppression_stranded_selftest.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Both-direction fixtures for the stranded coverage-marker detector.
4
5A `GCOVR_EXCL_BR_LINE` excludes only the branch on its own physical line, and
6gcov attributes a decision to the line where the controlling expression starts.
7So a marker pushed onto a continuation line by clang-format still reads as
8correct in review while excluding nothing. These fixtures are what stop that
9from recurring silently, which makes their own both-direction coverage
10load-bearing rather than decorative.
11
12Split out of `suppression_selftest.py` when that module reached the repository
131000-line file cap.
14"""
15
16from __future__ import annotations
17
18from selftest_assert import expect
19from suppression_inline_scan import stranded_branch_findings
20
21
22def assert_stranded_branch_markers(failures: list[str]) -> None:
23 """Assert a wrapped branch marker fires and an attached one stays quiet.
24
25 Both directions plus the block-comment case: the backward walk must not
26 mistake the interior of a multi-line ``/* ... */`` for an unfinished
27 statement, which is how a correct marker would be reported as stranded.
28 """
29 wrapped = (
30 "void f(void)\n"
31 "{\n"
32 " ra8_err_t e = g();\n"
33 " RA8_RETURN_ON_ERROR(e,\n"
34 " tag,\n"
35 ' "msg"); /* GCOVR_EXCL_BR_LINE -- hardware only */\n'
36 "}\n"
37 )
38 attached = (
39 "void f(void)\n"
40 "{\n"
41 " ra8_err_t e = g();\n"
42 ' RA8_RETURN_ON_ERROR(e, tag, "msg"); /* GCOVR_EXCL_BR_LINE -- hardware only */\n'
43 "}\n"
44 )
45 after_comment = (
46 "void f(void)\n"
47 "{\n"
48 " /* a note that runs\n"
49 " * across several lines */\n"
50 " if (x) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
51 " return;\n"
52 " }\n"
53 "}\n"
54 )
55 expect(
56 len(stranded_branch_findings("sample.c", wrapped, frozenset())) == 1,
57 "must fire: a branch marker stranded on a wrapped call excludes nothing",
58 failures,
59 )
60 expect(
61 not stranded_branch_findings("sample.c", attached, frozenset()),
62 "must stay quiet: a branch marker on its own statement line",
63 failures,
64 )
65 expect(
66 not stranded_branch_findings("sample.c", after_comment, frozenset({3, 4})),
67 "must stay quiet: a marker following a multi-line block comment",
68 failures,
69 )
70 assert_stranded_wrapping_shapes(failures)
71
72
73#: One fixture per way a C statement can wrap, with the verdict the detector
74#: must reach. ``True`` means the marker is stranded and the scan MUST fire;
75#: ``False`` means the marker is attached and the scan MUST stay quiet. Every
76#: wrapping shape seen in the tree is represented, because a shape with no
77#: fixture is a shape the detector is free to stop recognizing (#790).
78_WRAPPING_FIXTURES: tuple[tuple[str, bool, str], ...] = (
79 (
80 "wrapped-for-header",
81 True,
82 "void f(void)\n{\n"
83 " for (uint32_t i = 0U; i < limit;\n"
84 " i++) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
85 " return;\n }\n}\n",
86 ),
87 (
88 "attached-for-header",
89 False,
90 "void f(void)\n{\n"
91 " for (uint32_t i = 0U; i < limit; i++) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
92 " return;\n }\n}\n",
93 ),
94 (
95 "wrapped-if-condition",
96 True,
97 "void f(void)\n{\n"
98 " if ((a != 0) &&\n"
99 " (b != 0)) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
100 " return;\n }\n}\n",
101 ),
102 (
103 "attached-if-condition",
104 False,
105 "void f(void)\n{\n"
106 " if ((a != 0) && (b != 0)) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
107 " return;\n }\n}\n",
108 ),
109 (
110 "split-ternary",
111 True,
112 "void f(void)\n{\n"
113 " const uint32_t d = (ms > cap)\n"
114 " ? cap\n"
115 " : (uint32_t)ms; /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
116 "}\n",
117 ),
118 (
119 "attached-ternary",
120 False,
121 "void f(void)\n{\n"
122 " const uint32_t d = (ms > cap) ? cap : ms; /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
123 "}\n",
124 ),
125 (
126 "split-initializer-expression",
127 True,
128 "static const cfg_t c = {\n"
129 " .b = (x != 0)\n"
130 " ? 1\n"
131 " : 0, /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
132 "};\n",
133 ),
134 (
135 "attached-initializer-expression",
136 False,
137 "static const cfg_t c = {\n"
138 " .b = (x != 0) ? 1 : 0, /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
139 "};\n",
140 ),
141 (
142 "wrapped-macro-invocation",
143 True,
144 "void f(void)\n{\n"
145 " RA8_RETURN_ON_ERROR(e,\n"
146 " tag,\n"
147 ' "msg"); /* GCOVR_EXCL_BR_LINE -- hardware only */\n'
148 "}\n",
149 ),
150 (
151 "attached-macro-invocation",
152 False,
153 "void f(void)\n{\n"
154 ' RA8_RETURN_ON_ERROR(e, tag, "msg"); /* GCOVR_EXCL_BR_LINE -- hardware only */\n'
155 "}\n",
156 ),
157 (
158 "after-preprocessor-directive",
159 False,
160 "void f(void)\n{\n"
161 "#endif\n"
162 " if (x) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
163 " return;\n }\n}\n",
164 ),
165 (
166 "paren-in-string-literal",
167 False,
168 'void f(void)\n{\n log(tag, "a) b"); /* GCOVR_EXCL_BR_LINE -- hardware only */\n}\n',
169 ),
170)
171
172
173def assert_stranded_wrapping_shapes(failures: list[str]) -> None:
174 """Drive one fixture per wrapping shape through the production scan.
175
176 Args:
177 failures: Accumulator every ``expect`` appends its message to.
178 """
179 for name, must_fire, text in _WRAPPING_FIXTURES:
180 found = stranded_branch_findings("sample.c", text, frozenset())
181 if must_fire:
182 expect(
183 len(found) == 1,
184 f"must fire: {name} strands its branch marker",
185 failures,
186 )
187 else:
188 expect(
189 not found,
190 f"must stay quiet: {name} keeps its branch marker attached",
191 failures,
192 )
193 # A multi-line block comment between the statement and the marker must not
194 # be read as an unfinished statement -- the interior lines are prose.
195 across_comment = (
196 "void f(void)\n"
197 "{\n"
198 " g();\n"
199 " /* a note that runs\n"
200 " * across several lines */\n"
201 " if (x) { /* GCOVR_EXCL_BR_LINE -- hardware only */\n"
202 " return;\n"
203 " }\n"
204 "}\n"
205 )
206 expect(
207 not stranded_branch_findings("sample.c", across_comment, frozenset({4, 5})),
208 "must stay quiet: a marker separated from its statement by block-comment prose",
209 failures,
210 )
211
212
213def assert_stranded_line_markers(failures: list[str]) -> None:
214 """Assert a detached line marker fires and an attached one stays quiet.
215
216 ``GCOVR_EXCL_LINE`` excludes only the physical line it sits on, so a
217 marker pushed onto a comment-only line excludes a line gcov never
218 counted, while the statement it was written for stays in the measured
219 debt. That failure is invisible in review: the comment still sits
220 beside the code it describes.
221
222 Args:
223 failures: Accumulator every ``expect`` appends its message to.
224 """
225 orphaned_line = (
226 "void f(void)\n"
227 "{\n"
228 " return;\n"
229 " /* explanation */ /* GCOVR_EXCL_LINE -- host cannot reach this */\n"
230 "}\n"
231 )
232 attached_line = (
233 "void f(void)\n{\n return; /* GCOVR_EXCL_LINE -- host cannot reach this */\n}\n"
234 )
235 expect(
236 len(stranded_branch_findings("sample.c", orphaned_line, frozenset())) == 1,
237 "must fire: a line marker on a comment-only line excludes no counted line",
238 failures,
239 )
240 expect(
241 not stranded_branch_findings("sample.c", attached_line, frozenset()),
242 "must stay quiet: a line marker on the statement it excludes",
243 failures,
244 )