Files
codex-app-server/tests/test_v2cov_gaps_c.py
T
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

503 lines
20 KiB
Python

"""Coverage batch v4-C: host.py state/lock/worktree/verify/notify/
pr/dispatch/goal-loop branches.
Stubbing discipline: the function under test is never stubbed.
Patched collaborators are: socket.gethostname / os.getmtime / os.rmdir /
os.replace / log_event / save_state / build_verify_audit_report /
subprocess.run / gh runner / FakeServer (all sinks or data providers),
plus host.HERE (a path constant) and os.environ git identity vars for
the real-git worktree tests. Every assertion reads a real return value,
a real file, or real stdout.
"""
import contextlib
import io
import json
import os
import socket
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
import host
from host import (
_audit_verify,
_cmd_verify_audit,
_seed_worktree,
_state_lock,
_state_tmp_path,
build_verify_audit_report,
copy_tree_excluding_git,
dispatch,
log_event,
make_worktree,
merge_pr,
notify_failure,
parse_pr_checks,
recover_interrupted,
restore_state,
run_gated_verify,
run_gh,
run_goal_loop,
run_health_check,
run_task_turn,
run_verify_argv,
save_state,
task_diff,
)
GIT_ENV = {
"GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t",
"GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t",
"GIT_CONFIG_NOSYSTEM": "1",
}
def _git(args, cwd, env_extra=None):
env = dict(os.environ)
env.update(GIT_ENV)
if env_extra:
env.update(env_extra)
return subprocess.run(["git"] + args, cwd=cwd, capture_output=True,
text=True, env=env)
def _init_repo(path):
os.makedirs(path, exist_ok=True)
_git(["init", "-q"], path)
with open(os.path.join(path, "f.txt"), "w") as fh:
fh.write("base\n")
_git(["add", "-A"], path)
_git(["commit", "-qm", "init"], path)
class StateLockTestCase(unittest.TestCase):
def test_state_tmp_path_socket_failure(self):
with mock.patch.object(socket, "gethostname",
side_effect=OSError("no hostname")):
out = _state_tmp_path("/tmp/state.json")
self.assertIn("unknown", out)
def test_state_lock_getmtime_failure_timeout(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
os.mkdir(path + ".lockdir")
with mock.patch.object(host, "_STATE_LOCK_TIMEOUT", 0), \
mock.patch("os.path.getmtime",
side_effect=OSError("gone")):
with self.assertRaises(RuntimeError):
with _state_lock(path):
pass
def test_state_lock_stale_rmdir_once_failure(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
lockdir = path + ".lockdir"
os.mkdir(lockdir)
old = time.time() - 100000
os.utime(lockdir, (old, old))
real_rmdir = os.rmdir
calls = []
def _flaky(p):
calls.append(p)
if len(calls) == 1:
raise OSError("someone else")
return real_rmdir(p)
with mock.patch("os.rmdir", side_effect=_flaky):
with _state_lock(path):
pass
self.assertFalse(os.path.exists(lockdir))
def test_state_lock_final_rmdir_failure_swallowed(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
with _state_lock(path):
os.rmdir(path + ".lockdir")
self.assertFalse(os.path.exists(path + ".lockdir"))
class RestoreRecoverSaveLogTestCase(unittest.TestCase):
def test_restore_state_replace_failure(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
with open(path, "w") as fh:
fh.write("{broken")
with mock.patch("os.replace",
side_effect=RuntimeError("rename down")):
ok, msg = restore_state(path)
self.assertFalse(ok)
self.assertIn("改名留底失败", msg)
def test_restore_state_no_file_no_backups(self):
# 1047 行(corrupt=None)只在“严格读失败、但复查时文件已
# 不在”这一竞态下可达:单线程里文件缺失时严格读直接返回
# 空 state、无需恢复;这里用 exists 的调用序列注入该竞态。
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
with open(path, "w") as fh:
fh.write("{broken")
real_exists = os.path.exists
calls = []
def _exists(p):
calls.append(p)
if len(calls) == 1:
return True
return False
with mock.patch("os.path.exists", side_effect=_exists):
ok, msg = restore_state(path)
self.assertFalse(ok)
self.assertIn("没有可用的备份", msg)
def test_recover_interrupted_log_failure(self):
state = {"tasks": {"t": {"status": "running",
"last_turn_at": 0, "created_at": 0}}}
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")):
out = recover_interrupted(state, "/tmp/state.json")
self.assertEqual(out, ["t"])
self.assertEqual(state["tasks"]["t"]["status"], "interrupted")
def test_save_state_conflict_log_failure(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
with open(path, "w") as fh:
json.dump({"version": 5, "tasks": {}}, fh)
os.mkdir(os.path.join(d, "host-run.log"))
state = {"tasks": {}, "_loaded_version": 1}
save_state(path, state)
with open(path) as fh:
saved = json.load(fh)
self.assertEqual(saved["version"], 6)
def test_save_state_last_writer_socket_failure(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
with mock.patch.object(socket, "gethostname",
side_effect=OSError("no hostname")):
save_state(path, {"tasks": {}})
with open(path) as fh:
saved = json.load(fh)
self.assertEqual(saved["last_writer"], str(os.getpid()))
def test_log_event_failure_swallowed(self):
with tempfile.TemporaryDirectory() as d:
blocker = os.path.join(d, "blocker")
with open(blocker, "w") as fh:
fh.write("x")
log_event(os.path.join(blocker, "state.json"), "hi")
def test_health_check_pid_alive_and_close_failure(self):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")
with open(path, "w") as fh:
json.dump({"version": 1, "tasks": {}}, fh)
with open(os.path.join(d, "host-daemon.pid"), "w") as fh:
fh.write(str(os.getpid()))
server = mock.Mock()
server.list_threads.return_value = {"data": []}
server.close.side_effect = RuntimeError("close down")
out = run_health_check(path, {}, sys.executable, d, "a",
server_factory=lambda: server)
self.assertTrue(out["checks"]["daemon_alive"])
self.assertEqual(out["checks"]["daemon_pid"], os.getpid())
self.assertTrue(out["checks"]["server_ok"])
class WorktreeDiffTestCase(unittest.TestCase):
def test_make_worktree_existing(self):
with tempfile.TemporaryDirectory() as d:
with mock.patch.object(host, "HERE", d):
base = os.path.join(d, "work", "host-worktrees")
os.makedirs(os.path.join(base, "t1"))
path, branch = make_worktree("/tmp/repo", "t1")
self.assertEqual(path, os.path.join(base, "t1"))
self.assertIsNone(branch)
def test_make_worktree_branch_fallback(self):
with tempfile.TemporaryDirectory() as d:
repo = os.path.join(d, "repo")
_init_repo(repo)
_git(["branch", "host/t1"], repo)
with mock.patch.object(host, "HERE", d):
path, branch = make_worktree(repo, "t1")
self.assertEqual(branch, "host/t1")
self.assertTrue(os.path.isdir(path))
def test_make_worktree_both_fail(self):
with tempfile.TemporaryDirectory() as d:
repo = os.path.join(d, "repo")
os.makedirs(repo)
_git(["init", "-q"], repo)
with mock.patch.object(host, "HERE", d):
with self.assertRaises(RuntimeError):
make_worktree(repo, "t1")
def test_make_worktree_copy_non_git(self):
with tempfile.TemporaryDirectory() as d:
repo = os.path.join(d, "repo")
os.makedirs(repo)
with open(os.path.join(repo, "a.txt"), "w") as fh:
fh.write("hi")
with mock.patch.object(host, "HERE", d):
path, branch = make_worktree(repo, "t1")
self.assertIsNone(branch)
with open(os.path.join(path, "a.txt")) as fh:
self.assertEqual(fh.read(), "hi")
def test_task_diff_unreadable(self):
with tempfile.TemporaryDirectory() as d:
repo = os.path.join(d, "repo")
_init_repo(repo)
with open(os.path.join(repo, "bin.dat"), "wb") as fh:
fh.write(b"\xff\xfe\x00abc")
out = task_diff(repo)
self.assertEqual(out["untracked_contents"]["bin.dat"],
"<binary or unreadable>")
class VerifyAuditTestCase(unittest.TestCase):
def test_audit_verify_log_failure(self):
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")):
_audit_verify("/tmp/state.json", "true", "argv", 0)
def test_run_verify_argv_empty(self):
self.assertEqual(run_verify_argv([], "/tmp"), (0, ""))
def test_run_verify_argv_non_str(self):
with self.assertRaises(TypeError):
run_verify_argv(["echo", 123], "/tmp")
def test_run_gated_verify_blank(self):
self.assertEqual(run_gated_verify(" ", "/tmp"), (0, ""))
def test_build_report_non_dict_task(self):
report = build_verify_audit_report(
{"tasks": {"bad": "str",
"ok": {"verify_command": None}}})
self.assertEqual([r["task"] for r in report], [])
def test_cmd_verify_audit_notes_and_simple(self):
rows = [
{"task": "n", "kind": "shell", "features": ["f"],
"reason": "", "argv": None, "notes": ["note1"],
"applied": False},
{"task": "s", "kind": "simple", "features": [],
"reason": "", "argv": ["true"], "notes": [],
"applied": False},
]
args = SimpleNamespace(apply=False, json=False, state="st.json")
buf = io.StringIO()
with mock.patch.object(host, "build_verify_audit_report",
return_value=rows), \
contextlib.redirect_stdout(buf):
_cmd_verify_audit(args, {"tasks": {}})
out = buf.getvalue()
self.assertIn("notes=note1", out)
self.assertIn("[可转 argv=['true']]", out)
def test_cmd_verify_audit_json(self):
args = SimpleNamespace(apply=False, json=True, state="st.json")
buf = io.StringIO()
with mock.patch.object(host, "build_verify_audit_report",
return_value=[]), \
contextlib.redirect_stdout(buf):
_cmd_verify_audit(args, {"tasks": {}})
parsed = json.loads(buf.getvalue())
self.assertEqual(parsed["applied"], 0)
self.assertEqual(parsed["tasks"], [])
class NotifyTestCase(unittest.TestCase):
def _state_path(self, d):
return os.path.join(d, "state.json")
def test_notify_no_command_log_failure(self):
with tempfile.TemporaryDirectory() as d:
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")):
msg = notify_failure(self._state_path(d), {}, "t1",
{"status": "failed", "text": "boom"})
self.assertTrue(msg.startswith("FAILED t1"))
def test_notify_template_error_log_failure(self):
with tempfile.TemporaryDirectory() as d:
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")):
msg = notify_failure(
self._state_path(d), {"notify_command": "echo {unknown}"},
"t1", {"status": "failed", "text": "boom"})
self.assertTrue(msg.startswith("FAILED t1"))
def test_notify_rc_log_failure(self):
with tempfile.TemporaryDirectory() as d:
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")):
msg = notify_failure(
self._state_path(d), {"notify_command": "true"},
"t1", {"status": "failed", "text": "boom"})
self.assertTrue(msg.startswith("FAILED t1"))
def test_notify_timeout_log_failure(self):
with tempfile.TemporaryDirectory() as d:
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")), \
mock.patch("subprocess.run",
side_effect=subprocess.TimeoutExpired(
"cmd", 30)):
msg = notify_failure(
self._state_path(d), {"notify_command": "sleep 60"},
"t1", {"status": "failed", "text": "boom"})
self.assertTrue(msg.startswith("FAILED t1"))
def test_notify_generic_error_log_failure(self):
with tempfile.TemporaryDirectory() as d:
with mock.patch.object(host, "log_event",
side_effect=RuntimeError("log down")), \
mock.patch("subprocess.run",
side_effect=RuntimeError("spawn down")):
msg = notify_failure(
self._state_path(d), {"notify_command": "true"},
"t1", {"status": "failed", "text": "boom"})
self.assertTrue(msg.startswith("FAILED t1"))
class PrDispatchTurnTestCase(unittest.TestCase):
def test_parse_pr_checks_branches(self):
checks, all_pass = parse_pr_checks("foo\t\nrandom words\npass\n")
self.assertEqual(checks, [{"name": "pass", "state": "pass"}])
self.assertTrue(all_pass)
def test_merge_pr_gh_failure(self):
def _runner(args):
if args[1] == "checks":
return SimpleNamespace(returncode=0, stdout="ci\tpass\n",
stderr="")
return SimpleNamespace(returncode=1, stdout="",
stderr="merge failed")
task = {"name": "t", "pr_number": 7, "pr_url": "u"}
ok, msg = merge_pr(task, gh_runner=_runner, repo="/tmp")
self.assertFalse(ok)
self.assertIn("gh pr merge 失败", msg)
def test_run_gh_real_path_mocked(self):
sentinel = SimpleNamespace(returncode=0, stdout="", stderr="")
with mock.patch("subprocess.run", return_value=sentinel) as run:
out = run_gh(["pr", "checks", "1"], cwd="/tmp")
self.assertIs(out, sentinel)
run.assert_called_once()
def test_copy_tree_skips_pyc(self):
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "src")
dst = os.path.join(d, "dst")
os.makedirs(src)
with open(os.path.join(src, "a.pyc"), "wb") as fh:
fh.write(b"\x00")
with open(os.path.join(src, "b.txt"), "w") as fh:
fh.write("b")
copy_tree_excluding_git(src, dst)
self.assertFalse(os.path.exists(os.path.join(dst, "a.pyc")))
self.assertTrue(os.path.exists(os.path.join(dst, "b.txt")))
def test_dispatch_relative_repo(self):
with tempfile.TemporaryDirectory() as d:
rel = os.path.join(d, "relrepo")
os.makedirs(rel)
server = SimpleNamespace(
create_thread=lambda cwd, **kw: "th1")
state = {"tasks": {}}
with mock.patch.object(host, "HERE", d):
task = dispatch(server, state, "t1", "prompt", "relrepo",
"m", "low", None, None, use_worktree=False)
self.assertEqual(task["repo"], rel)
self.assertEqual(task["cwd"], rel)
def test_run_task_turn_type_error_reraise(self):
class _Server:
def run_turn(self, *args, **kwargs):
raise TypeError("boom")
task = {"thread_id": "th", "sandbox": None, "turns": []}
with self.assertRaises(TypeError):
run_task_turn(_Server(), {"tasks": {}}, task, "p", "m", "low",
interrupt_file="/tmp/if")
def test_seed_worktree_git_skipped(self):
with tempfile.TemporaryDirectory() as d:
seed = os.path.join(d, "seed")
work = os.path.join(d, "work")
os.makedirs(os.path.join(seed, ".git"))
os.makedirs(work)
with open(os.path.join(seed, "f.txt"), "w") as fh:
fh.write("f")
_seed_worktree(work, seed)
self.assertTrue(os.path.exists(os.path.join(work, "f.txt")))
self.assertFalse(os.path.exists(os.path.join(work, ".git")))
class FakeServer:
def run_turn(self, *args, **kwargs):
return {"status": "completed", "text": ""}
class GoalLoopTestCase(unittest.TestCase):
def _setup(self, d):
state_path = os.path.join(d, "state.json")
task = {"name": "g", "thread_id": "x", "cwd": d,
"sandbox": None, "turns": []}
state = {"tasks": {"g": task}, "automations": []}
return state_path, task, state
def test_goal_verify_oserror_interrupt(self):
with tempfile.TemporaryDirectory() as d:
state_path, task, state = self._setup(d)
out = run_goal_loop(
FakeServer(), state, state_path, task, "goal", None, 1,
"m", "low", "workspace-write", cfg={},
verify_argv=["/nonexistent/binary-xyz"])
self.assertTrue(out["interrupted"])
self.assertEqual(task["status"], "interrupted")
def test_goal_save_failure_interrupt(self):
with tempfile.TemporaryDirectory() as d:
state_path, task, state = self._setup(d)
orig = host.save_state
calls = []
def _flaky(path, st):
calls.append(1)
if len(calls) > 1:
raise RuntimeError("save down")
return orig(path, st)
with mock.patch.object(host, "save_state",
side_effect=_flaky):
out = run_goal_loop(
FakeServer(), state, state_path, task, "goal", "true",
1, "m", "low", "workspace-write", cfg={})
self.assertTrue(out["interrupted"])
self.assertEqual(task["status"], "interrupted")
def test_goal_steps_write_failure_swallowed(self):
with tempfile.TemporaryDirectory() as d:
state_path, task, state = self._setup(d)
with open(os.path.join(d, "host-steps"), "w") as fh:
fh.write("blocker")
out = run_goal_loop(
FakeServer(), state, state_path, task, "goal", "true",
1, "m", "low", "workspace-write", cfg={})
self.assertTrue(out["ok"])
if __name__ == "__main__":
unittest.main()