Skip to content

Commit 1cefaa0

Browse files
TMHSDigitalclaude
andcommitted
test: one forbidden-falsifier-exit set, and pin release-gate's content-smoke mirror
protocol.py forbade (0, 1, 77) while the catalog linter forbade {0, 1, 2, 77}, so a direct run_example.py --expect-exit 2 passed even though 2 is argparse usage and a deleted flag exits 2 too. protocol.py now defines the set once, including 2, and check_smoke_catalog and check_falsifier_targets import it; the showcase static check also rejects a README falsifier declaring one. release-gate.sh hand-copies content-smoke.yml's positive paths filter; a new test fails when the two drift, so a direct push touching a newly filtered path cannot release without waiting for Content Smoke. Closes #537 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpkeXRvikhJZSVGBFM91Dv Signed-off-by: TM Hospitality Strategies <TMHospitalityStrategies@gmail.com>
1 parent a1b70f7 commit 1cefaa0

5 files changed

Lines changed: 46 additions & 7 deletions

File tree

‎tests/check_falsifier_targets.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
import time
5454

5555
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
56+
sys.path.insert(0, os.path.join(ROOT, "tests", "smoke"))
57+
from protocol import FORBIDDEN_FALSIFIER_EXITS # noqa: E402
58+
5659
SHOWCASE = os.path.join(ROOT, "showcase")
5760

5861
# Pieces that predate the falsifier-table convention. Tracked for backfill;
@@ -209,6 +212,12 @@ def check_static():
209212
(name, f"{flag} documented but not an argparse flag")
210213
)
211214
continue
215+
if code in FORBIDDEN_FALSIFIER_EXITS:
216+
failures.append(
217+
(name, f"{flag} declares exit {code}, which names no check "
218+
f"(forbidden: {sorted(FORBIDDEN_FALSIFIER_EXITS)})")
219+
)
220+
continue
212221
if code not in exits:
213222
failures.append(
214223
(name, f"{flag} declares exit {code}, absent from the "

‎tests/check_smoke_catalog.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@
2525
import sys
2626
from pathlib import Path
2727

28+
sys.path.insert(0, str(Path(__file__).resolve().parent / "smoke"))
29+
from protocol import FORBIDDEN_FALSIFIER_EXITS # noqa: E402
30+
2831
# Directories that intentionally have no smoke row. Keep empty unless justified.
2932
EXCLUDED: set[str] = set()
3033

3134
ROW_KEYS = {"name", "script", "args", "min_version", "expect_file",
3235
"expect_sidecar", "sidecar_contains", "falsifiers", "timeout"}
3336
FALSIFIER_KEYS = {"args", "expect_exit", "expect_sidecar_fail", "min_version"}
34-
# 2 is argparse's usage-error exit: an unknown or deleted flag also exits 2,
35-
# so a falsifier expecting 2 passes even when its flag no longer exists.
36-
FORBIDDEN_EXITS = {0, 1, 2, 77}
37+
# Defined once in tests/smoke/protocol.py, which explains each code.
38+
FORBIDDEN_EXITS = set(FORBIDDEN_FALSIFIER_EXITS)
3739

3840

3941
def check_schema(row: dict) -> list[str]:

‎tests/smoke/protocol.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,14 @@ def sidecar_ok(path: Optional[str], contains: Optional[str]) -> Tuple[bool, str]
5959
return True, ""
6060

6161

62+
# argparse exits 2 on a usage error, so an unknown or deleted flag also exits
63+
# 2: a falsifier expecting 2 passes even when its flag no longer exists.
64+
USAGE_EXIT = 2
65+
6266
# Exits a falsifier may never "expect": 0 is a pass, 1 is the FATAL wrapper
63-
# (an uncaught exception, not a named check), 77 is a skip.
64-
FORBIDDEN_FALSIFIER_EXITS = (0, 1, SKIP_EXIT)
67+
# (an uncaught exception, not a named check), 2 is argparse usage, 77 is a
68+
# skip. The single source for every runner and linter (#537).
69+
FORBIDDEN_FALSIFIER_EXITS = (0, 1, USAGE_EXIT, SKIP_EXIT)
6570

6671

6772
def classify(

‎tests/smoke/test_harness.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,17 @@ def test_other_check_is_fail(self):
191191
def test_crash_is_fail(self):
192192
self.assertEqual(self.c(1, expect_exit=7), FAIL)
193193

194-
def test_cannot_expect_0_1_or_77(self):
195-
for bad in (0, 1, SKIP_EXIT):
194+
def test_cannot_expect_0_1_2_or_77(self):
195+
# 2 is argparse usage: a deleted flag exits 2 too (#537)
196+
for bad in (0, 1, 2, SKIP_EXIT):
196197
self.assertEqual(self.c(bad, expect_exit=bad), FAIL)
197198

199+
def test_catalog_linter_shares_the_forbidden_set(self):
200+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
201+
import check_smoke_catalog
202+
from protocol import FORBIDDEN_FALSIFIER_EXITS
203+
self.assertEqual(check_smoke_catalog.FORBIDDEN_EXITS, set(FORBIDDEN_FALSIFIER_EXITS))
204+
198205
def test_legal_skip_stays_skip(self):
199206
out = "SMOKE_SKIP: needs 5.1\n"
200207
st, _ = classify(proc_exit=SKIP_EXIT, output=out, min_version="5.1",

‎tests/test_release_gate.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,22 @@ def test_gate_mirrors_smoke_paths_ignore(self):
129129
self.assertIsNotNone(case, "release-gate.sh smoke-ignored case line not found")
130130
self.assertEqual(set(case.group(1).split("|")), want, event)
131131

132+
def test_gate_mirrors_content_smoke_paths(self):
133+
# content_smoke_applies is release-gate.sh's copy of content-smoke.yml's
134+
# positive `paths` filter; a path added to one must reach the other (#537).
135+
import re
136+
import yaml
137+
wf = yaml.safe_load((REPO / ".github" / "workflows" / "content-smoke.yml")
138+
.read_text(encoding="utf-8"))
139+
on = wf.get("on", wf.get(True))
140+
body = GATE.read_text(encoding="utf-8").split("content_smoke_applies() {", 1)[1]
141+
case = re.search(r"^\s*([^\n]*\|[^\n]*)\) return 0 ;;", body, re.M)
142+
self.assertIsNotNone(case, "content_smoke_applies case line not found")
143+
have = set(case.group(1).split("|"))
144+
for event in ("push", "pull_request"):
145+
want = {g.replace("/**", "/*") for g in on[event]["paths"]}
146+
self.assertEqual(have, want, event)
147+
132148
def test_truncated_file_list_waits_for_smoke(self):
133149
# 300 docs paths is the API's cap: a code change may sort after them.
134150
many = r"\n".join(f"docs/gallery/p{i:03d}.html" for i in range(300))

0 commit comments

Comments
 (0)