ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
misra_deviations_fixtures.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Deterministic fixtures for the MISRA deviation-register checker self-test."""
4
5from __future__ import annotations
6
7Case = tuple[str, str, str, str, int]
8
9
10def _fixture_baseline(rules: list[str]) -> tuple[str, list[tuple[str, str, int]]]:
11 """Build the synthetic baseline and return its parsed source rows."""
12 base_rows = [("a.c", rules[0], 3), ("b.c", rules[0], 1)]
13 base_rows += [(f"f{i}.c", rules[i - 1], i) for i in range(2, len(rules) + 1)]
14 total = sum(count for _file, _rule, count in base_rows)
15 baseline = "\n".join(
16 [
17 "# fixture baseline",
18 "# cppcheck: Cppcheck 9.9.9",
19 f"# total findings: {total}",
20 "# columns: file<TAB>rule<TAB>count",
21 *[f"{file}\t{rule}\t{count}" for file, rule, count in sorted(base_rows)],
22 "",
23 ]
24 )
25 return baseline, base_rows
26
27
28def _fixture_suppressions(rules: list[str]) -> str:
29 """Build the synthetic cppcheck suppression list."""
30 return "\n".join(
31 [
32 "# fixture suppressions",
33 "unusedStructMember:inc/*.h",
34 f"{rules[0]}:a.c:10",
35 f"{rules[0]}:a.c:20",
36 f"{rules[1]}:glob/*",
37 *[f"{rule}:filler{i}.c" for i, rule in enumerate(rules[2:12])],
38 "",
39 ]
40 )
41
42
43def _fixture_index_lines(
44 rules: list[str], base_rows: list[tuple[str, str, int]]
45) -> tuple[list[str], list[tuple[str, str]]]:
46 """Build the register index and machine-derived population lines."""
47 deviations = [(f"D-{i:03d}", rules[i - 1]) for i in range(1, 7)]
48 register_rows = [
49 f"| {deviation} | {rule} | Advisory | Fixture | Active | 2027-01-01 "
50 f"| {4 if rule == rules[0] else int(rule.split('-')[2].split('.')[0])} "
51 f"| {2 if rule == rules[0] else 1} |"
52 for deviation, rule in deviations
53 ]
54 residual_rules = rules[6:]
55 residual_findings = sum(int(rule.split("-")[2].split(".")[0]) for rule in residual_rules)
56 total = sum(count for _file, _rule, count in base_rows)
57 lines = [
58 "# Fixture register",
59 "",
60 "Fixture header (D-001..D-006 active).",
61 "",
62 "## Deviation index",
63 "",
64 "| ID | Rule | Category | Class | Status | MAR | Findings | Files |",
65 "|-------|------|----------|-------|--------|-----|---------:|------:|",
66 *register_rows,
67 "",
68 "## Derived population",
69 "",
70 f"Baseline: {total} findings across {len(base_rows)} file/rule rows (Cppcheck 9.9.9).",
71 "",
72 f"Residual (no deviation record): {len(residual_rules)} rules, "
73 f"{residual_findings} findings, {len(residual_rules)} rows.",
74 "",
75 ]
76 return lines, deviations
77
78
79def _fixture_detail_lines(rules: list[str], deviations: list[tuple[str, str]]) -> list[str]:
80 """Build ownership, excerpt, and deviation-section fixture lines."""
81 return [
82 f"- `{rules[0]}` (2 rows, 1 path): fixture family one.",
83 f"- `{rules[1]}` (1 row, 1 path): fixture family two.",
84 *[f"- `{rule}` (1 row, 1 path): fixture filler family." for rule in rules[2:12]],
85 "",
86 f"Highest-count files for {rules[0]} (top 2, derived):",
87 "",
88 "| File | Findings |",
89 "|------|---------:|",
90 "| `a.c` | 3 |",
91 "| `b.c` | 1 |",
92 "",
93 *[
94 f"## {deviation}: Rule {rule.removeprefix('misra-c2012-')} -- fixture\n"
95 for deviation, rule in deviations
96 ],
97 "Population note under the last section: 6 findings across 1 files.",
98 "",
99 ]
100
101
102def fixture_files() -> dict[str, str]:
103 """Build a mutually consistent register, baseline, and suppression fixture."""
104 rules = [f"misra-c2012-{i}.1" for i in range(1, 33)]
105 baseline, base_rows = _fixture_baseline(rules)
106 index_lines, deviations = _fixture_index_lines(rules, base_rows)
107 document = "\n".join(index_lines + _fixture_detail_lines(rules, deviations))
108 return {
109 "doc": document,
110 "baseline": baseline,
111 "suppressions": _fixture_suppressions(rules),
112 }
113
114
115def _register_cases(exit_drift: int, exit_malformed: int) -> list[Case]:
116 """Return index-row and heading mutation cases."""
117 return [
118 (
119 "stale register count fires",
120 "doc",
121 "2027-01-01 | 4 | 2 |",
122 "2027-01-01 | 5 | 2 |",
123 exit_drift,
124 ),
125 (
126 "missing register row fires",
127 "doc",
128 "| D-003 | misra-c2012-3.1 | Advisory | Fixture | Active | 2027-01-01 | 3 | 1 |\n",
129 "",
130 exit_drift,
131 ),
132 (
133 "extra register row fires",
134 "doc",
135 "## Derived population",
136 "| D-099 | misra-c2012-31.1 | Advisory | Fixture | Active | 2027-01-01 | 31 | 1 |\n"
137 "\n## Derived population",
138 exit_drift,
139 ),
140 (
141 "misshapen register row is malformed",
142 "doc",
143 "| D-002 | misra-c2012-2.1 | Advisory | Fixture | Active | 2027-01-01 | 2 | 1 |",
144 "| D-002 | misra-c2012-2.1 | 2 | 1 |",
145 exit_malformed,
146 ),
147 (
148 "heading rule mismatch fires",
149 "doc",
150 "## D-002: Rule 2.1 -- fixture",
151 "## D-002: Rule 2.2 -- fixture",
152 exit_drift,
153 ),
154 ]
155
156
157def _claim_cases(exit_drift: int, exit_malformed: int) -> list[Case]:
158 """Return derived-claim and suppression-ownership mutation cases."""
159 return [
160 ("missing provenance line is malformed", "doc", "Baseline: ", "Basel1ne: ", exit_malformed),
161 (
162 "stale residual fires",
163 "doc",
164 "Residual (no deviation record): 26 rules,",
165 "Residual (no deviation record): 27 rules,",
166 exit_drift,
167 ),
168 (
169 "suppression row drift fires",
170 "suppressions",
171 "misra-c2012-1.1:a.c:20",
172 "misra-c2012-1.1:a.c:20\nmisra-c2012-1.1:c.c:9",
173 exit_drift,
174 ),
175 (
176 "unowned suppression family fires",
177 "suppressions",
178 "misra-c2012-2.1:glob/*",
179 "misra-c2012-2.1:glob/*\nmisra-c2012-3.1:zzz.c",
180 exit_drift,
181 ),
182 (
183 "ghost ownership bullet fires",
184 "suppressions",
185 "misra-c2012-2.1:glob/*\n",
186 "",
187 exit_drift,
188 ),
189 ]
190
191
192def _evidence_cases(exit_drift: int, exit_malformed: int) -> list[Case]:
193 """Return baseline, excerpt, and population mutation cases."""
194 return [
195 (
196 "misordered excerpt fires",
197 "doc",
198 "| `a.c` | 3 |\n| `b.c` | 1 |",
199 "| `b.c` | 1 |\n| `a.c` | 3 |",
200 exit_drift,
201 ),
202 (
203 "tampered baseline header is malformed",
204 "baseline",
205 "# total findings: ",
206 "# total findings: 1",
207 exit_malformed,
208 ),
209 (
210 "vacuous baseline is malformed",
211 "baseline",
212 "a.c\tmisra-c2012-1.1\t3",
213 "a.c\tmisra-c2012-1.1\tX",
214 exit_malformed,
215 ),
216 (
217 "two-column baseline row is malformed",
218 "baseline",
219 "b.c\tmisra-c2012-1.1\t1",
220 "b.c\tmisra-c2012-1.1",
221 exit_malformed,
222 ),
223 (
224 "stale in-section population fires",
225 "doc",
226 "6 findings across 1 files.",
227 "7 findings across 1 files.",
228 exit_drift,
229 ),
230 ]
231
232
233def _shape_cases(exit_drift: int, exit_malformed: int) -> list[Case]:
234 """Return malformed-shape and deleted-evidence mutation cases."""
235 return [
236 (
237 "wrapped excerpt intro is malformed",
238 "doc",
239 "Highest-count files for misra-c2012-1.1 (top 2, derived):",
240 "Highest-count files for misra-c2012-1.1\n(top 2, derived):",
241 exit_malformed,
242 ),
243 (
244 "short deviation-id register row is malformed",
245 "doc",
246 "## Derived population",
247 "| D-11 | misra-c2012-31.1 | Advisory | Fixture | Active | 2027-01-01 | 31 | 1 |\n"
248 "\n## Derived population",
249 exit_malformed,
250 ),
251 (
252 "short deviation-id heading is malformed",
253 "doc",
254 "## D-002: Rule 2.1 -- fixture",
255 "## D-11: Rule 2.1 -- fixture",
256 exit_malformed,
257 ),
258 (
259 "deleting the whole excerpt block is malformed",
260 "doc",
261 "Highest-count files for misra-c2012-1.1 (top 2, derived):\n\n"
262 "| File | Findings |\n|------|---------:|\n| `a.c` | 3 |\n| `b.c` | 1 |\n",
263 "",
264 exit_malformed,
265 ),
266 (
267 "deleting one ownership bullet fires as an unowned waiver",
268 "doc",
269 "- `misra-c2012-1.1` (2 rows, 1 path): fixture family one.",
270 "",
271 exit_drift,
272 ),
273 ]
274
275
276def _retirement_cases(exit_drift: int, exit_malformed: int) -> list[Case]:
277 """Return population-placement and deviation-retirement mutation cases."""
278 return [
279 (
280 "population claim outside a deviation section is malformed",
281 "doc",
282 "## Derived population",
283 "Rule 1.1 alone accounts for 4 findings across 2 files.\n\n## Derived population",
284 exit_malformed,
285 ),
286 (
287 "population claim reworded with 'in' still fires",
288 "doc",
289 "6 findings across 1 files.",
290 "7 findings in 1 file.",
291 exit_drift,
292 ),
293 (
294 "retiring a deviation without amending the header range fires",
295 "doc",
296 "| D-006 | misra-c2012-6.1 | Advisory | Fixture | Active | 2027-01-01 | 6 | 1 |\n",
297 "",
298 exit_drift,
299 ),
300 (
301 "a register row naming a rule absent from the baseline fires",
302 "doc",
303 "| D-006 | misra-c2012-6.1 | Advisory | Fixture | Active | 2027-01-01 | 6 | 1 |",
304 "| D-006 | misra-c2012-99.9 | Advisory | Fixture | Active | 2027-01-01 | 0 | 0 |",
305 exit_drift,
306 ),
307 (
308 "a missing header range is malformed",
309 "doc",
310 "Fixture header (D-001..D-006 active).",
311 "Fixture header.",
312 exit_drift,
313 ),
314 ]
315
316
317def selftest_cases(exit_ok: int, exit_drift: int, exit_malformed: int) -> list[Case]:
318 """Enumerate all mutation cases and the quiet control."""
319 return [
320 ("clean fixture passes", "doc", "", "", exit_ok),
321 *_register_cases(exit_drift, exit_malformed),
322 *_claim_cases(exit_drift, exit_malformed),
323 *_evidence_cases(exit_drift, exit_malformed),
324 *_shape_cases(exit_drift, exit_malformed),
325 *_retirement_cases(exit_drift, exit_malformed),
326 ]