ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
hook_runtime_selftest.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Runtime fixtures for immutable hook ownership and candidate dispatch."""
5
6from __future__ import annotations
7
8import os
9import shutil
10import signal
11import subprocess
12import sys
13import tempfile
14import time
15from collections.abc import Callable
16from contextlib import suppress
17from dataclasses import dataclass
18from pathlib import Path
19
20sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
21
22from scripts.dev.git_environment import sanitized_git_environment, trusted_git_executable
23
24REPO_ROOT = Path(__file__).resolve().parents[2]
25INSTALLER = REPO_ROOT / "scripts/git/install-hooks.sh"
26LAUNCHER = REPO_ROOT / "scripts/git/hook-launcher"
27PRE_COMMIT = REPO_ROOT / "scripts/git/pre-commit"
28HOOKS_JUST = REPO_ROOT / "just/hooks.just"
29CI_SCRIPT = REPO_ROOT / "scripts/ci.sh"
30PROOF_WRITER = REPO_ROOT / "scripts/git/write-proof.py"
31HOOK_NAMES = (
32 "commit-msg",
33 "post-checkout",
34 "post-commit",
35 "post-merge",
36 "pre-commit",
37 "pre-push",
38)
39ABORTED = 3
40EXPECTED_GATES = (
41 "ascii",
42 "copyright",
43 "since",
44 "format",
45 "pre-commit-checks",
46 "shebangs",
47 "entry-points",
48 "annotations",
49 "doc-attachment",
50 "toolchain-parity",
51 "lint-py-shell",
52 "lint-go",
53 "lint-just",
54 "cite-check",
55 "hil-eil-parity",
56 "roadmap-stats",
57 "sbom",
58 "soup-upstream",
59 "tidy",
60 "cppcheck",
61)
62
63
64class RuntimeSelftestError(RuntimeError):
65 """One runtime hook invariant failed."""
66
67
68def _fail(message: str) -> None:
69 raise RuntimeSelftestError(message)
70
71
72def default_signal_test_command(*command: str) -> tuple[str, ...]:
73 """Wrap one test child so an asynchronous parent cannot mask its signals."""
74 if not command:
75 _fail("default-signal fixture command is empty")
76 program = """\
77import os
78import signal
79import sys
80for item in (signal.SIGHUP, signal.SIGINT, signal.SIGQUIT, signal.SIGTERM):
81 signal.signal(item, signal.SIG_DFL)
82os.execv(sys.argv[1], sys.argv[1:])
83"""
84 return (sys.executable, "-I", "-c", program, *command)
85
86
87@dataclass(frozen=True)
88class PrivateRun:
89 """One fully specified invocation of a private selftest script."""
90
91 command: tuple[str, ...]
92 cwd: Path
93 environment: dict[str, str]
94 pass_fds: tuple[int, ...] = ()
95 umask: int = -1
96 preexec_fn: Callable[[], None] | None = None
97
98
99def _run_private(spec: PrivateRun) -> subprocess.CompletedProcess[str]:
100 """Run one repository-owned or generated private fixture."""
101 return subprocess.run( # noqa: S603 -- private fixture command and paths
102 spec.command,
103 cwd=spec.cwd,
104 env=spec.environment,
105 pass_fds=spec.pass_fds,
106 umask=spec.umask,
107 preexec_fn=spec.preexec_fn,
108 capture_output=True,
109 text=True,
110 check=False,
111 timeout=15,
112 )
113
114
115def _git_result(
116 root: Path, *args: str, input_data: bytes | None = None
117) -> subprocess.CompletedProcess[bytes]:
118 """Run trusted Git in one private fixture without assuming its status."""
119 return subprocess.run( # noqa: S603 -- private fixture Git argv
120 [trusted_git_executable(), "-C", str(root), *args],
121 env=sanitized_git_environment(),
122 input=input_data,
123 capture_output=True,
124 check=False,
125 )
126
127
128def _git(root: Path, *args: str, input_data: bytes | None = None) -> bytes:
129 proc = _git_result(root, *args, input_data=input_data)
130 if proc.returncode:
131 _fail(proc.stderr.decode(errors="replace").strip())
132 return proc.stdout
133
134
135def _write(path: Path, text: str, *, executable: bool = False) -> None:
136 path.parent.mkdir(parents=True, exist_ok=True)
137 path.write_text(text, encoding="utf-8")
138 if executable:
139 path.chmod(0o755)
140
141
142def _init_repo(root: Path) -> None:
143 _git(root, "init", "--quiet")
144 _git(root, "config", "user.email", "selftest@invalid")
145 _git(root, "config", "user.name", "selftest")
146
147
148def _owner_text(hook: str, label: str = "") -> str:
149 return f"#!/usr/bin/env bash\nset -euo pipefail\nprintf '%s\\0' '{label}{hook}' \"$@\"\n"
150
151
152def _seed_launcher_repo(root: Path, *, include_launcher: bool = True) -> None:
153 _init_repo(root)
154 if include_launcher:
155 (root / "scripts/git").mkdir(parents=True, exist_ok=True)
156 shutil.copy2(LAUNCHER, root / "scripts/git/hook-launcher")
157 for hook in HOOK_NAMES:
158 _write(root / f"scripts/git/{hook}", _owner_text(hook), executable=True)
159 _git(root, "add", ".")
160 _git(root, "commit", "--quiet", "-m", "fixture")
161
162
163def _run_installer(
164 root: Path,
165 environment: dict[str, str] | None = None,
166 installer: Path = INSTALLER,
167) -> subprocess.CompletedProcess[str]:
168 return _run_private(
169 PrivateRun(
170 default_signal_test_command("/bin/bash", "-p", str(installer)),
171 root,
172 sanitized_git_environment() if environment is None else environment,
173 )
174 )
175
176
177def _managed_dir(root: Path) -> Path:
178 common = os.fsdecode(_git(root, "rev-parse", "--path-format=absolute", "--git-common-dir"))
179 return Path(common.strip()) / "ra8-hooks"
180
181
182def _run_launcher(
183 root: Path, hook: str, args: tuple[str, ...], environment: dict[str, str] | None = None
184) -> bytes:
185 proc = subprocess.run( # noqa: S603 -- private installed launcher
186 [str(_managed_dir(root) / hook), *args],
187 cwd=root,
188 env=sanitized_git_environment() if environment is None else environment,
189 capture_output=True,
190 check=False,
191 timeout=15,
192 )
193 if proc.returncode:
194 _fail(f"{hook} launcher returned {proc.returncode}: {proc.stderr!r}")
195 return proc.stdout
196
197
198def _assert_argv_forwarding(root: Path) -> None:
199 args = ("plain", "path with spaces", "line one\nline two")
200 for hook in HOOK_NAMES:
201 expected = b"\0".join(item.encode() for item in (hook, *args)) + b"\0"
202 actual = _run_launcher(root, hook, args)
203 if actual != expected:
204 _fail(f"{hook} launcher corrupted argv: {actual!r}")
205
206
207def _launcher_path_case(root: Path) -> None:
208 fake_bin = root / ".venv/bin"
209 git_marker = root / ".git/fake-git"
210 core_markers = tuple(
211 root / f".git/fake-{name}"
212 for name in ("chmod", "env", "ln", "mkdir", "mktemp", "readlink", "rm")
213 )
214 _write(
215 fake_bin / "git",
216 f"#!/bin/bash\nprintf 'ran\\n' >{git_marker!s}\nexit 99\n",
217 executable=True,
218 )
219 for marker in core_markers:
220 name = marker.name.removeprefix("fake-")
221 _write(
222 fake_bin / name,
223 f"#!/bin/bash -p\nprintf 'ran\\n' >{marker!s}\nexit 99\n",
224 executable=True,
225 )
226 environment = sanitized_git_environment()
227 environment["PATH"] = f"{fake_bin}:{environment.get('PATH', os.defpath)}"
228 actual = _run_launcher(root, "pre-commit", (), environment)
229 if actual != b"pre-commit\0":
230 _fail("launcher PATH hardening changed hook output")
231 if git_marker.exists() or any(marker.exists() for marker in core_markers):
232 _fail("launcher executed a source-tree Git or core-utility shim")
233
234
235def _installer_path_case(base: Path) -> None:
236 """Prove installation never resolves mutable core tools through PATH."""
237 root, fake_bin = base / "installer-path", base / "installer-path-bin"
238 marker_dir = base / "installer-path-markers"
239 root.mkdir()
240 fake_bin.mkdir()
241 marker_dir.mkdir()
242 _seed_launcher_repo(root)
243 for name in ("chmod", "cp", "grep", "mkdir", "mktemp", "mv", "rm", "rmdir"):
244 _write(
245 fake_bin / name,
246 f"#!/bin/bash -p\nprintf 'ran\\n' >{marker_dir!s}/${{0##*/}}\nexit 99\n",
247 executable=True,
248 )
249 environment = sanitized_git_environment()
250 environment["PATH"] = f"{fake_bin}:{environment.get('PATH', os.defpath)}"
251 result = _run_installer(root, environment)
252 if result.returncode:
253 _fail(f"installer PATH isolation failed: {result.stderr}")
254 if tuple(marker_dir.iterdir()):
255 _fail("installer executed an arbitrary-PATH core utility")
256
257
258def _launcher_immutability_case(base: Path) -> None:
259 root = base / "launcher"
260 linked = base / "linked worktree"
261 root.mkdir()
262 _seed_launcher_repo(root)
263 _git(root, "branch", "linked-branch")
264 _git(root, "worktree", "add", "--quiet", str(linked), "linked-branch")
265 _write(linked / "scripts/git/pre-commit", _owner_text("pre-commit", "linked:"), executable=True)
266 _git(linked, "add", "scripts/git/pre-commit")
267 _git(linked, "commit", "--quiet", "-m", "linked owner")
268 result = _run_installer(root)
269 if result.returncode:
270 _fail(f"launcher install failed: {result.stderr}")
271 managed = _managed_dir(root)
272 configured = os.fsdecode(_git(root, "config", "--local", "--get", "core.hooksPath")).strip()
273 if configured != str(managed) or _managed_dir(linked) != managed:
274 _fail("linked worktrees did not share the managed common-dir hook path")
275 _assert_argv_forwarding(root)
276 if not _run_launcher(linked, "pre-commit", ()).startswith(b"linked:pre-commit\0"):
277 _fail("shared launcher did not select the linked worktree HEAD")
278 _write(root / "scripts/git/pre-commit", _owner_text("pre-commit", "mutable:"), executable=True)
279 _launcher_path_case(root)
280 _git(root, "add", "scripts/git/pre-commit")
281 if _run_launcher(root, "pre-commit", ()).startswith(b"mutable:"):
282 _fail("staged worktree hook ran instead of immutable HEAD")
283 _write(root / "scripts/git/pre-commit", _owner_text("pre-commit"), executable=True)
284 _git(root, "add", "scripts/git/pre-commit")
285 _unmanaged_install_cases(root)
286
287
288def _unmanaged_install_cases(root: Path) -> None:
289 managed = _managed_dir(root)
290 unmanaged = root / "unmanaged-hooks"
291 _git(root, "config", "--local", "core.hooksPath", str(unmanaged))
292 result = _run_installer(root)
293 if result.returncode == 0:
294 _fail("installer replaced an unmanaged core.hooksPath")
295 current = os.fsdecode(_git(root, "config", "--local", "--get", "core.hooksPath")).strip()
296 if current != str(unmanaged):
297 _fail("failed unmanaged install changed core.hooksPath")
298 _git(root, "config", "--local", "core.hooksPath", str(managed))
299 intruder = managed / "unmanaged"
300 _write(intruder, "unmanaged\n")
301 result = _run_installer(root)
302 if result.returncode == 0:
303 _fail("installer replaced an unknown file in its managed directory")
304 intruder.unlink()
305 hidden = managed / ".pre-commit.new.interrupted"
306 _write(hidden, "interrupted\n")
307 result = _run_installer(root)
308 if result.returncode == 0:
309 _fail("installer ignored an interrupted hidden candidate")
310 hidden.unlink()
311
312
313def _same_commit_bootstrap_case(base: Path) -> None:
314 root = base / "bootstrap"
315 root.mkdir()
316 _seed_launcher_repo(root, include_launcher=False)
317 shutil.copy2(LAUNCHER, root / "scripts/git/hook-launcher")
318 result = _run_installer(root)
319 if result.returncode == 0 or "HEAD does not own" not in result.stderr:
320 _fail("same-commit launcher bootstrap did not fail closed before commit")
321 _git(root, "add", "scripts/git/hook-launcher")
322 _git(root, "commit", "--quiet", "-m", "add launcher")
323 result = _run_installer(root)
324 if result.returncode:
325 _fail(f"committed launcher did not install: {result.stderr}")
326
327
328def _wait_for_staging(common: Path, process: subprocess.Popen[str]) -> None:
329 """Stop only after the install transaction owns a populated staging dir."""
330 deadline = time.monotonic() + 10
331 while time.monotonic() < deadline:
332 if tuple(common.glob("ra8-hooks.stage.*")):
333 return
334 if process.poll() is not None:
335 _fail(f"installer exited before staging: {process.returncode}")
336 time.sleep(0.001)
337 _fail("installer did not expose its staging transaction")
338
339
340def _installer_transaction_case(base: Path) -> None:
341 root = base / "installer-transaction"
342 root.mkdir()
343 _seed_launcher_repo(root)
344 result = _run_installer(root)
345 if result.returncode:
346 _fail(f"initial transactional install failed: {result.stderr}")
347 launcher = root / "scripts/git/hook-launcher"
348 with launcher.open("ab") as stream:
349 stream.write(b"\n# padding keeps the transaction observable\n")
350 stream.write(os.urandom(8 * 1024 * 1024))
351 _git(root, "add", "scripts/git/hook-launcher")
352 _git(root, "commit", "--quiet", "-m", "large launcher transaction fixture")
353 environment = sanitized_git_environment()
354 common = _managed_dir(root).parent
355 process = subprocess.Popen( # noqa: S603 -- audited private-repo installer
356 default_signal_test_command("/bin/bash", "-p", str(INSTALLER)),
357 cwd=root,
358 env=environment,
359 stdout=subprocess.PIPE,
360 stderr=subprocess.PIPE,
361 text=True,
362 start_new_session=True,
363 )
364 try:
365 _wait_for_staging(common, process)
366 os.kill(process.pid, signal.SIGSTOP)
367 contender = _run_installer(root)
368 if contender.returncode == 0 or "another hook installer" not in contender.stderr:
369 _fail("concurrent installer did not fail on the common-dir lock")
370 os.kill(process.pid, signal.SIGCONT)
371 os.killpg(process.pid, signal.SIGTERM)
372 process.communicate(timeout=15)
373 finally:
374 if process.poll() is None:
375 with suppress(ProcessLookupError):
376 os.killpg(process.pid, signal.SIGKILL)
377 process.wait(timeout=5)
378 residue = tuple(common.glob("ra8-hooks.*"))
379 hooks = tuple(sorted(path.name for path in _managed_dir(root).iterdir()))
380 if residue or hooks != tuple(sorted(HOOK_NAMES)):
381 _fail("interrupted installer left residue or a partial hook generation")
382
383
384ManagedState = tuple[int, tuple[tuple[str, int, bytes], ...]] | None
385ConfigState = tuple[bool, str]
386
387
388def _managed_state(root: Path) -> ManagedState:
389 """Return the installed generation's exact names, modes, and bytes."""
390 managed = _managed_dir(root)
391 if not managed.exists():
392 return None
393 entries = tuple(
394 (path.name, path.stat().st_mode & 0o777, path.read_bytes())
395 for path in sorted(managed.iterdir())
396 )
397 return managed.stat().st_mode & 0o777, entries
398
399
400def _committed_state(root: Path) -> ManagedState:
401 """Return the only generation a successful transaction may install."""
402 launcher = _git(root, "show", "HEAD:scripts/git/hook-launcher")
403 return 0o700, tuple((name, 0o500, launcher) for name in HOOK_NAMES)
404
405
406def _hooks_config_state(root: Path) -> ConfigState:
407 """Distinguish an absent key from a present empty or nonempty value."""
408 result = _git_result(root, "config", "--local", "--get", "core.hooksPath")
409 if result.returncode == 0:
410 return True, result.stdout.decode(encoding="utf-8").removesuffix("\n")
411 if result.returncode == 1:
412 return False, ""
413 _fail(f"fatal hooksPath read in fixture: {result.stderr!r}")
414 return False, ""
415
416
417def _configure_fixture(root: Path, state: str) -> tuple[ConfigState, ManagedState]:
418 """Prepare one exact pre-transaction config/directory state."""
419 if state == "managed":
420 result = _run_installer(root)
421 if result.returncode:
422 _fail(f"managed fixture install failed: {result.stderr}")
423 elif state == "empty":
424 _git(root, "config", "--local", "core.hooksPath", "")
425 elif state == "legacy":
426 _git(root, "config", "--local", "core.hooksPath", "scripts/git")
427 elif state != "absent":
428 _fail(f"unknown hook config fixture: {state}")
429 return _hooks_config_state(root), _managed_state(root)
430
431
432def _signal_injected_installer(
433 base: Path,
434 label: str,
435 insertions: tuple[tuple[str, str], ...],
436) -> Path:
437 """Materialize the real installer with deterministic signal injections."""
438 text = INSTALLER.read_text(encoding="ascii")
439 for needle, injected in insertions:
440 if text.count(needle) != 1:
441 _fail(f"installer signal boundary {label} is not unique: {needle!r}")
442 text = text.replace(needle, f"{needle}\n{injected}", 1)
443 path = base / f"install-hooks-{label}.sh"
444 _write(path, text, executable=True)
445 return path
446
447
448def _assert_transaction_state(
449 root: Path,
450 label: str,
451 config: ConfigState,
452 generation: ManagedState,
453) -> None:
454 """Require exact configuration presence/value and launcher bytes/modes."""
455 if _hooks_config_state(root) != config:
456 _fail(f"{label}: transaction changed exact core.hooksPath state")
457 if _managed_state(root) != generation:
458 _fail(f"{label}: transaction produced incorrect launcher bytes or modes")
459
460
461@dataclass(frozen=True)
462class _BoundaryCase:
463 """One exact signal/configuration transaction interruption."""
464
465 label: str
466 boundary: str
467 committed: bool
468 config_state: str = "managed"
469 signal_name: str = "TERM"
470 second_cleanup_signal: bool = False
471
472
473def _installer_boundary_signal_case(base: Path, case: _BoundaryCase) -> None:
474 """Signal one exact transaction boundary and verify the resulting generation."""
475 root = base / f"installer-boundary-{case.label}"
476 root.mkdir()
477 _seed_launcher_repo(root)
478 original_config, original_state = _configure_fixture(root, case.config_state)
479 launcher = root / "scripts/git/hook-launcher"
480 with launcher.open("a", encoding="ascii") as stream:
481 stream.write("\n# boundary signal generation\n")
482 _git(root, "add", "scripts/git/hook-launcher")
483 _git(root, "commit", "--quiet", "-m", "new launcher generation")
484
485 committed_state = _committed_state(root)
486 insertions = [(case.boundary, f' kill -{case.signal_name} "$$"')]
487 if case.second_cleanup_signal:
488 insertions.append((" trap '' HUP INT QUIT TERM", ' kill -TERM "$$"'))
489 installer = _signal_injected_installer(base, case.label, tuple(insertions))
490 result = _run_installer(root, installer=installer)
491 if result.returncode != ABORTED:
492 _fail(f"{case.label}: injected termination returned {result.returncode}, not 3")
493 if case.committed:
494 expected_config = (True, str(_managed_dir(root)))
495 _assert_transaction_state(root, case.label, expected_config, committed_state)
496 else:
497 _assert_transaction_state(root, case.label, original_config, original_state)
498 residue = tuple(_managed_dir(root).parent.glob("ra8-hooks.*"))
499 if residue:
500 _fail(f"{case.label}: interrupted transaction left residue: {residue!r}")
501
502
503def _installer_boundary_signal_cases(base: Path) -> None:
504 """Inject termination after every directory/configuration commit boundary."""
505 cases = (
506 ("before-first-move", " transaction_started=1", False, False),
507 ("after-backup-move", ' mv -- "$managed" "$backup"', False, False),
508 ("after-install-move", ' mv -- "$staging" "$managed"', False, False),
509 (
510 "after-config",
511 ' "$TRUSTED_GIT" -C "$root" config --local core.hooksPath "$managed"',
512 False,
513 False,
514 ),
515 ("after-commit-record", " installed=1", True, False),
516 ("rollback-second-signal", ' mv -- "$managed" "$backup"', False, True),
517 )
518 for label, boundary, committed, second_cleanup_signal in cases:
519 _installer_boundary_signal_case(
520 base,
521 _BoundaryCase(label, boundary, committed, second_cleanup_signal=second_cleanup_signal),
522 )
523 for config_state in ("absent", "empty", "legacy", "managed"):
524 for signal_name in ("HUP", "INT", "QUIT", "TERM"):
525 label = f"matrix-{config_state}-{signal_name.lower()}"
526 _installer_boundary_signal_case(
527 base,
528 _BoundaryCase(
529 label,
530 ' "$TRUSTED_GIT" -C "$root" config --local core.hooksPath "$managed"',
531 committed=False,
532 config_state=config_state,
533 signal_name=signal_name,
534 ),
535 )
536 _installer_restore_failure_case(base)
537 _installer_config_read_failure_case(base)
538 _installer_multiline_config_case(base)
539 _installer_exact_generation_case(base)
540 _installer_lock_acquisition_signal_case(base)
541
542
543def _installer_lock_acquisition_signal_case(base: Path) -> None:
544 """Defer termination until an acquired installer lock can be released."""
545 root = base / "installer-lock-acquisition-signal"
546 root.mkdir()
547 _seed_launcher_repo(root)
548 installer = _signal_injected_installer(
549 base,
550 "lock-acquisition-signal",
551 ((' if mkdir -- "$lock" 2>/dev/null; then', ' kill -TERM "$$"'),),
552 )
553 result = _run_installer(root, installer=installer)
554 if result.returncode != ABORTED:
555 _fail(f"lock acquisition termination returned {result.returncode}, not 3")
556 _assert_transaction_state(root, "lock-acquisition-signal", (False, ""), None)
557 residue = tuple((root / ".git").glob("ra8-hooks.*"))
558 if residue:
559 _fail(f"lock acquisition termination left residue: {residue!r}")
560 retry = _run_installer(root)
561 if retry.returncode:
562 _fail(f"lock acquisition termination blocked retry: {retry.stderr}")
563 expected_config = (True, str(_managed_dir(root)))
564 _assert_transaction_state(
565 root,
566 "lock-acquisition-retry",
567 expected_config,
568 _committed_state(root),
569 )
570
571
572def _installer_restore_failure_case(base: Path) -> None:
573 """Retain an exact recovery backup when directory restoration fails."""
574 root = base / "installer-restore-failure"
575 root.mkdir()
576 _seed_launcher_repo(root)
577 original_config, original_state = _configure_fixture(root, "managed")
578 launcher = root / "scripts/git/hook-launcher"
579 with launcher.open("a", encoding="ascii") as stream:
580 stream.write("\n# restore failure generation\n")
581 _git(root, "add", "scripts/git/hook-launcher")
582 _git(root, "commit", "--quiet", "-m", "new launcher generation")
583 text = INSTALLER.read_text(encoding="ascii")
584 move = ' mv -- "$managed" "$backup"'
585 restore = ' if mv -- "$backup" "$managed"; then'
586 if text.count(move) != 1 or text.count(restore) != 1:
587 _fail("restore-failure fixture did not bind both production moves")
588 text = text.replace(move, f'{move}\n kill -TERM "$$"', 1)
589 text = text.replace(restore, " if false; then", 1)
590 installer = base / "install-hooks-restore-failure.sh"
591 _write(installer, text, executable=True)
592 result = _run_installer(root, installer=installer)
593 if result.returncode != 1 or "recovery backup retained" not in result.stderr:
594 _fail("restore failure did not return 1 and disclose retained recovery state")
595 if _hooks_config_state(root) != original_config or _managed_state(root) is not None:
596 _fail("restore failure changed configuration or fabricated a generation")
597 common = _managed_dir(root).parent
598 backups = tuple(common.glob("ra8-hooks.backup.*"))
599 if len(backups) != 1:
600 _fail("restore failure did not retain exactly one recovery backup")
601 backup = backups[0]
602 backup_state = (
603 backup.stat().st_mode & 0o777,
604 tuple(
605 (path.name, path.stat().st_mode & 0o777, path.read_bytes())
606 for path in sorted(backup.iterdir())
607 ),
608 )
609 if backup_state != original_state:
610 _fail("retained recovery backup changed original bytes or modes")
611
612
613def _installer_config_read_failure_case(base: Path) -> None:
614 """Require a fatal hooksPath read to abort before any transaction."""
615 root = base / "installer-config-read-failure"
616 root.mkdir()
617 _seed_launcher_repo(root)
618 text = INSTALLER.read_text(encoding="ascii")
619 needle = ' read_hooks_path() {\n local count=0 row status=""'
620 if text.count(needle) != 1:
621 _fail("fatal config-read fixture did not bind the production helper")
622 text = text.replace(
623 needle,
624 ' read_hooks_path() {\n return 5\n local count=0 row status=""',
625 1,
626 )
627 installer = base / "install-hooks-config-read-failure.sh"
628 _write(installer, text, executable=True)
629 result = _run_installer(root, installer=installer)
630 if result.returncode != 1 or "cannot read local core.hooksPath" not in result.stderr:
631 _fail("fatal hooksPath read did not fail closed")
632 if _managed_state(root) is not None or tuple((root / ".git").glob("ra8-hooks.*")):
633 _fail("fatal hooksPath read started a transaction")
634
635
636def _installer_multiline_config_case(base: Path) -> None:
637 """Refuse an unmanaged trailing-newline value without normalizing it."""
638 root = base / "installer-multiline-config"
639 root.mkdir()
640 _seed_launcher_repo(root)
641 value = "scripts/git\n"
642 _git(root, "config", "--local", "core.hooksPath", value)
643 before = _hooks_config_state(root)
644 result = _run_installer(root)
645 if result.returncode != 1 or "refusing to replace unmanaged" not in result.stderr:
646 _fail("multiline hooksPath was normalized into an allowed value")
647 if before != (True, value) or _hooks_config_state(root) != before:
648 _fail("multiline hooksPath did not preserve its exact value")
649 if _managed_state(root) is not None:
650 _fail("multiline hooksPath refusal installed a managed generation")
651
652
653def _installer_exact_generation_case(base: Path) -> None:
654 """Repair unauthorized installed bytes to the exact committed generation."""
655 root = base / "installer-exact-generation"
656 root.mkdir()
657 _seed_launcher_repo(root)
658 first = _run_installer(root)
659 if first.returncode:
660 _fail(f"exact generation fixture failed to install: {first.stderr}")
661 managed = _managed_dir(root)
662 mutated = managed / "pre-commit"
663 mutated.chmod(0o700)
664 with mutated.open("ab") as stream:
665 stream.write(b"# unauthorized installed bytes\n")
666 (managed / "pre-push").chmod(0o700)
667 second = _run_installer(root)
668 if second.returncode:
669 _fail(f"installer did not repair unauthorized installed generation: {second.stderr}")
670 expected_config = (True, str(managed))
671 _assert_transaction_state(root, "exact-generation", expected_config, _committed_state(root))
672
673
674def _launcher_signal_case(base: Path) -> None:
675 root = base / "launcher-signal"
676 ready = base / "launcher.ready"
677 continued = base / "launcher.continued"
678 root.mkdir()
679 _seed_launcher_repo(root)
680 script = """#!/usr/bin/env bash
681set -euo pipefail
682trap 'exit 3' HUP INT QUIT TERM
683printf 'ready\\n' >"${RA8_SELFTEST_READY:?}"
684while :; do :; done
685printf 'continued\\n' >"${RA8_SELFTEST_CONTINUED:?}"
686"""
687 _write(root / "scripts/git/pre-commit", script, executable=True)
688 _git(root, "add", "scripts/git/pre-commit")
689 _git(root, "commit", "--quiet", "-m", "signal owner")
690 result = _run_installer(root)
691 if result.returncode:
692 _fail(f"signal fixture install failed: {result.stderr}")
693 environment = sanitized_git_environment()
694 environment.update(RA8_SELFTEST_READY=str(ready), RA8_SELFTEST_CONTINUED=str(continued))
695 proc = subprocess.Popen( # noqa: S603 -- private installed launcher
696 default_signal_test_command(str(_managed_dir(root) / "pre-commit")),
697 cwd=root,
698 env=environment,
699 stdout=subprocess.PIPE,
700 stderr=subprocess.PIPE,
701 start_new_session=True,
702 )
703 try:
704 _wait_path(ready, proc)
705 os.kill(proc.pid, signal.SIGTERM)
706 proc.communicate(timeout=15)
707 finally:
708 if proc.poll() is None:
709 with suppress(ProcessLookupError):
710 os.killpg(proc.pid, signal.SIGKILL)
711 proc.wait(timeout=5)
712 residue = tuple(_managed_dir(root).parent.glob("ra8-hook-run.*"))
713 if proc.returncode != ABORTED or continued.exists() or residue:
714 _fail("launcher did not preserve owner semantics after parent-only SIGTERM")
715
716
717def _wait_path(path: Path, proc: subprocess.Popen[bytes]) -> None:
718 deadline = time.monotonic() + 10
719 while time.monotonic() < deadline:
720 if path.exists():
721 return
722 if proc.poll() is not None:
723 _fail(f"fixture exited before ready: {proc.returncode}")
724 time.sleep(0.02)
725 _fail("fixture did not report readiness")
726
727
728def _extract_supervisor() -> str:
729 text = PRE_COMMIT.read_text(encoding="utf-8")
730 start = text.index("<<'PY' || true\n") + len("<<'PY' || true\n")
731 end = text.index("\nPY\n", start)
732 return text[start:end]
733
734
735def _supervisor_failure_case(base: Path, mode: str) -> None:
736 ready = base / f"{mode}.ready"
737 child_pid = base / f"{mode}.pid"
738 child = base / f"{mode}.sh"
739 _write(child, f"#!/usr/bin/env bash\necho $$ >{child_pid!s}\nsleep 60\n", executable=True)
740 if mode == "collision":
741 _write(ready, "occupied\n")
742 environment = sanitized_git_environment()
743 if mode == "write-failure":
744 ready = base / "missing-ready-parent" / "ready"
745 command = [
746 "/usr/bin/python3",
747 "-I",
748 "-c",
749 _extract_supervisor(),
750 str(ready),
751 "/bin/bash",
752 "-p",
753 str(child),
754 ]
755 result = subprocess.run( # noqa: S603 -- extracted audited supervisor
756 command, env=environment, capture_output=True, check=False, timeout=15
757 )
758 if result.returncode == 0:
759 _fail(f"supervisor {mode} returned success")
760 if child_pid.exists():
761 pgid = int(child_pid.read_text(encoding="ascii").strip())
762 try:
763 os.killpg(pgid, 0)
764 except ProcessLookupError:
765 pass
766 else:
767 with suppress(ProcessLookupError):
768 os.killpg(pgid, signal.SIGKILL)
769 _fail(f"supervisor {mode} left its child process group alive")
770
771
772def _supervisor_failure_cases(base: Path) -> None:
773 _supervisor_failure_case(base, "collision")
774 _supervisor_failure_case(base, "write-failure")
775 result = subprocess.run( # noqa: S603 -- extracted audited supervisor
776 [
777 "/usr/bin/python3",
778 "-I",
779 "-c",
780 _extract_supervisor(),
781 str(base / "missing.ready"),
782 "/absent",
783 ],
784 env=sanitized_git_environment(),
785 capture_output=True,
786 check=False,
787 timeout=10,
788 )
789 if result.returncode == 0:
790 _fail("supervisor interpreter failure returned success")
791
792
793def _stub_ci_support(root: Path) -> None:
794 for relative in ("git_environment.sh",):
795 _write(root / f"scripts/dev/{relative}", "#!/usr/bin/env bash\n")
796 _write(root / "scripts/ci/lib/parallelism.sh", "#!/usr/bin/env bash\n")
797 _write(root / "scripts/ci/lib/arm_toolchain.sh", "#!/usr/bin/env bash\n")
798 _write(root / "scripts/ci/lib/snapshot.sh", "#!/usr/bin/env bash\n")
799 _write(root / "scripts/ci/lib/tool_env.sh", "use_pinned_tool_path() { :; }\n")
800 _write(
801 root / "scripts/ci/lib/abort.sh",
802 "RA8_CI_EXIT_ABORTED=3\nci_require_tree_intact() { :; }\nci_install_abort_traps() { :; }\n",
803 )
804 gate_source = """for row in "${RA8_GATE_REGISTRY[@]}"; do
805 name="${row%%|*}"
806 fn="gate_${name//-/_}"
807 eval "$fn() { \"$RA8_SELFTEST_IGNORED_PROBE\"; \\
808 printf '%s\\n' '$name' >>\"$RA8_SELFTEST_GATE_LOG\"; }"
809done
810"""
811 _write(root / "scripts/ci/gates/fixture.sh", gate_source) # PATHREF-OK: private fixture
812
813
814def _policy_environment(root: Path) -> tuple[Path, dict[str, str]]:
815 gate_log = root / ".git/gates.log"
816 probe = root / ".venv/bin/probe"
817 _write(
818 probe,
819 "#!/usr/bin/env bash\n"
820 "if env | grep -Eq '^RA8_STAGED_(HOOK|GATE)_PROOF'; then exit 91; fi\n",
821 executable=True,
822 )
823 environment = sanitized_git_environment()
824 tools = root / ".git/policy-tools"
825 tools.mkdir()
826 (tools / "git").symlink_to(trusted_git_executable())
827 (tools / "bash").symlink_to("/bin/bash")
828 environment.update(
829 PATH=f"{tools}:{environment.get('PATH', os.defpath)}",
830 RA8_STAGED_HOOK_SNAPSHOT="1",
831 RA8_SELFTEST_GATE_LOG=str(gate_log),
832 RA8_SELFTEST_IGNORED_PROBE=str(probe),
833 RA8_TOOLS_CACHE=str(root / ".git/tool-cache"),
834 )
835 return gate_log, environment
836
837
838def _policy_fixture(root: Path, hooks_text: str) -> tuple[Path, dict[str, str]]:
839 _init_repo(root)
840 _write(root / "just/hooks.just", hooks_text)
841 _write(
842 root / "justfile",
843 (
844 'set shell := ["/bin/bash", "-puc"]\n'
845 'export BASH_ENV := "/dev/null"\n'
846 'export ENV := "/dev/null"\n'
847 'export PYTHONHOME := ""\n'
848 'export PYTHONPATH := ""\n'
849 'mod git_hooks "just/hooks.just"\n'
850 'mod quality "quality.just"\n'
851 ),
852 )
853 _write(root / "quality.just", 'set working-directory := "."\nmod local "quality_local.just"\n')
854 _write(
855 root / "quality_local.just",
856 'set working-directory := "."\ngate name:\n'
857 ' /bin/bash -p scripts/ci.sh --gate "{{ name }}"\n',
858 )
859 (root / "scripts/git").mkdir(parents=True)
860 shutil.copy2(CI_SCRIPT, root / "scripts/ci.sh")
861 shutil.copy2(PROOF_WRITER, root / "scripts/git/write-proof.py")
862 _stub_ci_support(root)
863 for name in (
864 "check_hook_parity.py",
865 "check_mcdc_block.py",
866 "check_new_compound_has_mcdc.py",
867 "check_obsolete_standards.py",
868 ):
869 _write(
870 root / f"scripts/checks/{name}",
871 "#!/usr/bin/env python3\nraise SystemExit(0)\n",
872 executable=True,
873 )
874 _write(root / "sample.c", "int value;\n")
875 _write(root / ".gitignore", ".venv/\n")
876 _git(root, "add", ".")
877 _git(root, "commit", "--quiet", "-m", "fixture")
878 _write(root / "sample.c", "int value = 1;\n")
879 _git(root, "add", "sample.c")
880 return _policy_environment(root)
881
882
883def _policy_case(base: Path, name: str, hooks_text: str) -> bool:
884 root = base / name
885 root.mkdir()
886 gate_log, environment = _policy_fixture(root, hooks_text)
887 just = shutil.which("just")
888 if just is None:
889 _fail("Just is required for the real recipe runtime selftest")
890 result = subprocess.run( # noqa: S603 -- private fixture Just and paths
891 [
892 just,
893 "--shell",
894 "/bin/bash",
895 "--clear-shell-args",
896 "--shell-arg",
897 "-puc",
898 "--justfile",
899 str(root / "justfile"),
900 "--working-directory",
901 str(root),
902 "git_hooks::pre-commit",
903 ],
904 cwd=root,
905 env=environment,
906 capture_output=True,
907 text=True,
908 check=False,
909 timeout=30,
910 )
911 gates = tuple(gate_log.read_text(encoding="utf-8").splitlines()) if gate_log.exists() else ()
912 return result.returncode == 0 and gates == EXPECTED_GATES
913
914
915def _real_policy_cases(base: Path) -> None:
916 live = HOOKS_JUST.read_text(encoding="utf-8")
917 if not _policy_case(base, "policy-live", live):
918 _fail("real Just pre-commit recipe did not run every gate")
919 early = live.replace(" gates=(", " exit 0\n gates=(", 1)
920 dead = live.replace(
921 ' for gate in "${gates[@]}"; do\n run_gate "$gate"\n done',
922 (
923 ' if false; then\n for gate in "${gates[@]}"; do\n'
924 ' run_gate "$gate"\n done\n fi'
925 ),
926 1,
927 )
928 reordered = live.replace(
929 " ascii\n copyright", " copyright\n ascii", 1
930 )
931 for name, mutated in (
932 ("policy-early", early),
933 ("policy-dead", dead),
934 ("policy-order", reordered),
935 ):
936 if _policy_case(base, name, mutated):
937 _fail(f"real Just runtime accepted {name} mutation")
938
939
940def _proof_writer_case(base: Path) -> None:
941 proof = base / "atomic.proof"
942 command = ["/usr/bin/python3", "-I", str(PROOF_WRITER), str(proof)]
943 first = subprocess.run( # noqa: S603 -- audited helper and private path
944 command, input=b"token\n", capture_output=True, check=False
945 )
946 second = subprocess.run( # noqa: S603 -- audited helper and private path
947 command, input=b"token\n", capture_output=True, check=False
948 )
949 target = base / "target"
950 target.write_text("unchanged\n", encoding="ascii")
951 linked = base / "linked.proof"
952 linked.symlink_to(target)
953 linked_result = subprocess.run( # noqa: S603 -- audited helper and private path
954 ["/usr/bin/python3", "-I", str(PROOF_WRITER), str(linked)],
955 input=b"changed\n",
956 capture_output=True,
957 check=False,
958 )
959 if first.returncode or second.returncode == 0 or linked_result.returncode == 0:
960 _fail("atomic proof writer did not enforce exclusive no-follow creation")
961 if target.read_text(encoding="ascii") != "unchanged\n":
962 _fail("atomic proof writer followed a final-component symlink")
963
964
965def run_runtime_selftests() -> None:
966 """Run all hook ownership, supervisor, real-policy, and proof fixtures."""
967 with tempfile.TemporaryDirectory(prefix="ra8-hook-runtime-") as temporary:
968 base = Path(temporary)
969 _launcher_immutability_case(base)
970 _installer_path_case(base)
971 _same_commit_bootstrap_case(base)
972 _launcher_signal_case(base)
973 _supervisor_failure_cases(base)
974 _installer_transaction_case(base)
975 _installer_boundary_signal_cases(base)
976 _real_policy_cases(base)
977 _proof_writer_case(base)