ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ci_parity_scan_selftest.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""End-to-end workflow scan fixtures for ``check_ci_parity.py``.
4
5The production checker owns the policy. This companion owns the temporary
6workflow trees that prove the complete scan fires in both directions.
7"""
8
9from __future__ import annotations
10
11import tempfile
12from pathlib import Path
13
14import check_ci_parity as parity
15import yaml
16
17
18def _workflow_yaml(trigger_block: str, *, soft: str = "", job_if: str = "") -> str:
19 """Render a minimal one-gate workflow for the scan selftest."""
20 return (
21 "name: probe\n"
22 f"{trigger_block}"
23 "jobs:\n"
24 " probe:\n"
25 f"{job_if}"
26 " runs-on: ubuntu-latest\n"
27 " steps:\n"
28 " - name: probe gate\n"
29 f"{soft}"
30 " run: just quality::local::gate probe-gate\n"
31 )
32
33
34def _cases() -> list[tuple[str, str, str, bool]]:
35 """Return the automatic/manual reachability fixtures."""
36 push = "on:\n push:\n branches: [dev]\n"
37 dispatch = "on:\n workflow_dispatch:\n"
38 no_trigger = "# on:\n# push:\n"
39
40 return [
41 # (label, registry speed, workflow text, must_fire)
42 ("push-triggered gate", "fast", _workflow_yaml(push), False),
43 (
44 "triggers all commented out (hil-all's shape)",
45 "fast",
46 _workflow_yaml(no_trigger),
47 True,
48 ),
49 (
50 "dispatch-only workflow for a fast gate",
51 "fast",
52 _workflow_yaml(dispatch),
53 True,
54 ),
55 (
56 "dispatch-only workflow for a manual gate",
57 "manual",
58 _workflow_yaml(dispatch),
59 False,
60 ),
61 (
62 "continue-on-error step for a fast gate",
63 "fast",
64 _workflow_yaml(push, soft=" continue-on-error: true\n"),
65 True,
66 ),
67 (
68 "job disabled by `if: false`",
69 "fast",
70 _workflow_yaml(push, job_if=" if: false\n"),
71 True,
72 ),
73 ]
74
75
76def scan_selftest() -> int:
77 """Prove the real workflow scan and reachability checks both ways."""
78 failures = 0
79 registry_name = "probe-gate"
80 for label, speed, workflow_text, must_fire in _cases():
81 with tempfile.TemporaryDirectory() as tmp:
82 directory = Path(tmp)
83 (directory / "probe.yml").write_text(workflow_text, encoding="utf-8")
84 registry = {registry_name: speed}
85 errors, bindings = parity.check_workflows(registry, directory)
86 errors.extend(parity.reachability_errors(registry, bindings))
87 fired = bool(errors)
88 ok = fired == must_fire
89 failures += 0 if ok else 1
90 expectation = "must fire" if must_fire else "must stay quiet"
91 print(f" [{'ok' if ok else 'FAIL'}] scan: {label} ({expectation})")
92
93 # An unscheduled gate must still be caught by the scan-plus-main logic,
94 # and an empty workflow directory must never read as parity.
95 with tempfile.TemporaryDirectory() as tmp:
96 errors, _ = parity.check_workflows({registry_name: "fast"}, Path(tmp))
97 ok = bool(errors)
98 failures += 0 if ok else 1
99 print(f" [{'ok' if ok else 'FAIL'}] scan: an empty workflow directory is refused")
100
101 # The YAML 1.1 `on:` -> True quirk: if this regressed, every workflow would
102 # look trigger-less and the reachability rules would fire on everything.
103 parsed = yaml.safe_load("on:\n push:\n branches: [dev]\njobs: {}\n")
104 ok = parity.workflow_triggers(parsed) == {"push"}
105 failures += 0 if ok else 1
106 print(f" [{'ok' if ok else 'FAIL'}] scan: bare `on:` parses as a trigger map, not a bool key")
107
108 return failures