c52d591851
gates / gates (push) Successful in 49s
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
82 lines
3.7 KiB
Python
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)
|