ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
suppression_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 suppression inventory scanner."""
4
5from __future__ import annotations
6
7import tempfile
8from pathlib import Path
9
10from selftest_assert import expect, report
11from suppression_build_controls import compiler_records
12from suppression_catalog import REQUIRED_FAMILIES
13from suppression_compiler_selftest import (
14 assert_clang_tidy_config_fail_closed,
15 assert_compiler_controls,
16 assert_compiler_tool_probes,
17)
18from suppression_debt_selftest import assert_baseline_ceiling_controls
19from suppression_governance_selftest import assert_governance_parsers
20from suppression_identity_selftest import assert_identity_semantics
21from suppression_ledger_selftest import assert_ledger_gate
22from suppression_mcdc_selftest import assert_mcdc_macro_binding
23from suppression_model import Inventory, Suppression
24from suppression_scan import decode_git_paths, scan_paths
25from suppression_selftest_fixtures import FIXTURES
26from suppression_selftest_integrity import assert_identity_and_structure
27from suppression_stranded_selftest import (
28 assert_stranded_branch_markers,
29 assert_stranded_line_markers,
30)
31from suppression_tool_selftest import assert_tool_control_syntax_awareness
32
33EXPECTED_CLANG_ROWS = 14
34EXPECTED_BROAD_NOLINT_ROWS = 3
35EXPECTED_OPTIONAL_INACTIVE = 2
36EXPECTED_CENTRAL_ROWS = 4
37EXPECTED_BUILD_ROWS = 37
38EXPECTED_PROJECT_ROWS = 3
39EXPECTED_PYTHON_REGION_ROWS = 2
40EXPECTED_RUFF_CONFIG_ROWS = 3
41EXPECTED_SHELLCHECK_ROWS = 11
42EXPECTED_SHELLCHECK_GLOBAL_ROWS = 4
43EXPECTED_ACTIVE_SHELL_STATUS_ROWS = 4
44EXPECTED_EMBEDDED_SHELL_STATUS_ROWS = 2
45EXPECTED_JUST_SHELL_STATUS_ROWS = 2
46EXPECTED_YAML_SHELL_STATUS_ROWS = 5
47EXPECTED_ANSIBLE_CONTROL_ROWS = 4
48EXPECTED_CTEST_CONTROL_ROWS = 3
49EXPECTED_PYTHON_CONTROL_ROWS = 13
50EXPECTED_WORKFLOW_CONTROL_ROWS = 3
51EXPECTED_MALFORMED_NATIVE_SKIP_ROWS = 2
52EXPECTED_BASELINE_ROWS = 4
53EXPECTED_MCDC_ROWS = 7
54EXPECTED_NATIVE_SKIP_ROWS = 3
55EXPECTED_COVERAGE_MASK_ROWS = 3
56EXPECTED_YAML_BUILD_ROWS = 8
57EXPECTED_CMAKE_WNO_DEV_ROWS = 8
58EXPECTED_COMPILER_WNO_DEV_ROWS = 3
59SHELL_ARGUMENT_TOOL_LINE = 6
60EXPECTED_DEAD_ANCHOR_FINDINGS = 2
61
62
63def _write_fixture(root: Path) -> list[str]:
64 """Write the deterministic ASCII fixture and return its relative paths."""
65 for rel, text in FIXTURES.items():
66 target = root / rel
67 target.parent.mkdir(parents=True, exist_ok=True)
68 target.write_text(text, encoding="ascii")
69 return sorted(FIXTURES)
70
71
72def _assert_families(inventory: Inventory, failures: list[str]) -> None:
73 """Assert every phase-one recognizer fires through the production scanner."""
74 seen = {item.family for item in inventory.suppressions}
75 repository_bound = {
76 "ansible-lint-config",
77 "checker-nonfatal-control",
78 "checker-scope-control",
79 "ci-parity-exemption",
80 "documentation-control",
81 "generated-artifact",
82 "other-language-control",
83 "security-analysis-control",
84 }
85 for family in sorted(REQUIRED_FAMILIES - repository_bound):
86 expect(family in seen, f"must fire: {family}", failures)
87
88
89def _assert_python_syntax_awareness(inventory: Inventory, failures: list[str]) -> None:
90 """Assert Python controls fire only in active comments and carry reasons."""
91 python_rows = [item for item in inventory.suppressions if item.path == "sample.py"]
92 python_rules = {item.rule for item in python_rows}
93 expect(
94 {"S603", "S607"} <= python_rules,
95 "must fire: comma-separated noqa rules become separate rows",
96 failures,
97 )
98 tools = {item.tool for item in python_rows}
99 expect(
100 {"bandit", "coverage.py", "mypy", "pylint", "pyright", "ruff-format"} <= tools,
101 "must fire: every Python analyzer, formatter, and coverage control",
102 failures,
103 )
104 expect(
105 not any("blank-reason" in item.concerns for item in python_rows),
106 "quiet: every supported Python fixture carries a local reason",
107 failures,
108 )
109 bare_rows = [item for item in inventory.suppressions if item.path == "bare_python_controls.py"]
110 expect(
111 all("blank-reason" in item.concerns for item in bare_rows),
112 "must fire: bare Python controls retain blank-reason concerns",
113 failures,
114 )
115 expect(
116 len([item for item in python_rows if item.tool == "coverage.py"]) == 1
117 and len([item for item in python_rows if item.tool == "pylint"])
118 == EXPECTED_PYTHON_REGION_ROWS
119 and len([item for item in python_rows if item.tool == "ruff-format"])
120 == EXPECTED_PYTHON_REGION_ROWS,
121 "quiet: directive-looking Python strings do not add inventory rows",
122 failures,
123 )
124
125
126def _assert_control_syntax_awareness(inventory: Inventory, failures: list[str]) -> None:
127 """Assert test and infrastructure controls use active language syntax."""
128 python_rows = [item for item in inventory.suppressions if item.path == "controls.py"]
129 expect(
130 len(python_rows) == EXPECTED_PYTHON_CONTROL_ROWS,
131 "must fire: Python skip and strict-xfail calls/decorators",
132 failures,
133 )
134 expect(
135 len([item for item in python_rows if "non-strict-xfail" in item.concerns]) == 1,
136 "must fire: permissive marker xfail is distinct from strict/runtime xfail",
137 failures,
138 )
139 ctest_rows = [item for item in inventory.suppressions if item.family == "ctest"]
140 expect(
141 len(ctest_rows) == EXPECTED_CTEST_CONTROL_ROWS,
142 "must fire: active CTest fail, disabled, and skip properties",
143 failures,
144 )
145 expect(
146 not any("blank-reason" in item.concerns for item in ctest_rows),
147 "quiet: getters, strings, and comments are not CTest controls",
148 failures,
149 )
150 workflow_rows = [item for item in inventory.suppressions if item.family == "workflow"]
151 expect(
152 len(workflow_rows) == EXPECTED_WORKFLOW_CONTROL_ROWS,
153 "must fire: workflow soft failure and both missing-artifact modes",
154 failures,
155 )
156 ansible_rows = [item for item in inventory.suppressions if item.family == "ansible"]
157 expect(
158 len(ansible_rows) == EXPECTED_ANSIBLE_CONTROL_ROWS,
159 "must fire: every requested active Ansible result/log control",
160 failures,
161 )
162 expect(
163 not any("blank-reason" in item.concerns for item in workflow_rows + ansible_rows),
164 "quiet: YAML block payloads and explicit false controls stay inactive",
165 failures,
166 )
167
168
169def _assert_global_shellcheck_controls(
170 inventory: Inventory, shellcheck_rows: list[Suppression], failures: list[str]
171) -> None:
172 """Assert local and repository-wide ShellCheck controls are distinct."""
173 expect(
174 len(shellcheck_rows) == EXPECTED_SHELLCHECK_ROWS,
175 "quiet: quoted strings and heredoc payloads are not ShellCheck controls",
176 failures,
177 )
178 controls = {item.directive for item in shellcheck_rows}
179 expect(
180 {
181 "shellcheck enable",
182 "shellcheck external-sources",
183 "shellcheck shell",
184 "shellcheck source",
185 "shellcheck source-path",
186 }
187 <= controls,
188 "must fire: every supported ShellCheck analysis control",
189 failures,
190 )
191 global_rows = [
192 item
193 for item in inventory.suppressions
194 if item.directive in {"SHELLCHECK_OPTS exclude", ".shellcheckrc exclude"}
195 ]
196 expect(
197 len(global_rows) == EXPECTED_SHELLCHECK_GLOBAL_ROWS
198 and not any(item.concerns for item in global_rows),
199 "must fire: reasoned global ShellCheck exclusions, but not quoted lookalikes",
200 failures,
201 )
202
203
204def _assert_shell_syntax_awareness(inventory: Inventory, failures: list[str]) -> None:
205 """Assert shell controls fire only where their syntax is executable."""
206 shell_rows = [item for item in inventory.suppressions if item.path == "sample.sh"]
207 shellcheck_rows = [item for item in shell_rows if item.family == "shellcheck"]
208 _assert_global_shellcheck_controls(inventory, shellcheck_rows, failures)
209 status_rows = [item for item in shell_rows if item.family == "shell-status"]
210 active_status = [item for item in status_rows if item.scope == "command-list"]
211 embedded_status = [item for item in status_rows if item.scope == "embedded-shell-or-heredoc"]
212 expect(
213 len(active_status) == EXPECTED_ACTIVE_SHELL_STATUS_ROWS,
214 "must fire: direct and multiline masks while strings stay inactive",
215 failures,
216 )
217 expect(
218 len(embedded_status) == EXPECTED_EMBEDDED_SHELL_STATUS_ROWS,
219 "must fire: quoted and heredoc masks remain visible for embedded-shell review",
220 failures,
221 )
222 expect(
223 active_status[0].reason == "fixture cleanup status is intentionally ignored",
224 "must fire: active status mask carries its same-line rationale",
225 failures,
226 )
227 just_status = [
228 item
229 for item in inventory.suppressions
230 if item.path == "just/sample.just" and item.family == "shell-status"
231 ]
232 expect(
233 len(just_status) == EXPECTED_JUST_SHELL_STATUS_ROWS
234 and all(item.scope == "command-list" for item in just_status),
235 "must fire: Just recipe shell masks are inventoried as active syntax",
236 failures,
237 )
238
239
240def _assert_debt_control_families(inventory: Inventory, failures: list[str]) -> None:
241 """Assert the four newly-supported debt/control grammars in both directions."""
242 baseline_rows = [item for item in inventory.suppressions if item.family == "baseline-ratchet"]
243 expect(
244 len(baseline_rows) == EXPECTED_BASELINE_ROWS,
245 "must fire: exact baseline schemas become source-located rows",
246 failures,
247 )
248 codes = {item.code for item in inventory.findings}
249 expect(
250 {
251 "duplicate-baseline-row",
252 "stale-baseline-path",
253 "malformed-baseline-row",
254 "unknown-baseline-file",
255 "baseline-growth",
256 }
257 <= codes,
258 "must fire: duplicate, malformed, unknown, growing, and path-missing baseline debt",
259 failures,
260 )
261 mcdc_rows = [item for item in inventory.suppressions if item.family == "mcdc-deactivation"]
262 expect(
263 len(mcdc_rows) == EXPECTED_MCDC_ROWS
264 and any(item.scope == "function" for item in mcdc_rows),
265 "must fire: exact comment and literal macro MC/DC grammars",
266 failures,
267 )
268 expect(
269 any(item.scope.startswith("decision-line:") for item in mcdc_rows),
270 "must fire: a reasoned MC/DC marker pairs to one compound decision",
271 failures,
272 )
273 expect(
274 {
275 "malformed-mcdc-deactivation",
276 "duplicate-mcdc-deactivation",
277 "unpaired-mcdc-deactivation",
278 "unknown-mcdc-macro",
279 "malformed-mcdc-macro",
280 }
281 <= codes,
282 "must fire: malformed, duplicate, unpaired, and unknown MC/DC controls",
283 failures,
284 )
285 assert_mcdc_macro_binding(inventory, failures)
286 _assert_native_coverage_controls(inventory, failures)
287
288
289def _assert_native_coverage_controls(inventory: Inventory, failures: list[str]) -> None:
290 """Assert native-test skips and gcovr masks fire only on active syntax."""
291 native_rows = [item for item in inventory.suppressions if item.path == "native_test.cpp"]
292 expect(
293 len(native_rows) == EXPECTED_NATIVE_SKIP_ROWS
294 and len([item for item in native_rows if "blank-reason" in item.concerns]) == 1,
295 "must fire: GTest and both Unity skip forms while strings stay quiet",
296 failures,
297 )
298 malformed_skips = [
299 item for item in inventory.findings if item.code == "malformed-native-test-skip"
300 ]
301 expect(
302 len(malformed_skips) == EXPECTED_MALFORMED_NATIVE_SKIP_ROWS,
303 "must fire: malformed GTest and Unity skip calls",
304 failures,
305 )
306 coverage_rows = [item for item in inventory.suppressions if item.family == "coverage-mask"]
307 expect(
308 len(coverage_rows) == EXPECTED_COVERAGE_MASK_ROWS
309 and not any(item.concerns for item in coverage_rows),
310 "must fire: active shell and gcovr.cfg masks while inactive values stay quiet",
311 failures,
312 )
313 config_rules = {item.rule for item in coverage_rows if item.path == "config/gcovr.cfg"}
314 expect(
315 config_rules
316 == {"gcov-ignore-parse-errors=negative_hits.warn", "exclude-unreachable-branches"},
317 "quiet: false gcovr.cfg booleans do not become active masks",
318 failures,
319 )
320 codes = {item.code for item in inventory.findings}
321 expect(
322 {"duplicate-coverage-mask", "malformed-coverage-mask"} <= codes,
323 "must fire: duplicate and invalid gcovr.cfg controls fail closed",
324 failures,
325 )
326 expect(
327 any(
328 item.code == "malformed-coverage-mask" and item.path == "invalid/gcovr.cfg"
329 for item in inventory.findings
330 )
331 and any(
332 item.code == "duplicate-coverage-mask" and item.path == "config/gcovr.cfg"
333 for item in inventory.findings
334 ),
335 "must fire: gcovr.cfg invalid and duplicate branches are both exercised",
336 failures,
337 )
338
339
340def _assert_syntax_awareness(root: Path, inventory: Inventory, failures: list[str]) -> None:
341 """Assert directive-looking strings and prose stay outside the inventory."""
342 c_rows = [item for item in inventory.suppressions if item.path == "sample.c"]
343 clang_rows = [item for item in c_rows if item.family == "clang-tidy"]
344 expect(
345 len(clang_rows) == EXPECTED_CLANG_ROWS,
346 "must fire: clang-tidy raw-source NOLINT semantics are inventoried",
347 failures,
348 )
349 clang_rules = {item.rule for item in clang_rows}
350 expect(
351 {
352 "raw_token",
353 "raw_spliced_token",
354 "readability-magic-numbers",
355 "readability/fn_size",
356 }
357 <= clang_rules,
358 "must fire: strings, identifiers, splices, and live vendor NOLINT forms",
359 failures,
360 )
361 expect(
362 len([item for item in clang_rows if item.rule == "*"]) == EXPECTED_BROAD_NOLINT_ROWS,
363 "must fire: bare and bracket NOLINT forms follow clang-tidy semantics",
364 failures,
365 )
366 _assert_shell_syntax_awareness(inventory, failures)
367 prose_rows = [item for item in inventory.suppressions if item.path == "policy.md"]
368 expect(
369 len(prose_rows) == EXPECTED_PROJECT_ROWS,
370 "quiet: prose, fenced, and inline-code directive examples are ignored",
371 failures,
372 )
373 bare = [item for item in prose_rows if item.rule == "MAGIC-OK"]
374 expect(
375 bool(bare and "blank-reason" in bare[0].concerns),
376 "must fire: bare project marker has a blank reason",
377 failures,
378 )
379 _assert_python_syntax_awareness(inventory, failures)
380 cpplint = [item for item in c_rows if item.rule == "whitespace/line_length"]
381 expect(
382 bool(cpplint and cpplint[0].tool == "cpplint"),
383 "quiet: cpplint slash rule is classified without an unknown finding",
384 failures,
385 )
386 splice = [item for item in c_rows if item.rule == "readability-redundant-string-init"]
387 expect(bool(splice), "must fire: directive in a spliced C line comment", failures)
388 _assert_control_syntax_awareness(inventory, failures)
389 assert_tool_control_syntax_awareness(root, inventory, failures)
390
391
392def _assert_integrity_findings(inventory: Inventory, failures: list[str]) -> None:
393 """Assert unknown and duplicate syntax fail closed instead of disappearing."""
394 codes = [item.code for item in inventory.findings]
395 expect(
396 not any(item.code == "unsupported-category" for item in inventory.findings),
397 "quiet: every formerly unsupported class has a typed recognizer",
398 failures,
399 )
400 expect("unknown-directive" in codes, "must fire: unknown project marker", failures)
401 expect(
402 "unterminated-html-comment" in codes,
403 "must fire: unterminated multiline HTML comment",
404 failures,
405 )
406 expect("malformed-heredoc" in codes, "must fire: empty heredoc delimiter", failures)
407 expect("unterminated-heredoc" in codes, "must fire: unterminated heredoc body", failures)
408 expect(
409 "unterminated-shell-quote" in codes,
410 "must fire: unterminated multiline shell quote",
411 failures,
412 )
413 expect(
414 "unterminated-line-comment-splice" in codes,
415 "must fire: C line-comment splice reaching EOF",
416 failures,
417 )
418 malformed = [item for item in inventory.findings if item.path == "malformed.py"]
419 malformed_messages = {item.message for item in malformed}
420 expect(
421 "noqa: F401 E402" in malformed_messages,
422 "must fire: whitespace-separated multi-rule noqa",
423 failures,
424 )
425 for directive in (
426 "pragma: no cover because fixture",
427 "pylint: disable",
428 "mypy: disable-error-code",
429 "pyright: nonsense",
430 "bandit: skip=",
431 "fmt: sideways",
432 "ruff: noqa: E402 F401",
433 ):
434 expect(
435 directive in malformed_messages,
436 f"must fire: malformed Python control {directive}",
437 failures,
438 )
439 malformed_nolint = [item for item in inventory.findings if "NOLINT(foo,,bar)" in item.message]
440 expect(bool(malformed_nolint), "must fire: empty NOLINT rule ID", failures)
441 expect("duplicate-directive" in codes, "must fire: duplicate central waiver", failures)
442 malformed_cppcheck = [
443 item for item in inventory.findings if item.code == "malformed-cppcheck-list"
444 ]
445 expect(bool(malformed_cppcheck), "must fire: bogus cppcheck rule ID", failures)
446 broad = [item for item in inventory.suppressions if item.rule == "-w"]
447 expect(
448 bool(broad and "broad-rule" in broad[0].concerns),
449 "must fire: blanket compiler warning disable",
450 failures,
451 )
452
453
454def _assert_yaml_and_ruff_config(inventory: Inventory, failures: list[str]) -> None:
455 """Assert executable YAML blocks and central Ruff controls are distinct."""
456 yaml_rows = [item for item in inventory.suppressions if item.path == "sample.yml"]
457 yaml_shell_rows = [item for item in yaml_rows if item.family == "shell-status"]
458 expect(
459 len(yaml_shell_rows) == EXPECTED_YAML_SHELL_STATUS_ROWS
460 and all(item.provenance == "yaml-shell-block" for item in yaml_shell_rows),
461 "must fire: content, workflow run, and Ansible shell YAML payloads",
462 failures,
463 )
464 syntax_rows = [
465 item for item in yaml_rows if item.provenance not in {"yaml-shell-block", "build-config"}
466 ]
467 expect(
468 len(syntax_rows) == 1,
469 "quiet: non-executable YAML block payloads are not active syntax",
470 failures,
471 )
472 expect(
473 not any(item.path == "windows.yml" for item in inventory.findings),
474 "quiet: PowerShell workflow blocks do not emit Bash parser findings",
475 failures,
476 )
477 ruff_config = [
478 item
479 for item in inventory.suppressions
480 if item.provenance == "central-config" and item.tool == "ruff"
481 ]
482 expect(
483 len(ruff_config) == EXPECTED_RUFF_CONFIG_ROWS,
484 "must fire: every active Ruff global/per-file/path waiver is inventoried",
485 failures,
486 )
487 expect(
488 not any(item.concerns for item in ruff_config),
489 "quiet: Ruff central waivers have precise local reasons",
490 failures,
491 )
492
493
494def _assert_regions_and_config(inventory: Inventory, failures: list[str]) -> None:
495 """Assert region structure and repository-global waivers use the real model."""
496 region_findings = [item for item in inventory.findings if "region" in item.code]
497 expect(
498 not any(item.path == "sample.c" for item in region_findings),
499 "quiet: balanced regions are accepted",
500 failures,
501 )
502 expect(
503 any(item.path == "unmatched.c" for item in region_findings),
504 "must fire: unmatched region end",
505 failures,
506 )
507 unmatched_messages = " ".join(
508 item.message for item in region_findings if item.path == "unmatched.c"
509 )
510 expect(
511 "GCOVR_EXCL_BR" in unmatched_messages
512 and "LCOV_EXCL" in unmatched_messages
513 and "GCOVR_EXCL" in unmatched_messages,
514 "must fire: coverage tool and BR/non-BR region identities do not cross-close",
515 failures,
516 )
517 central = [item for item in inventory.suppressions if item.provenance == "central-list"]
518 expect(
519 len(central) == EXPECTED_CENTRAL_ROWS,
520 "must fire: global cppcheck config is inventoried",
521 failures,
522 )
523 reasons = {item.rule: item.reason for item in central}
524 expect(
525 "paired divider rationale" in reasons["unusedFunction"],
526 "quiet: paired cppcheck section rationale is frozen",
527 failures,
528 )
529 expect(
530 "local rationale A" in reasons["unreadVariable"],
531 "quiet: adjacent local rationale replaces grouped rationale",
532 failures,
533 )
534 expect(
535 "local rationale B" in reasons["unknownMacro"],
536 "quiet: second adjacent local rationale does not accumulate",
537 failures,
538 )
539 _assert_yaml_and_ruff_config(inventory, failures)
540 _assert_build_config(inventory, failures)
541
542
543def _assert_build_config(inventory: Inventory, failures: list[str]) -> None:
544 """Dispatch the unchanged build-control assertion groups."""
545 build = [item for item in inventory.suppressions if item.provenance == "build-config"]
546 expect(
547 len(build) == EXPECTED_BUILD_ROWS,
548 "must fire: build-config warning controls are inventoried",
549 failures,
550 )
551 _assert_yaml_build_config(inventory, failures)
552 _assert_shell_path_build_config(build, failures)
553 _assert_cmake_family_build_config(build, failures)
554 _assert_cmake_dev_build_config(build, failures)
555 _assert_build_reason_capture(failures)
556 _assert_yaml_build_reason_capture(failures)
557
558
559def _assert_build_reason_capture(failures: list[str]) -> None:
560 """Assert warning controls retain only locally attached rationales."""
561 source = """# Suppression rationale: pinned compiler emits a false positive.
562target_compile_options(
563 sample PRIVATE
564 -Wno-shadow # ABI callback parameter cannot change
565 -Wno-cast-align
566)
567# Unrelated prose must not approve a control.
568target_compile_options(other PRIVATE -Wno-unused-parameter)
569# Suppression rationale:
570target_compile_options(empty PRIVATE -Wno-padded)
571"""
572 records = compiler_records("CMakeLists.txt", source)
573 reasons = {item.rule: item.reason for item in records}
574 expect(
575 reasons.get("-Wno-shadow") == "ABI callback parameter cannot change"
576 and reasons.get("-Wno-cast-align") == "pinned compiler emits a false positive.",
577 "quiet: inline and command-level compiler reasons are retained",
578 failures,
579 )
580 expect(
581 not reasons.get("-Wno-unused-parameter") and not reasons.get("-Wno-padded"),
582 "must fire: unrelated or empty comments do not become reasons",
583 failures,
584 )
585 missing = {item.rule for item in records if "blank-reason" in item.concerns}
586 expect(
587 {"-Wno-unused-parameter", "-Wno-padded"} <= missing,
588 "must fire: reasonless build controls retain blank-reason concerns",
589 failures,
590 )
591
592
593def _assert_yaml_build_reason_capture(failures: list[str]) -> None:
594 """Assert YAML command rationales bind locally in both block styles."""
595 source = """steps:
596 - run: |
597 # Suppression rationale: pinned YAML compiler wrapper.
598 clang -Wno-shadow sample.c
599 clang -Wno-padded sample.c # generated ABI contract
600 # unrelated prose
601 clang -Wno-conversion sample.c
602 - run: >
603 # Suppression rationale: folded command uses the pinned wrapper.
604
605 clang -Wno-cast-align sample.c
606"""
607 records = compiler_records("workflow.yml", source)
608 reasons = {item.rule: item.reason for item in records}
609 expect(
610 reasons.get("-Wno-shadow") == "pinned YAML compiler wrapper."
611 and reasons.get("-Wno-padded") == "generated ABI contract"
612 and reasons.get("-Wno-cast-align") == "folded command uses the pinned wrapper.",
613 "quiet: literal and folded YAML compiler reasons are retained",
614 failures,
615 )
616 conversion = next(
617 (item for item in records if item.rule == "-Wno-conversion"),
618 None,
619 )
620 expect(
621 conversion is not None and not conversion.reason and "blank-reason" in conversion.concerns,
622 "must fire: unrelated YAML comments do not become compiler reasons",
623 failures,
624 )
625
626
627def _assert_yaml_build_config(inventory: Inventory, failures: list[str]) -> None:
628 """Assert YAML command ownership and folded-line source mapping."""
629 yaml_rows = [item for item in inventory.suppressions if item.path == "sample.yml"]
630 expect(
631 len([item for item in yaml_rows if item.provenance == "build-config"])
632 == EXPECTED_YAML_BUILD_ROWS,
633 "must fire: active YAML cflags are inventoried",
634 failures,
635 )
636 yaml_folded = {
637 (item.line, item.column, item.family, item.rule)
638 for item in yaml_rows
639 if item.line in {14, 17}
640 }
641 expect(
642 yaml_folded
643 == {
644 (14, 7, "cmake", "-Wno-dev"),
645 (17, 7, "compiler", "-Wno-conversion"),
646 },
647 "must fire: folded YAML command flags retain exact source ownership",
648 failures,
649 )
650 expect(
651 len([item for item in yaml_rows if item.family == "python"]) == 1,
652 "quiet: YAML block-scalar payload is not active YAML syntax",
653 failures,
654 )
655 yaml_data_lines = {
656 item.line
657 for item in yaml_rows
658 if item.provenance == "build-config" and item.line in {3, 21, 28}
659 }
660 expect(
661 not yaml_data_lines,
662 "quiet: literal and folded YAML data blocks stay outside command syntax",
663 failures,
664 )
665
666
667def _assert_shell_path_build_config(build: list[Suppression], failures: list[str]) -> None:
668 """Assert absolute, quoted, and escaped executable path ownership."""
669 shell_build = [
670 item
671 for item in build
672 if item.path == "sample.sh" and item.line in {16, 17, 18, 19, 20, 21, 22, 23}
673 ]
674 expect(
675 {(item.line, item.family, item.rule) for item in shell_build}
676 == {
677 (17, "cmake", "-Wno-dev"),
678 (18, "compiler", "-Wno-sign-conversion"),
679 (20, "cmake", "-Wno-dev"),
680 (21, "compiler", "-Wno-float-conversion"),
681 (22, "compiler", "-Wno-padded"),
682 },
683 "quiet: CMake data stays quiet while quoted and nested absolute tools fire",
684 failures,
685 )
686 spaced_path_build = [
687 item
688 for item in build
689 if item.path == "sample.sh" and item.line in {24, 25, 26, 27, 28, 29, 30, 31, 32}
690 ]
691 expect(
692 {(item.line, item.family, item.rule) for item in spaced_path_build}
693 == {
694 (24, "compiler", "-Wno-error"),
695 (25, "cmake", "-Wno-dev"),
696 (26, "compiler", "-Wno-shadow"),
697 (27, "compiler", "-Wno-padded"),
698 (28, "compiler", "-Wno-switch"),
699 (29, "compiler", "-Wno-cast-align"),
700 },
701 "quiet: quoted/escaped path data stays quiet while executable words fire",
702 failures,
703 )
704
705
706def _assert_cmake_family_build_config(build: list[Suppression], failures: list[str]) -> None:
707 """Assert exact CMake diagnostic options retain CMake ownership."""
708 cmake_family_fixture_locations = {
709 ("sample.sh", 33),
710 ("sample.sh", 34),
711 ("sample.sh", 35),
712 ("sample.sh", 36),
713 ("sample.sh", 37),
714 ("sample.sh", 38),
715 ("sample.sh", 39),
716 ("sample.sh", 40),
717 ("CMakeLists.txt", 6),
718 ("CMakeLists.txt", 7),
719 ("CMakeLists.txt", 8),
720 }
721 cmake_family_rows = [
722 item for item in build if (item.path, item.line) in cmake_family_fixture_locations
723 ]
724 expect(
725 {(item.path, item.line, item.family, item.rule) for item in cmake_family_rows}
726 == {
727 ("sample.sh", 33, "cmake", "-Wno-deprecated"),
728 ("sample.sh", 34, "cmake", "-Wno-error=dev"),
729 ("sample.sh", 35, "cmake", "-Wno-error=deprecated"),
730 ("sample.sh", 36, "compiler", "-Wno-deprecated"),
731 ("sample.sh", 37, "compiler", "-Wno-deprecated"),
732 ("CMakeLists.txt", 6, "cmake", "-Wno-deprecated"),
733 ("CMakeLists.txt", 7, "compiler", "-Wno-deprecated"),
734 },
735 "quiet: CMake data stays quiet while exact diagnostic and compiler owners fire",
736 failures,
737 )
738 argument_tool_names = [
739 item for item in build if item.path == "sample.sh" and item.line == SHELL_ARGUMENT_TOOL_LINE
740 ]
741 expect(
742 not argument_tool_names,
743 "quiet: compiler-looking arguments are not executable command words",
744 failures,
745 )
746
747
748def _assert_cmake_dev_build_config(build: list[Suppression], failures: list[str]) -> None:
749 """Assert -Wno-dev ownership and shell blanket controls."""
750 cmake_controls = [item for item in build if item.rule == "-Wno-dev"]
751 expect(
752 len([item for item in cmake_controls if item.family == "cmake"])
753 == EXPECTED_CMAKE_WNO_DEV_ROWS,
754 "must fire: configure-mode CMake -Wno-dev has CMake ownership",
755 failures,
756 )
757 expect(
758 len([item for item in cmake_controls if item.family == "compiler"])
759 == EXPECTED_COMPILER_WNO_DEV_ROWS,
760 "must fire: compiler and target_compile_options -Wno-dev stay compiler-owned",
761 failures,
762 )
763 data_controls = [
764 item
765 for item in build
766 if item.path == "CMakeLists.txt" and item.line in {4, 5} and item.rule == "-Wno-dev"
767 ]
768 expect(
769 not data_controls,
770 "quiet: CMake data strings containing -Wno-dev are not controls",
771 failures,
772 )
773 shell_blanket = [item for item in build if item.path == "sample.sh" and item.rule == "-w"]
774 expect(
775 len(shell_blanket) == 1,
776 "quiet: shell test/fold -w stay quiet while the compiler option array fires",
777 failures,
778 )
779
780
781def _assert_nonvacuity(root: Path, failures: list[str]) -> None:
782 """Assert a collapsed scan reports vacuity rather than a clean result."""
783 tiny = scan_paths(root, ["sample.py"], enforce_floors=True)
784 codes = {item.code for item in tiny.findings}
785 expect("vacuous-files" in codes, "must fire: collapsed file census", failures)
786 expect("vacuous-inventory" in codes, "must fire: collapsed directive census", failures)
787 expect("missing-family" in codes, "must fire: collapsed family coverage", failures)
788 expect(
789 {"vacuous-family", "vacuous-baseline-files", "vacuous-baseline-rows"} <= codes,
790 "must fire: audited family and baseline census floors",
791 failures,
792 )
793 expect(
794 not any(
795 item.code == "missing-family" and item.message == "test-control"
796 for item in tiny.findings
797 ),
798 "quiet: the audited exact native-skip population is present",
799 failures,
800 )
801 injected = scan_paths(root, ["native_test.cpp"], enforce_floors=True)
802 expect(
803 any(item.code == "unexpected-family-count" for item in injected.findings),
804 "must fire: an injected native skip violates the audited exact family contract",
805 failures,
806 )
807
808
809def _assert_ruff_config_fail_closed(root: Path, failures: list[str]) -> None:
810 """Assert valid Ruff syntax outside the source locator cannot disappear."""
811 pyproject = root / "pyproject.toml"
812 text = pyproject.read_text(encoding="ascii")
813 pyproject.write_text(
814 text.replace('["SLF001"]', '["SLF001", "S603"]'),
815 encoding="ascii",
816 )
817 inventory = scan_paths(root, ["pyproject.toml"])
818 expect(
819 any(item.code == "malformed-ruff-config" for item in inventory.findings),
820 "must fire: unsupported-but-valid Ruff config shape fails closed",
821 failures,
822 )
823
824
825def _assert_tool_config_fail_closed(root: Path, failures: list[str]) -> None:
826 """Assert malformed central tool ignore syntax cannot disappear."""
827 config = root / ".hadolint.yaml"
828 text = config.read_text(encoding="ascii")
829 config.write_text(text.replace("- DL3008", "- not-a-rule"), encoding="ascii")
830 inventory = scan_paths(root, [".hadolint.yaml"])
831 expect(
832 any(item.code == "malformed-tool-config" for item in inventory.findings),
833 "must fire: malformed Hadolint central ignore fails closed",
834 failures,
835 )
836
837
838def _assert_optional_tool_activation(root: Path, failures: list[str]) -> None:
839 """Assert unused Prettier/markdownlint comments are not active waivers."""
840 inventory = scan_paths(root, ["docs/format_controls.md"])
841 optional = {item.tool for item in inventory.suppressions} & {"prettier", "markdownlint"}
842 inactive = [item for item in inventory.findings if item.code == "inactive-tool-control"]
843 expect(
844 not optional and len(inactive) >= EXPECTED_OPTIONAL_INACTIVE,
845 "quiet: optional formatter/linter controls require repository configuration",
846 failures,
847 )
848
849
850def _assert_path_safety(root: Path, inventory: Inventory, failures: list[str]) -> None:
851 """Assert invalid Git names and escaping symlinks fail closed."""
852 paths, findings = decode_git_paths(b"bad-\xff-name\0", root)
853 expect(not paths and bool(findings), "must fire: non-UTF-8 Git path", failures)
854 paths, findings = decode_git_paths(b"missing-or-broken\0", root)
855 expect(
856 paths == ["missing-or-broken"] and not findings,
857 "quiet: Git paths are preserved for fail-closed read validation",
858 failures,
859 )
860 unsafe = [item for item in inventory.findings if item.code == "unsafe-symlink"]
861 expect(bool(unsafe), "must fire: symlink escaping repository", failures)
862 broken = [
863 item
864 for item in inventory.findings
865 if item.code == "read-error" and item.path == "broken-link"
866 ]
867 expect(bool(broken), "must fire: broken symlink is not silently omitted", failures)
868 encoding = [item for item in inventory.findings if item.code == "invalid-text-encoding"]
869 expect(bool(encoding), "must fire: invalid UTF-8 authored text", failures)
870 vendor = [
871 item
872 for item in inventory.suppressions
873 if item.family == "encoding-exemption"
874 and item.owner == "vendor"
875 and item.scope.startswith("blob:sha256:")
876 ]
877 expect(
878 bool(vendor),
879 "quiet: vendored legacy encoding is inventoried as an exemption",
880 failures,
881 )
882
883
884def run_selftest() -> int:
885 """Drive production entry points through must-fire and must-stay-quiet cases."""
886 print("check_suppressions.py selftest:")
887 failures: list[str] = []
888 with tempfile.TemporaryDirectory(prefix="ra8-suppressions-") as temp:
889 base = Path(temp)
890 root = base / "repo"
891 root.mkdir()
892 paths = _write_fixture(root)
893 outside = base / "outside.txt"
894 outside.write_text("# noqa: F401 -- must not be followed\n", encoding="ascii")
895 (root / "outside-link").symlink_to(outside)
896 paths.append("outside-link")
897 (root / "broken-link").symlink_to(root / "does-not-exist")
898 paths.append("broken-link")
899 (root / "invalid.txt").write_bytes(b"authored-\xff-text\n")
900 paths.append("invalid.txt")
901 vendor = root / "libs/third_party/vendor/legacy.txt"
902 vendor.parent.mkdir(parents=True, exist_ok=True)
903 vendor.write_bytes(b"vendored-\xff-text\n")
904 paths.append("libs/third_party/vendor/legacy.txt")
905 inventory = scan_paths(root, paths)
906 assert_identity_and_structure(root, inventory, failures, EXPECTED_DEAD_ANCHOR_FINDINGS)
907 _assert_families(inventory, failures)
908 _assert_syntax_awareness(root, inventory, failures)
909 assert_stranded_branch_markers(failures)
910 assert_stranded_line_markers(failures)
911 _assert_debt_control_families(inventory, failures)
912 assert_compiler_controls(inventory, root, failures)
913 assert_compiler_tool_probes(root, failures)
914 assert_clang_tidy_config_fail_closed(root, failures)
915 _assert_integrity_findings(inventory, failures)
916 _assert_regions_and_config(inventory, failures)
917 _assert_ruff_config_fail_closed(root, failures)
918 _assert_tool_config_fail_closed(root, failures)
919 _assert_optional_tool_activation(root, failures)
920 _assert_nonvacuity(root, failures)
921 _assert_path_safety(root, inventory, failures)
922 assert_governance_parsers(root, failures)
923 assert_baseline_ceiling_controls(base, failures)
924 assert_identity_semantics(base, failures)
925 assert_ledger_gate(base, failures)
926 return report(failures)