code-to10 v6 A8 worker: worker gap tests (auto-memories, tenant skill failure, prepare-env failures, pr-job failures, run-loop drain)

(cherry picked from commit 903bd560bf2b5796daf4b86dacc29b0732513607)
This commit is contained in:
host committed 2026-11-03 02:49:26 +00:00
1 parent 4919e107a6
commit 0d51c1d334
1 file changed
+425
+425
View File
@@ -0,0 +1,425 @@
"""code-to10 v6 A8:worker 缺口补测。
补测基线(v5 头 10023fb 全量覆盖率,cloud/worker.py 3402
语句缺 338 行,90%):本文件按“假执行器/假派发”补下列
区间,其余缺口行以全量复测为准:
- 2092-2095:process_auto_memories 的 extracted 项审计。
- 2936-2944:_tenant_dirs 技能播种失败只记日志不阻塞,
且日志本身抛错也被吞掉。
- 3092-3111(及 checkout 失败行):_prepare_environment
的 clone/非 git/get-url/fetch/checkout 失败分支。
- 6626-6652:_run_pr_jobs 的 PrSkipped/PrStepError/通用
异常三路失败写回。
- 6977-7010:run_loop 退出排空(杀 proc、杀容器、标
interrupted、审计),含 project_id 为 NULL 与审计查询
抛错两路 _pid 回落。
绝不碰(任务口径不碰项,单列):
- claim/goal_resume_pending 的 except-pass 分支。
- MCP handler-error 通知分支。
只加测试,不改生产代码;假的是执行器/派发/播种等外部
依赖,不是被测代码本身;不加 no-coverage pragma、不 skip。
"""
import os
import sqlite3
import sys
import tempfile
import threading
import unittest
from pathlib import Path
from types import SimpleNamespace
try:
import cloud.worker as worker_mod
from cloud.db import connect as _db_connect
from cloud.pr import PrSkipped, PrStepError
from cloud.pr_exec import enqueue_pr_job
from cloud.server import init_db
from cloud.worker import Worker
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
import cloud.worker as worker_mod
from cloud.db import connect as _db_connect
from cloud.pr import PrSkipped, PrStepError
from cloud.pr_exec import enqueue_pr_job
from cloud.server import init_db
from cloud.worker import Worker
class _FakeSync:
"""同步假执行器:不走 Local/Docker/Cloud,只给 run_loop
的排空路径一个带 kill_container 的执行器替身。"""
def dispatch_task(self, task):
return {"status": "completed"}
def run_task(self, task):
return {"status": "completed"}
def kill_container(self, name):
raise RuntimeError("kill boom")
class _FakeProc:
"""pid 不存在的假进程:_kill_proc_group 在 getpgid 处即
返回(不真杀),wait 抛错走排空的 except 分支。"""
pid = 999999
def wait(self, timeout=None):
raise RuntimeError("wait boom")
class _RaisingLogger:
"""warning 一律抛错的假 logger:测 _tenant_dirs 里
“日志失败也被吞掉”的内层 except。"""
def warning(self, *args, **kwargs):
raise RuntimeError("log boom")
def info(self, *args, **kwargs):
pass
def debug(self, *args, **kwargs):
pass
def error(self, *args, **kwargs):
pass
def _insert_task(db_path, task_id, project_id="p1", status="queued"):
conn = sqlite3.connect(db_path)
try:
conn.execute(
"INSERT INTO tasks(id, project_id, prompt, status,"
" idempotency_key, depends_on, priority, created_by,"
" created_at, updated_at, last_heartbeat)"
" VALUES(?, ?, ?, ?, NULL, '[]', 0, 'tester', 0, 0, NULL)",
(task_id, project_id, "do work", status),
)
conn.commit()
finally:
conn.close()
def _row(db_path, sql):
conn = sqlite3.connect(db_path)
try:
return conn.execute(sql).fetchone()
finally:
conn.close()
class WorkerGapsBase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
self.hb = os.path.join(self.tmp.name, "hb.json")
def _worker(self, **kwargs):
kwargs.setdefault("env", {"XCM_API_KEY": "test-placeholder"})
kwargs.setdefault("heartbeat_file", self.hb)
kwargs.setdefault("max_retries", 0)
kwargs.setdefault("retry_backoff_seconds", 0)
return Worker(self.db_path, "/fake/host.py", **kwargs)
class ProcessAutoMemoriesTestCase(WorkerGapsBase):
def test_extracted_item_audited(self):
worker = self._worker()
orig = worker_mod._auto_process_due
worker_mod._auto_process_due = lambda db_path, **kw: [
{"status": "extracted", "task_id": "T1"},
{"status": "other", "task_id": "T2"},
]
try:
results = worker.process_auto_memories()
finally:
worker_mod._auto_process_due = orig
self.assertEqual(len(results), 2)
row = _row(self.db_path,
"SELECT action, detail FROM audit_log"
" WHERE action = 'worker_memory_extracted'")
self.assertEqual(row, ("worker_memory_extracted", "T1"))
def test_sweep_failure_returns_empty(self):
worker = self._worker()
orig = worker_mod._auto_process_due
worker_mod._auto_process_due = lambda db_path, **kw: (_ for _ in ()).throw(
RuntimeError("sweep boom"))
try:
self.assertEqual(worker.process_auto_memories(), [])
finally:
worker_mod._auto_process_due = orig
class TenantDirsSkillFailureTestCase(WorkerGapsBase):
def _seeded_worker(self):
tenants = os.path.join(self.tmp.name, "tenants")
template_home = os.path.join(self.tmp.name, "template-home")
os.makedirs(template_home, exist_ok=True)
with open(os.path.join(template_home, "config.toml"), "w") as f:
f.write("# template\n")
worker = self._worker(tenants_root=tenants,
tenant_template_home=template_home,
seed_skills=True)
# 预置 codex-home 配置与非空 test-repo:播种分支只测
# 技能失败本身,不走到 test-repo 模板复制/报错。
base = os.path.join(tenants, "p1")
os.makedirs(os.path.join(base, "codex-home"), exist_ok=True)
with open(os.path.join(base, "codex-home", "config.toml"),
"w") as f:
f.write("# existing\n")
os.makedirs(os.path.join(base, "test-repo"), exist_ok=True)
with open(os.path.join(base, "test-repo", "sample.py"),
"w") as f:
f.write("# sample\n")
return worker
def test_skill_failure_only_logged_not_blocking(self):
worker = self._seeded_worker()
orig = worker_mod._ensure_skills
worker_mod._ensure_skills = lambda home: (_ for _ in ()).throw(
RuntimeError("skill boom"))
try:
dirs = worker._tenant_dirs("p1")
finally:
worker_mod._ensure_skills = orig
self.assertTrue(os.path.isdir(dirs["test_repo"]))
self.assertTrue(os.path.exists(
os.path.join(dirs["test_repo"], "sample.py")))
def test_skill_failure_and_logger_failure_swallowed(self):
worker = self._seeded_worker()
worker.logger = _RaisingLogger()
orig = worker_mod._ensure_skills
worker_mod._ensure_skills = lambda home: (_ for _ in ()).throw(
RuntimeError("skill boom"))
try:
dirs = worker._tenant_dirs("p1")
finally:
worker_mod._ensure_skills = orig
self.assertTrue(os.path.isdir(dirs["work"]))
class PrepareEnvironmentTestCase(WorkerGapsBase):
def _env_worker(self, runner):
repos = os.path.join(self.tmp.name, "repos")
return self._worker(repos_dir=repos, repos_runner=runner), repos
def test_clone_failure(self):
def runner(cmd):
return SimpleNamespace(returncode=1, stdout="",
stderr="clone boom")
worker, _repos = self._env_worker(runner)
err = worker._prepare_environment(
{"id": "env1", "repo_url": "https://example.invalid/x.git"})
self.assertIn("git clone 失败", err)
self.assertIn("clone boom", err)
def test_existing_dir_not_a_git_repo(self):
worker, repos = self._env_worker(lambda cmd: SimpleNamespace(
returncode=0, stdout="", stderr=""))
target = os.path.join(repos, "envs", "env2")
os.makedirs(target, exist_ok=True)
err = worker._prepare_environment(
{"id": "env2", "repo_url": "https://example.invalid/x.git"})
self.assertIn("不是 git 仓库", err)
def _git_repo_worker(self, behaviour):
worker, repos = self._env_worker(behaviour)
target = os.path.join(repos, "envs", "env3")
os.makedirs(os.path.join(target, ".git"), exist_ok=True)
return worker
def test_get_url_failure(self):
def runner(cmd):
return SimpleNamespace(returncode=1, stdout="",
stderr="no remote")
worker = self._git_repo_worker(runner)
err = worker._prepare_environment(
{"id": "env3", "repo_url": "https://example.invalid/x.git"})
self.assertIn("取不到 remote origin", err)
def test_origin_mismatch(self):
def runner(cmd):
if "get-url" in cmd:
return SimpleNamespace(
returncode=0, stdout="https://other.invalid/y.git\n",
stderr="")
return SimpleNamespace(returncode=0, stdout="", stderr="")
worker = self._git_repo_worker(runner)
err = worker._prepare_environment(
{"id": "env3", "repo_url": "https://example.invalid/x.git"})
self.assertIn("origin 与环境 repo_url 不一致", err)
def test_fetch_failure(self):
def runner(cmd):
if "get-url" in cmd:
return SimpleNamespace(
returncode=0,
stdout="https://example.invalid/x.git\n", stderr="")
if "fetch" in cmd:
return SimpleNamespace(returncode=1, stdout="",
stderr="fetch boom")
return SimpleNamespace(returncode=0, stdout="", stderr="")
worker = self._git_repo_worker(runner)
err = worker._prepare_environment(
{"id": "env3", "repo_url": "https://example.invalid/x.git"})
self.assertIn("git fetch origin 失败", err)
self.assertIn("fetch boom", err)
def test_checkout_failure_and_success(self):
def runner(cmd):
if "get-url" in cmd:
return SimpleNamespace(
returncode=0,
stdout="https://example.invalid/x.git\n", stderr="")
if "fetch" in cmd:
return SimpleNamespace(returncode=0, stdout="", stderr="")
if "checkout" in cmd:
return SimpleNamespace(returncode=1, stdout="",
stderr="no such branch")
return SimpleNamespace(returncode=0, stdout="", stderr="")
worker = self._git_repo_worker(runner)
env = {"id": "env3", "repo_url": "https://example.invalid/x.git",
"branch": "nope"}
err = worker._prepare_environment(env)
self.assertIn("git checkout nope 失败", err)
self.assertIn("no such branch", err)
# 同一 runner 改走成功 checkout:返回 None(准备完成)。
def ok_runner(cmd):
return SimpleNamespace(returncode=0,
stdout="https://example.invalid/x.git\n",
stderr="")
worker.repos_runner = ok_runner
self.assertIsNone(worker._prepare_environment(env))
class RunPrJobsFailureTestCase(WorkerGapsBase):
def _pr_worker(self, failure):
worker = self._worker(env_key="TEST_KEY",
host_name="elsewhere-host-1")
conn = _db_connect(self.db_path)
try:
worker._ensure_result_columns(conn)
conn.execute(
"INSERT INTO tasks(id, project_id, prompt, status,"
" depends_on, priority, created_by, created_at,"
" updated_at, last_heartbeat) VALUES('t1', 'p1', 'x',"
" 'merged', '[]', 0, 'system', 0, 0, NULL)")
conn.commit()
enqueue_pr_job(conn, "t1", "p1", "elsewhere-host-1")
finally:
conn.close()
return worker
def _run_with_failure(self, failure):
worker = self._pr_worker(failure)
orig = worker_mod.execute_task_pr
def _fake(db_path, task_id, user_id, **kwargs):
raise failure
worker_mod.execute_task_pr = _fake
try:
results = worker._run_pr_jobs()
finally:
worker_mod.execute_task_pr = orig
return results
def test_pr_skipped_marks_failed(self):
results = self._run_with_failure(PrSkipped("no longer merged"))
self.assertEqual(len(results), 1)
self.assertEqual(results[0]["status"], "failed")
self.assertIn("skipped: no longer merged", results[0]["error"])
row = _row(self.db_path,
"SELECT status FROM pr_jobs WHERE task_id = 't1'")
self.assertEqual(row[0], "failed")
def test_pr_step_error_marks_failed(self):
results = self._run_with_failure(
PrStepError("push", "remote denied"))
self.assertEqual(results[0]["error"], "push: remote denied")
row = _row(self.db_path,
"SELECT status FROM pr_jobs WHERE task_id = 't1'")
self.assertEqual(row[0], "failed")
def test_generic_error_marks_failed(self):
results = self._run_with_failure(RuntimeError("boom"))
self.assertEqual(results[0]["error"], "boom")
row = _row(self.db_path,
"SELECT status FROM pr_jobs WHERE task_id = 't1'")
self.assertEqual(row[0], "failed")
class RunLoopDrainTestCase(WorkerGapsBase):
def _drain_worker(self, task_id, project_id):
_insert_task(self.db_path, task_id, project_id=project_id,
status="running")
worker = self._worker(executor=_FakeSync())
worker._running_set(task_id, proc=_FakeProc(), container="c1")
worker.executor.kill_container = lambda name: (_ for _ in ()).throw(
RuntimeError("kill boom"))
return worker
def _run_drain(self, worker):
stop = threading.Event()
stop.set()
results = worker.run_loop(interval=10, drain_timeout=0,
stop_event=stop)
self.assertEqual(results, [])
return worker
def test_drain_kills_proc_and_container_marks_interrupted(self):
worker = self._drain_worker("t-drain", "p1")
self._run_drain(worker)
row = _row(self.db_path,
"SELECT status, interrupted_reason FROM tasks"
" WHERE id = 't-drain'")
self.assertEqual(row[0], "interrupted")
self.assertEqual(row[1], "worker_shutdown")
audit = _row(self.db_path,
"SELECT action, detail FROM audit_log"
" WHERE action = 'worker_shutdown_interrupted'")
self.assertEqual(audit,
("worker_shutdown_interrupted", "t-drain"))
def test_drain_null_project_id_audits_empty_project(self):
worker = self._drain_worker("t-null", None)
self._run_drain(worker)
audit = _row(self.db_path,
"SELECT action, detail FROM audit_log"
" WHERE action = 'worker_shutdown_interrupted'"
" AND detail = 't-null'")
self.assertIsNotNone(audit)
def test_drain_audit_lookup_failure_still_interrupts(self):
worker = self._drain_worker("t-dberr", "p1")
orig_connect = worker_mod._db_connect
import inspect
def _guarded(db_path):
# 只有 run_loop 本体直接发起的审计查询才抛错:
# _set_status/_mark_interrupted_reason 调 _db_connect
# 时直接调用方是各自方法名,不受影响;排空审计的
# except 分支据此覆盖。
caller = inspect.currentframe().f_back
if caller is not None and \
caller.f_code.co_name == "run_loop":
raise RuntimeError("db gone")
return orig_connect(db_path)
worker_mod._db_connect = _guarded
try:
self._run_drain(worker)
finally:
worker_mod._db_connect = orig_connect
row = _row(self.db_path,
"SELECT status FROM tasks WHERE id = 't-dberr'")
self.assertEqual(row[0], "interrupted")
if __name__ == "__main__":
unittest.main()