Files
codex-app-server/tests/test_v2cov_gaps_d5.py
host 12e72dab9b code-to10 v4 coverage thickening: 219 new assertion tests (batches A/B1/C/D1-D6)
Baseline merge head e5ff26a: 28144 stmts, 2807 missing, exact 90.0263%, margin +7.
After thickening (full run): 2350 missing, exact 91.6501%, margin +464.
Full suite 3516 items: 3509 pass, 1 failure (pre-existing November date
failure, untouched), 6 skipped. No production code changed.
2026-11-03 02:49:26 +00:00

234 lines
8.4 KiB
Python

"""Coverage batch v4-D5: host.py leftover branches (wait-turn first
slice, run-turn interrupt raise, daemon-stop inner break, daemon
subcommand failure, interrupt poll paths, goal verify warning).
Stubbing discipline: the function under test is never stubbed. Patched
collaborators are: time.monotonic / time.sleep / os.kill /
subprocess.run / interrupt_turn / _wait_turn / dispatch /
run_goal_loop / json.load (sinks, clocks and predicates). Every
assertion reads a real return value or real stdout.
"""
import contextlib
import io
import json
import os
import queue
import signal
import sys
import tempfile
import threading
import time
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
import host
from host import Server, _wait_turn
def _server():
srv = object.__new__(Server)
srv.proc = None
srv.codex_bin = "codex"
srv.codex_home = None
srv.approval = "deny"
srv.model_provider = None
srv.progress_interval = 30
srv.send_lock = threading.Lock()
srv.next_id = 1
srv.pending = {}
srv.pending_lock = threading.Lock()
srv.turn_queues = {}
srv.events = []
srv.events_lock = threading.Lock()
srv.restart_count = 0
srv.trace_path = None
srv._trace_lines = 0
return srv
class _FakeServer:
def __init__(self, *args, **kwargs):
pass
def initialize(self):
pass
def set_trace_file(self, *args, **kwargs):
pass
def close(self):
pass
def resume_thread(self, thread_id):
pass
def interrupt_turn(self, thread_id, turn_id):
return {"error": "x"}
class WaitTurnTestCase(unittest.TestCase):
def test_first_slice_timeout(self):
# 154: elapsed already past timeout before the first slice.
q = queue.Queue()
with mock.patch.object(
host.time, "monotonic",
side_effect=[100.0, 100.5]):
self.assertEqual(_wait_turn(q, 0.1), ("timeout", None))
class RunTurnInterruptRaiseTestCase(unittest.TestCase):
def test_interrupt_turn_raises(self):
# 777-780
srv = _server()
srv.request = mock.Mock(return_value={
"result": {"turn": {"id": "turn1"}}})
srv.interrupt_turn = mock.Mock(
side_effect=RuntimeError("interrupt down"))
with mock.patch.object(host, "_wait_turn",
return_value=("interrupted-requested",
None)):
out = srv.run_turn("th1", "do work")
self.assertEqual(out["status"], "interrupted")
class MainLeftoverTestCase(unittest.TestCase):
def _state_cfg(self, tmp, tasks):
state_path = os.path.join(tmp, "host-state.json")
cfg_path = os.path.join(tmp, "host-config.json")
with open(state_path, "w", encoding="utf-8") as f:
json.dump({"tasks": tasks, "automations": []}, f)
with open(cfg_path, "w", encoding="utf-8") as f:
json.dump({}, f)
return state_path, cfg_path
def _run_main(self, argv, patches=()):
with contextlib.ExitStack() as stack:
for p in patches:
stack.enter_context(p)
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
with mock.patch.object(sys, "argv", argv):
host.main()
return buf.getvalue()
def test_daemon_stop_inner_break(self):
# 3407-3408: SIGTERM succeeds, the liveness probe then raises.
# (A real sleep child would stay a zombie until reaped, so the
# probe would succeed and the break would never fire; the
# kill calls are therefore faked for this branch.)
def _fake_kill(pid, sig):
if sig == 0:
raise ProcessLookupError("gone")
with tempfile.TemporaryDirectory() as tmp:
state_path, cfg_path = self._state_cfg(tmp, {})
pidfile = os.path.join(tmp, "host-daemon.pid")
with open(pidfile, "w", encoding="utf-8") as f:
f.write("12345")
argv = ["host.py", "--state", state_path,
"--config", cfg_path, "daemon-stop"]
out = self._run_main(
argv, patches=[mock.patch("os.kill",
side_effect=_fake_kill)])
self.assertIn("daemon 已停", out)
self.assertFalse(os.path.exists(pidfile))
def test_daemon_subcommand_failure(self):
# 3504-3505
with tempfile.TemporaryDirectory() as tmp:
state_path, cfg_path = self._state_cfg(tmp, {})
argv = ["host.py", "--state", state_path,
"--config", cfg_path, "daemon", "--foreground",
"--interval", "300"]
old_term = signal.getsignal(signal.SIGTERM)
old_int = signal.getsignal(signal.SIGINT)
def sender():
time.sleep(0.5)
try:
os.kill(os.getpid(), signal.SIGTERM)
except Exception:
pass
sender_thread = threading.Thread(target=sender, daemon=True)
try:
sender_thread.start()
out = self._run_main(argv, patches=[
mock.patch("subprocess.run",
side_effect=RuntimeError("child down")),
])
finally:
sender_thread.join(timeout=5)
signal.signal(signal.SIGTERM, old_term)
signal.signal(signal.SIGINT, old_int)
self.assertIn("daemon subcommand", out)
def test_interrupt_poll_responded(self):
# 3963-3964
with tempfile.TemporaryDirectory() as tmp:
state_path, cfg_path = self._state_cfg(tmp, {
"t1": {"name": "t1", "thread_id": "th1",
"status": "interrupted",
"turns": [{"result": {"turn_id": "turn9"}}]}})
argv = ["host.py", "--state", state_path,
"--config", cfg_path, "interrupt", "--task", "t1"]
out = self._run_main(argv, patches=[
mock.patch.object(host, "Server",
lambda **kw: _FakeServer()),
mock.patch("time.sleep", lambda *a, **k: None),
])
self.assertIn("属主已响应", out)
def test_interrupt_poll_read_failure(self):
# 3965-3967 (+ else branch): state reads fail during polling.
with tempfile.TemporaryDirectory() as tmp:
state_path, cfg_path = self._state_cfg(tmp, {
"t1": {"name": "t1", "thread_id": "th1",
"status": "running",
"turns": [{"result": {"turn_id": "turn9"}}]}})
argv = ["host.py", "--state", state_path,
"--config", cfg_path, "interrupt", "--task", "t1"]
real_load = json.load
calls = {"n": 0}
def _load(fp, *a, **k):
calls["n"] += 1
if calls["n"] <= 2:
return real_load(fp, *a, **k)
raise RuntimeError("poll read down")
out = self._run_main(argv, patches=[
mock.patch.object(host, "Server",
lambda **kw: _FakeServer()),
mock.patch("time.sleep", lambda *a, **k: None),
mock.patch("json.load", side_effect=_load),
])
self.assertIn("10 秒内未响应", out)
def test_goal_verify_argv_warning(self):
# 4025
with tempfile.TemporaryDirectory() as tmp:
state_path, cfg_path = self._state_cfg(tmp, {})
argv = ["host.py", "--state", state_path,
"--config", cfg_path, "goal", "--name", "g",
"--task", "t", "--repo", "/tmp",
"--verify", "echo hi", "--verify-argv", "echo", "hi"]
out = self._run_main(argv, patches=[
mock.patch.object(host, "Server",
lambda **kw: _FakeServer()),
mock.patch.object(host, "dispatch",
return_value={"name": "g"}),
mock.patch.object(host, "run_goal_loop",
return_value=None),
])
self.assertIn("verify_argv 已指定", out)
if __name__ == "__main__":
unittest.main()