Reuse the existing run input for command images and ordinary queue/steer delivery. Align Unicode command separators, bound goal observation, preserve plan input encoding, and handle explicitly expected Windows cancellation disconnects in runtime tests.
142 lines
6.3 KiB
Python
142 lines
6.3 KiB
Python
"""Goal observation deadlines and runtime cleanup through the runnable example."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import runpy
|
|
import sys
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from deepseek_harness import DeepSeekHarness
|
|
|
|
|
|
_EXAMPLE = Path(__file__).resolve().parents[1] / "examples/goal.py"
|
|
|
|
|
|
@pytest.mark.parametrize("pending", ["command", "goal-event", "wrapup", "complete", "goal-queue", "wrapup-queue"])
|
|
def test_goal_example_bounds_pending_work_and_reaps_runtime(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], pending: str,
|
|
) -> None:
|
|
peer = tmp_path / "goal-peer.py"
|
|
observed = tmp_path / "observed.jsonl"
|
|
peer.write_text('''import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
pending, observed = sys.argv[1:]
|
|
|
|
def send(frame):
|
|
print(json.dumps({"jsonrpc": "2.0", **frame}), flush=True)
|
|
|
|
for line in sys.stdin:
|
|
frame = json.loads(line)
|
|
with Path(observed).open("a", encoding="utf-8") as stream:
|
|
stream.write(json.dumps(frame) + "\\n")
|
|
method = frame["method"]
|
|
if method == "initialize":
|
|
send({"id": frame["id"], "result": {"serverInfo": {"name": "goal-example-peer"}}})
|
|
elif method == "session/query":
|
|
assert frame["params"]["contentBlocks"] == [{"type": "text", "text": "/goal Finish this task"}]
|
|
session = frame["params"]["sessionId"]
|
|
if pending == "command":
|
|
continue
|
|
if pending in ("wrapup", "complete", "wrapup-queue"):
|
|
send({"method": "session.event", "params": {"sessionId": session, "event": {
|
|
"type": "goal/change", "data": {"operation": "complete"}}}})
|
|
elif pending == "goal-queue":
|
|
for operation in ("create", "edit"):
|
|
send({"method": "session.event", "params": {"sessionId": session, "event": {
|
|
"type": "goal/change", "data": {"operation": operation}}}})
|
|
send({"id": frame["id"], "result": {"kind": "command", "execution": {
|
|
"commandId": "goal-command", "result": {"kind": "success", "text": "Goal set"}}}})
|
|
elif method == "session/wait":
|
|
if pending == "wrapup":
|
|
continue
|
|
for _ in range(2 if pending == "wrapup-queue" else 1):
|
|
send({"method": "session.event", "params": {"sessionId": session, "event": {
|
|
"type": "assistant/message", "data": {"message": {
|
|
"content": [{"type": "text", "text": "Closing reply"}]}}}}})
|
|
send({"id": frame["id"], "result": {}})
|
|
elif method == "shutdown":
|
|
send({"id": frame["id"], "result": {}})
|
|
break
|
|
else:
|
|
raise AssertionError(method)
|
|
''', encoding="utf-8")
|
|
example = runpy.run_path(str(_EXAMPLE))
|
|
harnesses: list[DeepSeekHarness] = []
|
|
|
|
def configured_harness(**options: object) -> DeepSeekHarness:
|
|
harness = DeepSeekHarness(
|
|
**options, _launch_args=(sys.executable, str(peer), pending, str(observed)),
|
|
initialize_timeout_seconds=10, shutdown_timeout_seconds=10,
|
|
)
|
|
harnesses.append(harness)
|
|
return harness
|
|
|
|
monkeypatch.setitem(example["main"].__globals__, "DeepSeekHarness", configured_harness)
|
|
queued = pending in {"goal-queue", "wrapup-queue"}
|
|
displayed = []
|
|
if queued:
|
|
clock = [0.0]
|
|
display = example["display"]
|
|
|
|
def advance_after_display(notification):
|
|
event = display(notification)
|
|
if event is not None:
|
|
displayed.append(event["type"])
|
|
if pending == "goal-queue" or event["type"] == "assistant/message":
|
|
clock[0] = 11.0
|
|
return event
|
|
|
|
# Advance only the example clock while the real transport retains its own timeout.
|
|
monkeypatch.setitem(example["main"].__globals__, "time", SimpleNamespace(monotonic=lambda: clock[0]))
|
|
monkeypatch.setitem(example["main"].__globals__, "display", advance_after_display)
|
|
timeout = "10" if pending == "complete" or queued else "1"
|
|
monkeypatch.setattr(sys, "argv", [str(_EXAMPLE), "--dsh-home", str(tmp_path / "home"),
|
|
"--workspace", str(tmp_path), "--timeout-seconds", timeout, "Finish this task"])
|
|
# The outer bound also releases an implementation that forgets its own deadline.
|
|
with ThreadPoolExecutor(max_workers=1) as workers:
|
|
execution = workers.submit(example["main"])
|
|
try:
|
|
if pending == "complete":
|
|
execution.result(timeout=30)
|
|
else:
|
|
expected = {"command": "session/query timed out", "goal-event": "SDK notification",
|
|
"wrapup": "session/wait timed out", "goal-queue": "Goal observation timed out",
|
|
"wrapup-queue": "Goal observation timed out"}[pending]
|
|
with pytest.raises(TimeoutError, match=expected):
|
|
execution.result(timeout=30)
|
|
finally:
|
|
for harness in harnesses:
|
|
harness.close()
|
|
[harness] = harnesses
|
|
assert harness.client._proc is None
|
|
frames = [json.loads(line) for line in observed.read_text(encoding="utf-8").splitlines()]
|
|
methods = [frame["method"] for frame in frames]
|
|
assert methods == ["initialize", "session/query",
|
|
*(["session/wait"] if pending in {"wrapup", "complete", "wrapup-queue"} else []), "shutdown"]
|
|
if queued:
|
|
assert displayed == (["goal/change"] if pending == "goal-queue" else ["goal/change", "assistant/message"])
|
|
if pending == "complete":
|
|
output = capsys.readouterr().out
|
|
assert "Goal: complete" in output
|
|
assert "Closing reply" in output
|
|
|
|
|
|
@pytest.mark.parametrize("timeout", ["0", "-1", "nan", "inf", "-inf"])
|
|
def test_goal_example_rejects_unbounded_or_nonpositive_timeout(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], timeout: str,
|
|
) -> None:
|
|
example = runpy.run_path(str(_EXAMPLE))
|
|
monkeypatch.setattr(sys, "argv", [str(_EXAMPLE), "--dsh-home", str(tmp_path / "home"),
|
|
f"--timeout-seconds={timeout}", "Finish this task"])
|
|
with pytest.raises(SystemExit) as error:
|
|
example["main"]()
|
|
assert error.value.code == 2
|
|
assert "timeout-seconds must be finite and positive" in capsys.readouterr().err
|