fix(tests): the no-block guard reads the shell, not the comment explaining it (#3680)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 42s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 19s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 42s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 19s
The hook's header quotes `{"decision":"block"}` while explaining why this hook
must never emit one, and the instructions it prints use "decision" in a
sentence. Grepping the whole file caught both and failed the guard on the file
doing its job.
Strips comments and the heredoc first, and guards the stripper: `_code()`
returning nothing would make all three static checks pass against an empty
string, which is the circularity rule 167 is about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -51,6 +51,39 @@ def _run(event: dict) -> subprocess.CompletedProcess:
|
|||||||
capture_output=True, text=True, timeout=30)
|
capture_output=True, text=True, timeout=30)
|
||||||
|
|
||||||
|
|
||||||
|
def _code() -> str:
|
||||||
|
"""The script with its comments and its heredoc removed.
|
||||||
|
|
||||||
|
The static checks below are about what the shell DOES. Run over the whole
|
||||||
|
file they also read the header — which quotes `{"decision":"block"}` in the
|
||||||
|
course of explaining why this hook must never emit one — and the emitted
|
||||||
|
instructions, which use the word "decision" in a sentence. Both are the
|
||||||
|
file doing its job, and neither is code.
|
||||||
|
"""
|
||||||
|
lines, in_heredoc = [], False
|
||||||
|
for line in HOOK.read_text().splitlines():
|
||||||
|
if in_heredoc:
|
||||||
|
in_heredoc = line.strip() != "EOF"
|
||||||
|
continue
|
||||||
|
if line.lstrip().startswith("cat <<'EOF'"):
|
||||||
|
in_heredoc = True
|
||||||
|
continue
|
||||||
|
if not line.lstrip().startswith("#"):
|
||||||
|
lines.append(line)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_comment_stripper_still_leaves_the_shell_behind():
|
||||||
|
"""Guard on the three checks below it. `_code()` returning nothing would
|
||||||
|
make every one of them pass while checking an empty string — the same
|
||||||
|
circularity the plugin version check has to defend against."""
|
||||||
|
code = _code()
|
||||||
|
assert "set -uo pipefail" in code and "exit 0" in code
|
||||||
|
# It really did strip: both of the words the checks look for are present
|
||||||
|
# in the file, and neither is in the code.
|
||||||
|
assert "decision" in HOOK.read_text()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("trigger", ["manual", "auto"])
|
@pytest.mark.parametrize("trigger", ["manual", "auto"])
|
||||||
def test_it_exits_zero_with_instructions_on_stdout(trigger):
|
def test_it_exits_zero_with_instructions_on_stdout(trigger):
|
||||||
"""Exit 0 plus non-empty stdout is the entire contract for reaching the
|
"""Exit 0 plus non-empty stdout is the entire contract for reaching the
|
||||||
@@ -78,9 +111,9 @@ def test_it_emits_text_and_not_a_json_envelope():
|
|||||||
def test_it_never_blocks_the_compaction():
|
def test_it_never_blocks_the_compaction():
|
||||||
"""A block skips compaction silently from the model's side. Nothing in the
|
"""A block skips compaction silently from the model's side. Nothing in the
|
||||||
script may produce one — not an exit code, not a decision."""
|
script may produce one — not an exit code, not a decision."""
|
||||||
body = HOOK.read_text()
|
code = _code()
|
||||||
assert '"decision"' not in body and "'decision'" not in body
|
assert "decision" not in code, "a block decision would skip the compaction"
|
||||||
assert "exit 2" not in body
|
assert "exit 2" not in code
|
||||||
# A truncated or absent event must not turn into a non-zero exit either.
|
# A truncated or absent event must not turn into a non-zero exit either.
|
||||||
for event in ("", "not json", "{}"):
|
for event in ("", "not json", "{}"):
|
||||||
out = subprocess.run(["bash", str(HOOK)], input=event,
|
out = subprocess.run(["bash", str(HOOK)], input=event,
|
||||||
@@ -92,7 +125,7 @@ def test_additional_context_is_not_how_this_event_works():
|
|||||||
"""SessionStart's channel, which does not exist on PreCompact. A rewrite
|
"""SessionStart's channel, which does not exist on PreCompact. A rewrite
|
||||||
that reaches for it would read as consistent with the other hooks and
|
that reaches for it would read as consistent with the other hooks and
|
||||||
inject nothing at all."""
|
inject nothing at all."""
|
||||||
assert "additionalContext" not in HOOK.read_text().split("set -uo pipefail")[-1]
|
assert "additionalContext" not in _code()
|
||||||
|
|
||||||
|
|
||||||
def test_the_plugin_registers_it_on_precompact():
|
def test_the_plugin_registers_it_on_precompact():
|
||||||
|
|||||||
Reference in New Issue
Block a user