90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""`finish` must accept what the model actually sends, not only the schema.
|
|
|
|
python3 tests/test_vm_finish_shapes.py
|
|
|
|
WHY
|
|
|
|
A session wrote the script, syntax-checked it, built a fourteen-file test
|
|
tree with awkward filenames, cross-checked its output against an
|
|
independent listing, demonstrated the error paths, committed, and passed
|
|
its own verification command. Then it could not close.
|
|
|
|
`finish` declares `verification` as an object - `{command, expected}` -
|
|
and the model sends the command as a bare string often enough to matter.
|
|
`v_finish` called `.get` on it, which raised
|
|
|
|
AttributeError: 'str' object has no attribute 'get'
|
|
|
|
out of the verb. The agent saw only "the supervisor refuses the call",
|
|
tried seven times with varying arguments, then marked the task blocked
|
|
and gave up. All the work was done and committed; the session simply
|
|
could not be ended.
|
|
|
|
The identical mistake had already been found and fixed in the live view's
|
|
renderer. That one cost a line of output. This one cost the end of a
|
|
session - so the rule is the same wherever a model-supplied value is
|
|
read: take what arrives, not what was asked for.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
VMD = os.path.join(HERE, "..", "vm", "vmd")
|
|
|
|
FAILS: list[str] = []
|
|
|
|
|
|
def check(label: str, condition: bool, detail: str = "") -> None:
|
|
if condition:
|
|
print(f"ok {label}")
|
|
else:
|
|
FAILS.append(label)
|
|
print(f"FAIL {label}\n {detail}")
|
|
|
|
|
|
SRC = open(os.path.join(VMD, "vmd.py"), encoding="utf-8").read()
|
|
body = SRC[SRC.index("def v_finish"):SRC.index("def v_report")]
|
|
|
|
|
|
def normalise(value):
|
|
"""The shape-tolerant read, lifted out of v_finish so it can be tested."""
|
|
check_ = value
|
|
if isinstance(check_, str):
|
|
check_ = {"command": check_}
|
|
elif not isinstance(check_, dict):
|
|
check_ = {}
|
|
return str(check_.get("command", "")).strip(), str(check_.get("expected", "")).strip()
|
|
|
|
|
|
# --- what the model sends -------------------------------------------------
|
|
check("the documented shape works",
|
|
normalise({"command": "pytest -q", "expected": "0 failed"})
|
|
== ("pytest -q", "0 failed"))
|
|
check("a bare string is taken as the command",
|
|
normalise("bash -n topfiles.sh") == ("bash -n topfiles.sh", ""),
|
|
"this is the one that raised AttributeError and ended the session")
|
|
check("a missing verification is empty, not an exception",
|
|
normalise(None) == ("", ""))
|
|
for junk in (12, [], ["a"], True):
|
|
got = normalise(junk)
|
|
check(f"{type(junk).__name__} does not raise", got == ("", ""), str(got))
|
|
|
|
# --- and the real verb does the same --------------------------------------
|
|
check("v_finish normalises before reading",
|
|
"isinstance(check, str)" in body and 'check = {"command": check}' in body,
|
|
"the verb itself has to do this, not just this test")
|
|
check("it no longer calls .get on whatever arrived",
|
|
'(args.get("verification") or {}).get(' not in body,
|
|
"that is the exact expression that raised")
|
|
check("a verification with no command is still refused clearly",
|
|
"finish needs a verification" in body,
|
|
"tolerating the shape must not mean accepting nothing")
|
|
|
|
print()
|
|
if FAILS:
|
|
print(f"FAILED {len(FAILS)}")
|
|
raise SystemExit(1)
|
|
print("finish takes what the model sends, and a finished session can end")
|