9bd1a54d71
A census of all thirteen gate scripts across the four felhom repos on 2026-08-02 found one clean correlation: every check a CLAUDE.md tells a person to run was passing, and two of the four nobody is told to run were failing — one since 14 July. Neither failure was harmful in effect (checked line by line); nothing would have said so if they had been. The fix is not more gates, it is one place to run them from. repo_gates.py runs site + hostinstall + hub-confirm + manifest-bearer + reuse-refs, streams each gate's own output, and exits worst-wins non-zero. A missing gate script is a FAILURE and prints the path tried — fail-closed, because a runner that quietly skips a gate is the inert-seam failure this project has shipped four times. It copies catalog_gates.py (R-161), NOT site_gates.py, which is a gate and not a runner. .githooks/pre-push runs it with --fast and refuses the push. Honest limits are written into the hook itself: per-clone (core.hooksPath is local config), and --no-verify bypasses it on purpose. Any manual run WARNS when the clone is unarmed. Measured on git 2.47.3: a relative core.hooksPath resolves correctly and the hook's cwd is the repo root from any subdirectory. test_repo_gates.py is a SEAM test — it asserts each member gate's own distinctive stdout, not the runner's summary line, which an inert runner prints while calling nothing. Red-proofed: replacing run_gate's body with 'return 0' still prints 'all felhom.eu gates OK' and exits 0, and turns the seam test red.
59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Seam test for scripts/repo_gates.py.
|
|
|
|
Run: python3 scripts/test_repo_gates.py
|
|
|
|
WHY THIS EXISTS. An entry point is a seam by definition: a runner that LISTS a gate but never
|
|
executes it is inert and fully green, and this project has shipped an inert seam four times. So
|
|
the assertion is on each member gate's OWN distinctive stdout — never on the runner's summary
|
|
line, which the runner can print without ever calling anything — plus the exit code, which is a
|
|
runner's actual effect.
|
|
|
|
Red-proofed 2026-08-02: replacing run_gate's body with `return 0` (the inert runner) turns
|
|
test_every_member_gate_actually_ran red while the summary still prints "all felhom.eu gates OK".
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
ENTRY = os.path.join(ROOT, "scripts", "repo_gates.py")
|
|
|
|
# (label, a substring only THAT gate can print)
|
|
FINGERPRINTS = [
|
|
("site", "site gates OK"),
|
|
("hostinstall", "hostinstall gates: ALL PASS"),
|
|
("hub-confirm", "hub confirm gate"),
|
|
("manifest-bearer", "manifest bearer gate"),
|
|
("reuse-refs", "cited paths — exact"),
|
|
]
|
|
|
|
|
|
class RepoGatesTest(unittest.TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
p = subprocess.run([sys.executable, ENTRY, "--fast"], cwd=ROOT,
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
cls.rc = p.returncode
|
|
cls.out = p.stdout.decode("utf-8", "replace")
|
|
|
|
def test_exit_code_is_zero(self):
|
|
self.assertEqual(self.rc, 0, self.out)
|
|
|
|
def test_every_member_gate_actually_ran(self):
|
|
for label, fingerprint in FINGERPRINTS:
|
|
self.assertIn(fingerprint, self.out,
|
|
"gate %r is listed but its own output never appeared — an inert runner "
|
|
"prints the summary without calling anything:\n%s" % (label, self.out))
|
|
|
|
def test_unknown_argument_is_rejected(self):
|
|
p = subprocess.run([sys.executable, ENTRY, "--nope"], cwd=ROOT,
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
self.assertEqual(p.returncode, 2, p.stdout.decode("utf-8", "replace"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|