Files
felhom-agent/scripts/test_agent_gates.py
T
admin c52d591851
gates / gates (push) Successful in 49s
The decoy suite is a gate of the full run (R-1116); scripts only, no release
scripts/test_gate_decoys.py is now agent_gates.py's `gate-decoys` row: the full run (which CI runs on every
push), not --fast (~20 s in the workspace, 30 cases). Declared in its own COVERS; test_agent_gates.py pins it.
Red-proved: a reuse-refs FACT case flipped to expect rc 0 -> CONVICTED: gate-decoys; restored, green.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-17 11:01:25 +02:00

82 lines
3.7 KiB
Python

# -*- coding: utf-8 -*-
"""Seam test for scripts/agent_gates.py.
Run from the repo root: python3 scripts/test_agent_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 the 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.
The second test is the one that matters here: this repo's only gate lives in a SIBLING clone, so
"the sibling is missing" must be a FAILURE and not a quiet skip.
"""
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ENTRY = os.path.join(ROOT, "scripts", "agent_gates.py")
class AgentGatesTest(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_member_gate_actually_ran(self):
self.assertIn("cited paths — exact", self.out,
"the reuse-refs gate is listed but its own output never appeared — an inert "
"runner prints the summary without calling anything:\n%s" % self.out)
def test_missing_shared_checker_is_a_failure_not_a_skip(self):
"""Fail-closed. Copy the entry point into a lone directory with no felhom.eu sibling and
confirm it CONVICTS rather than reporting green with nothing run."""
tmp = tempfile.mkdtemp(prefix="agent-gates-")
try:
lone = os.path.join(tmp, "felhom-agent", "scripts")
os.makedirs(lone)
shutil.copy(ENTRY, os.path.join(lone, "agent_gates.py"))
p = subprocess.run([sys.executable, os.path.join(lone, "agent_gates.py"), "--fast"],
cwd=os.path.dirname(lone),
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out = p.stdout.decode("utf-8", "replace")
self.assertNotEqual(p.returncode, 0, out)
self.assertIn("is MISSING — tried", out)
finally:
shutil.rmtree(tmp, ignore_errors=True)
def test_decoy_suite_is_a_full_run_gate(self):
"""R-1116: the decoy suite is a gate of the FULL run (which CI runs on every push), and --fast
names it as skipped (~20 s, too slow for the hook)."""
sys.dont_write_bytecode = True # no __pycache__ left in scripts/ (not gitignored here)
sys.path.insert(0, os.path.join(ROOT, "scripts"))
import agent_gates as mod
rows = [g for g in mod.GATES if g[0] == "gate-decoys"]
self.assertEqual(len(rows), 1, "gate-decoys is not registered exactly once")
self.assertEqual(os.path.basename(rows[0][1]), "test_gate_decoys.py")
self.assertFalse(rows[0][3], "gate-decoys must not be --fast")
self.assertEqual([g[0] for g in mod.GATES if not g[3]], ["published", "gate-decoys"])
self.assertIn("--fast SKIPPED (deliberate periodic runs, never in a hook): published, gate-decoys",
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)