coverage85-step4a: worker run_one/_run_attempt 缺口测试(+34 项)
This commit is contained in:
1 parent
5a0652b346
commit
1c2e4e0aca
1 file changed
+566
@@ -0,0 +1,566 @@
|
|||||||
|
"""覆盖率 78%->85% 第 4 步:cloud/worker.py run_one/_run_attempt
|
||||||
|
剩余缺口的真实行为测试。
|
||||||
|
|
||||||
|
只用临时 SQLite(server.init_db 建表)与同步假执行器:
|
||||||
|
假执行器是执行边界(真实 Local/Docker 执行器的替身),
|
||||||
|
绝不碰真实 sub2api/生产网关、不跑模型;超时与重试成本
|
||||||
|
用 max_retries=0、retry_backoff_seconds=0 控制。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from cloud.sandbox import DockerExecutor
|
||||||
|
from cloud.server import init_db
|
||||||
|
from cloud.worker import Worker
|
||||||
|
|
||||||
|
|
||||||
|
def _insert_task(db_path, task_id, status="queued", **kw):
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
try:
|
||||||
|
cols = {"id": task_id, "project_id": kw.pop("project_id", "proj"),
|
||||||
|
"prompt": kw.pop("prompt", "do it"), "status": status,
|
||||||
|
"created_by": kw.pop("created_by", "owner1"),
|
||||||
|
"created_at": kw.pop("created_at", 1000.0),
|
||||||
|
"updated_at": kw.pop("updated_at", 1000.0)}
|
||||||
|
cols.update(kw)
|
||||||
|
names = ", ".join(cols)
|
||||||
|
placeholders = ", ".join("?" * len(cols))
|
||||||
|
conn.execute("INSERT INTO tasks(%s) VALUES(%s)"
|
||||||
|
% (names, placeholders), tuple(cols.values()))
|
||||||
|
conn.commit()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _row(db_path, sql, params=()):
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
try:
|
||||||
|
return conn.execute(sql, params).fetchall()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _set(db_path, sql, params=()):
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
try:
|
||||||
|
conn.execute(sql, params)
|
||||||
|
conn.commit()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _result(status="completed", stdout="done", **kw):
|
||||||
|
r = {"executor": "local", "status": status, "stdout": stdout,
|
||||||
|
"stderr": "", "returncode": 0, "host_status": None}
|
||||||
|
r.update(kw)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSync:
|
||||||
|
"""同步假执行器:非 Local/Docker/Cloud,走 _run_attempt 的
|
||||||
|
else 分支(dispatch_task(task) / goal_task(task) 单参形)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, dispatch=None, run=None, goal=None):
|
||||||
|
self.dispatch_result = dispatch or _result()
|
||||||
|
self.run_result = run or _result()
|
||||||
|
self.goal_result = goal or _result()
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def dispatch_task(self, task):
|
||||||
|
self.calls.append(("dispatch", task["id"]))
|
||||||
|
return dict(self.dispatch_result)
|
||||||
|
|
||||||
|
def run_task(self, task):
|
||||||
|
self.calls.append(("run", task["id"]))
|
||||||
|
return dict(self.run_result)
|
||||||
|
|
||||||
|
def goal_task(self, task):
|
||||||
|
self.calls.append(("goal", task["id"]))
|
||||||
|
return dict(self.goal_result)
|
||||||
|
|
||||||
|
|
||||||
|
class Worker3Base(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, executor=None, **kw):
|
||||||
|
kw.setdefault("env", {"XCM_API_KEY": "test-placeholder"})
|
||||||
|
kw.setdefault("heartbeat_file", self.hb)
|
||||||
|
kw.setdefault("worker_id", "cov85-worker3")
|
||||||
|
kw.setdefault("max_retries", 0)
|
||||||
|
kw.setdefault("retry_backoff_seconds", 0)
|
||||||
|
return Worker(self.db_path, "/fake/host.py",
|
||||||
|
executor=executor, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class RunOneGatesTest(Worker3Base):
|
||||||
|
def test_missing_key_gate(self):
|
||||||
|
_insert_task(self.db_path, "t-key", status="queued")
|
||||||
|
fake = FakeSync()
|
||||||
|
w = self._worker(executor=fake, env={})
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertTrue(result["dispatched"])
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("Missing environment variable", result["error"])
|
||||||
|
self.assertEqual(_row(self.db_path,
|
||||||
|
"SELECT status FROM tasks WHERE id = 't-key'"),
|
||||||
|
[("failed",)])
|
||||||
|
self.assertEqual(fake.calls, [])
|
||||||
|
|
||||||
|
def test_already_claimed(self):
|
||||||
|
_insert_task(self.db_path, "t-claim", status="running")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
task = w.get_task("t-claim")
|
||||||
|
result = w.run_one(task)
|
||||||
|
self.assertFalse(result["dispatched"])
|
||||||
|
self.assertEqual(result["reason"], "already_claimed")
|
||||||
|
|
||||||
|
def test_quota_gate(self):
|
||||||
|
_insert_task(self.db_path, "t-quota", status="queued")
|
||||||
|
_set(self.db_path,
|
||||||
|
"INSERT INTO user_quotas(user_id, quota_usd_micros,"
|
||||||
|
" updated_at) VALUES('owner1', 0, 0)")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertTrue(result["dispatched"])
|
||||||
|
self.assertEqual(result["status"], "interrupted")
|
||||||
|
self.assertIn("quota_exceeded", result["error"])
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT interrupted_reason FROM tasks WHERE id = 't-quota'"),
|
||||||
|
[("quota",)])
|
||||||
|
|
||||||
|
def test_goal_cap_over_and_zero_old_form(self):
|
||||||
|
for rounds, want in [(101, "超过上限"), (0, "已禁用")]:
|
||||||
|
with self.subTest(rounds=rounds):
|
||||||
|
tid = "t-cap-%s" % rounds
|
||||||
|
_insert_task(self.db_path, tid, status="queued",
|
||||||
|
verify_command="make test",
|
||||||
|
goal_max_rounds=rounds)
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn(want, result["error"])
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT status FROM tasks WHERE id = ?", (tid,)),
|
||||||
|
[("failed",)])
|
||||||
|
|
||||||
|
def test_goal_cap_bad_type_passes_gate(self):
|
||||||
|
# goal_max_rounds 非整数:int() 抛错后 _goal_rounds_int
|
||||||
|
# 为 None,门不拦,照常跑完(门只拦可解析的超限值)。
|
||||||
|
_insert_task(self.db_path, "t-cap-bad", status="queued",
|
||||||
|
verify_command="make test", goal_max_rounds="abc")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
|
||||||
|
def test_gateway_failure_gate(self):
|
||||||
|
_insert_task(self.db_path, "t-gw", status="queued")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
with mock.patch.dict(os.environ, {"CLOUD_GATEWAY": "sub2api",
|
||||||
|
"SUB2API_BASE_URL": ""}):
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("网关准备失败", result["error"])
|
||||||
|
|
||||||
|
def test_plain_resume_message_swaps_prompt(self):
|
||||||
|
_insert_task(self.db_path, "t-resume", status="queued",
|
||||||
|
resume_message="继续说下去")
|
||||||
|
fake = FakeSync()
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(fake.calls, [("dispatch", "t-resume")])
|
||||||
|
# 续跑消息已消费:库里 resume_message 清掉。
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT resume_message FROM tasks WHERE id = 't-resume'"),
|
||||||
|
[(None,)])
|
||||||
|
|
||||||
|
def test_verify_resume_message_is_breakpoint_kind(self):
|
||||||
|
# 旧验收形态 + 续跑消息:prompt 不换、_resume_kind 为
|
||||||
|
# breakpoint,假执行器走 goal_task 一次即完成。
|
||||||
|
_insert_task(self.db_path, "t-resume-v", status="queued",
|
||||||
|
verify_command="make test",
|
||||||
|
resume_message="继续原目标")
|
||||||
|
fake = FakeSync()
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(fake.calls, [("goal", "t-resume-v")])
|
||||||
|
|
||||||
|
def test_goal_resume_pending_fresh_restart(self):
|
||||||
|
_insert_task(self.db_path, "t-grp", status="queued",
|
||||||
|
goal_mode=1, goal_resume_pending=1)
|
||||||
|
fake = FakeSync()
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(fake.calls, [("goal", "t-grp")])
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT goal_resume_pending FROM tasks WHERE id = 't-grp'"),
|
||||||
|
[(0,)])
|
||||||
|
|
||||||
|
|
||||||
|
class RunOneAttemptFlowTest(Worker3Base):
|
||||||
|
def test_completed_simple(self):
|
||||||
|
_insert_task(self.db_path, "t-ok", status="queued")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(result["stdout"], "done")
|
||||||
|
self.assertIsNotNone(result["cost_record_id"])
|
||||||
|
|
||||||
|
def test_failed_simple_error_text(self):
|
||||||
|
_insert_task(self.db_path, "t-fail", status="queued")
|
||||||
|
fake = FakeSync(dispatch=_result(status="failed", stdout="",
|
||||||
|
stderr="boom-err"))
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("boom-err", result["error"])
|
||||||
|
|
||||||
|
def test_timeout_converts_to_failed(self):
|
||||||
|
_insert_task(self.db_path, "t-timeout", status="queued")
|
||||||
|
fake = FakeSync(dispatch=_result(status="timeout", stdout=""))
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("task timeout", result["error"])
|
||||||
|
|
||||||
|
def test_retry_same_model(self):
|
||||||
|
_insert_task(self.db_path, "t-retry", status="queued")
|
||||||
|
fake = FakeSync(dispatch=_result(status="failed", stdout="",
|
||||||
|
stderr="first-fail"))
|
||||||
|
w = self._worker(executor=fake, max_retries=1,
|
||||||
|
retry_backoff_seconds=0)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("[retries=1]", result["error"])
|
||||||
|
self.assertEqual([c[0] for c in fake.calls],
|
||||||
|
["dispatch", "dispatch"])
|
||||||
|
|
||||||
|
def test_fallback_model_second_failure_combines_errors(self):
|
||||||
|
_insert_task(self.db_path, "t-fb", status="queued")
|
||||||
|
calls = {"n": 0}
|
||||||
|
|
||||||
|
class _Fb(FakeSync):
|
||||||
|
def dispatch_task(self, task):
|
||||||
|
calls["n"] += 1
|
||||||
|
if calls["n"] == 1:
|
||||||
|
return _result(status="failed", stdout="",
|
||||||
|
stderr="primary-bad 429",
|
||||||
|
host_status="failed")
|
||||||
|
return _result(status="failed", stdout="",
|
||||||
|
stderr="fallback-bad",
|
||||||
|
host_status="failed")
|
||||||
|
fake = _Fb()
|
||||||
|
w = self._worker(executor=fake, fallback_models=["fb-model"])
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
# 真 fallback 换模型再跑一次:两次原因都写进错误文本。
|
||||||
|
self.assertEqual(calls["n"], 2)
|
||||||
|
self.assertIn("primary model", result["error"])
|
||||||
|
self.assertIn("fallback model", result["error"])
|
||||||
|
self.assertIn("fallback-bad", result["error"])
|
||||||
|
|
||||||
|
def test_fallback_via_token(self):
|
||||||
|
_insert_task(self.db_path, "t-fb2", status="queued")
|
||||||
|
seq = [("failed", "model overloaded 429"),
|
||||||
|
("failed", "second bad")]
|
||||||
|
|
||||||
|
class _Fb2(FakeSync):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.n = 0
|
||||||
|
|
||||||
|
def dispatch_task(self, task):
|
||||||
|
self.n += 1
|
||||||
|
status, err = seq[min(self.n - 1, len(seq) - 1)]
|
||||||
|
return _result(status=status, stdout="",
|
||||||
|
stderr=err, host_status="failed")
|
||||||
|
fake = _Fb2()
|
||||||
|
w = self._worker(executor=fake, fallback_models=["fb-model"])
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
# 真 fallback 走过时错误文本含两次原因(或至少第二次)。
|
||||||
|
self.assertEqual(fake.n, 2)
|
||||||
|
self.assertIn("fallback model", result["error"])
|
||||||
|
|
||||||
|
def test_cancelled_via_result(self):
|
||||||
|
_insert_task(self.db_path, "t-cancel", status="queued")
|
||||||
|
fake = FakeSync(dispatch=_result(status="cancelled", stdout=""))
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "interrupted")
|
||||||
|
self.assertEqual(result["error"], "cancelled")
|
||||||
|
|
||||||
|
def test_goal_already_exists_falls_back_to_resume(self):
|
||||||
|
# goal 首轮 stdout 含“已存在”:run_one 走一次断点续跑,
|
||||||
|
# 续跑完成即最终完成(不直接 failed)。
|
||||||
|
_insert_task(self.db_path, "t-goal-exists", status="queued",
|
||||||
|
goal_mode=1)
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
class _GoalEx(FakeSync):
|
||||||
|
def goal_task(self, task):
|
||||||
|
calls.append(task["id"])
|
||||||
|
if len(calls) == 1:
|
||||||
|
return _result(status="failed", stdout="任务已存在")
|
||||||
|
return _result(status="completed", stdout="resumed-ok")
|
||||||
|
w = self._worker(executor=_GoalEx())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(result["stdout"], "resumed-ok")
|
||||||
|
self.assertEqual(len(calls), 2)
|
||||||
|
|
||||||
|
def test_goal_met_false_notifies_goal_unmet(self):
|
||||||
|
_insert_task(self.db_path, "t-goal-unmet", status="queued",
|
||||||
|
goal_mode=1)
|
||||||
|
fake = FakeSync(goal=_result(status="completed", stdout="",
|
||||||
|
goal_met=False))
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
actions = _row(self.db_path,
|
||||||
|
"SELECT action FROM audit_log ORDER BY id")
|
||||||
|
self.assertIn(("worker_completed",), actions)
|
||||||
|
|
||||||
|
def test_followup_goal_convert_queues_task(self):
|
||||||
|
_insert_task(self.db_path, "t-fu", status="queued")
|
||||||
|
_set(self.db_path,
|
||||||
|
"INSERT INTO task_followups(id, task_id, project_id, body,"
|
||||||
|
" status, created_at) VALUES('fu1', 't-fu', 'proj',"
|
||||||
|
" '/goal 收齐周报', 'pending', 1000)")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "queued")
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT status FROM tasks WHERE id = 't-fu'"), [("queued",)])
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT status FROM task_followups WHERE id = 'fu1'"),
|
||||||
|
[("done",)])
|
||||||
|
|
||||||
|
def test_followup_plain_message_runs_second_round(self):
|
||||||
|
_insert_task(self.db_path, "t-fu2", status="queued")
|
||||||
|
_set(self.db_path,
|
||||||
|
"INSERT INTO task_followups(id, task_id, project_id, body,"
|
||||||
|
" status, created_at) VALUES('fu2', 't-fu2', 'proj',"
|
||||||
|
" '再补充一点', 'pending', 1000)")
|
||||||
|
fake = FakeSync()
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(len(fake.calls), 2)
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT status FROM task_followups WHERE id = 'fu2'"),
|
||||||
|
[("done",)])
|
||||||
|
|
||||||
|
def test_followup_failure_stops_drain(self):
|
||||||
|
_insert_task(self.db_path, "t-fu3", status="queued")
|
||||||
|
_set(self.db_path,
|
||||||
|
"INSERT INTO task_followups(id, task_id, project_id, body,"
|
||||||
|
" status, created_at) VALUES('fu3', 't-fu3', 'proj',"
|
||||||
|
" '再补充一点', 'pending', 1000)")
|
||||||
|
|
||||||
|
class _FuFail(FakeSync):
|
||||||
|
def dispatch_task(self, task):
|
||||||
|
self.calls.append(("dispatch", task["id"]))
|
||||||
|
if len(self.calls) == 1:
|
||||||
|
return _result()
|
||||||
|
return _result(status="failed", stdout="",
|
||||||
|
stderr="fu-bad")
|
||||||
|
w = self._worker(executor=_FuFail())
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("fu-bad", result["error"])
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT status FROM task_followups WHERE id = 'fu3'"),
|
||||||
|
[("failed",)])
|
||||||
|
|
||||||
|
def test_quota_stop_after_attempt(self):
|
||||||
|
# 首轮返回 quota_exceeded:run_one 走配额中断收尾,
|
||||||
|
# 状态 interrupted、续跑消息保留(本例无消息,只断状态)。
|
||||||
|
_insert_task(self.db_path, "t-qstop", status="queued")
|
||||||
|
fake = FakeSync(dispatch=_result(status="quota_exceeded",
|
||||||
|
stdout=""))
|
||||||
|
w = self._worker(executor=fake)
|
||||||
|
result = w.run_one()
|
||||||
|
self.assertEqual(result["status"], "interrupted")
|
||||||
|
self.assertIn("quota", result["error"])
|
||||||
|
self.assertEqual(_row(
|
||||||
|
self.db_path,
|
||||||
|
"SELECT interrupted_reason FROM tasks WHERE id = 't-qstop'"),
|
||||||
|
[("quota",)])
|
||||||
|
|
||||||
|
|
||||||
|
class RunAttemptDirectTest(Worker3Base):
|
||||||
|
def _task_dict(self, tid, **kw):
|
||||||
|
d = {"id": tid, "project_id": "proj", "prompt": "do it",
|
||||||
|
"created_by": "owner1"}
|
||||||
|
d.update(kw)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def test_dispatch_already_exists_runs_again(self):
|
||||||
|
w = self._worker(executor=FakeSync(
|
||||||
|
dispatch=_result(status="failed", stdout="任务已存在"),
|
||||||
|
run=_result(stdout="run-ok")))
|
||||||
|
result, error = w._run_attempt(self._task_dict("t-ae"))
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(result["stdout"], "run-ok")
|
||||||
|
self.assertIsNone(error)
|
||||||
|
|
||||||
|
def test_executor_raises_becomes_failed(self):
|
||||||
|
class _Boom(FakeSync):
|
||||||
|
def dispatch_task(self, task):
|
||||||
|
raise RuntimeError("runner 炸了")
|
||||||
|
w = self._worker(executor=_Boom())
|
||||||
|
result, error = w._run_attempt(self._task_dict("t-boom"))
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertEqual(error, "runner 炸了")
|
||||||
|
|
||||||
|
def test_failed_stdout_tail_and_docker_timeout_marker(self):
|
||||||
|
w = self._worker(executor=FakeSync(
|
||||||
|
dispatch=_result(status="failed", stdout="docker run timeout")))
|
||||||
|
result, _ = w._run_attempt(self._task_dict("t-dto"))
|
||||||
|
self.assertEqual(result["status"], "timeout")
|
||||||
|
|
||||||
|
def test_quota_hit_rewrites_to_quota_exceeded(self):
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
w._mark_quota_hit("t-qh")
|
||||||
|
result, error = w._run_attempt(self._task_dict("t-qh"))
|
||||||
|
self.assertEqual(result["status"], "quota_exceeded")
|
||||||
|
self.assertIn("配额看门狗", error)
|
||||||
|
|
||||||
|
def test_goal_breakpoint_unsupported_for_fake_executor(self):
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
task = self._task_dict("t-bp", goal_mode=1)
|
||||||
|
task["_goal_resume_breakpoint"] = True
|
||||||
|
result, error = w._run_attempt(task)
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("不支持 goal 断点续跑", error)
|
||||||
|
|
||||||
|
def test_goal_task_via_fake_executor(self):
|
||||||
|
w = self._worker(executor=FakeSync(
|
||||||
|
goal=_result(stdout="goal-done")))
|
||||||
|
result, _ = w._run_attempt(self._task_dict("t-goal",
|
||||||
|
goal_mode=1))
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
self.assertEqual(result["stdout"], "goal-done")
|
||||||
|
|
||||||
|
def test_no_time_limit_task(self):
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
result, _ = w._run_attempt(self._task_dict(
|
||||||
|
"t-ntl", no_time_limit=1))
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
|
||||||
|
|
||||||
|
class DockerAttemptTest(Worker3Base):
|
||||||
|
def _docker_worker(self, **kw):
|
||||||
|
class _Docker(DockerExecutor):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(runner=lambda cmd, timeout=None: None,
|
||||||
|
env={"XCM_API_KEY": "test"})
|
||||||
|
self.timeout = kw.pop("timeout", None)
|
||||||
|
|
||||||
|
def container_name(self, task):
|
||||||
|
return "codex-t-" + task["id"]
|
||||||
|
|
||||||
|
def goal_task(self, task, **_kw):
|
||||||
|
return _result(stdout="docker-goal-ok")
|
||||||
|
|
||||||
|
def goal_resume_task(self, task, text, **_kw):
|
||||||
|
return _result(stdout="docker-resume-ok")
|
||||||
|
|
||||||
|
def goal_resume_breakpoint_task(self, task, **_kw):
|
||||||
|
return _result(stdout="docker-bp-ok")
|
||||||
|
|
||||||
|
def dispatch_task(self, task, **_kw):
|
||||||
|
return _result(stdout="docker-dispatch-ok")
|
||||||
|
|
||||||
|
def run_task(self, task, **_kw):
|
||||||
|
return _result(stdout="docker-run-ok")
|
||||||
|
|
||||||
|
def ensure_network(self, name):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def network_gateway(self, network):
|
||||||
|
return "10.0.0.1"
|
||||||
|
w = self._worker(executor=_Docker(), **kw)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def _task_dict(self, tid, **kw):
|
||||||
|
d = {"id": tid, "project_id": "proj", "prompt": "do it",
|
||||||
|
"created_by": "owner1"}
|
||||||
|
d.update(kw)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def test_docker_goal_and_resume_variants(self):
|
||||||
|
w = self._docker_worker()
|
||||||
|
result, _ = w._run_attempt(self._task_dict("t-dg",
|
||||||
|
goal_mode=1))
|
||||||
|
self.assertEqual(result["stdout"], "docker-goal-ok")
|
||||||
|
task = self._task_dict("t-dr", goal_mode=1)
|
||||||
|
task["_resume"] = True
|
||||||
|
task["_resume_kind"] = "new-goal"
|
||||||
|
result, _ = w._run_attempt(task)
|
||||||
|
self.assertEqual(result["stdout"], "docker-resume-ok")
|
||||||
|
task = self._task_dict("t-dbp", goal_mode=1)
|
||||||
|
task["_goal_resume_breakpoint"] = True
|
||||||
|
result, _ = w._run_attempt(task)
|
||||||
|
self.assertEqual(result["stdout"], "docker-bp-ok")
|
||||||
|
result, _ = w._run_attempt(self._task_dict("t-dd"))
|
||||||
|
self.assertEqual(result["stdout"], "docker-dispatch-ok")
|
||||||
|
|
||||||
|
def test_docker_context_egress_gateway_failure(self):
|
||||||
|
class _NoGw(DockerExecutor):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(runner=lambda cmd, timeout=None: None,
|
||||||
|
env={"XCM_API_KEY": "test"})
|
||||||
|
|
||||||
|
def network_gateway(self, network):
|
||||||
|
return None
|
||||||
|
w = self._worker(executor=_NoGw(), egress_proxy_port=8888)
|
||||||
|
result, error = w._run_attempt(self._task_dict("t-dgw"))
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertIn("egress 代理网关解析失败", error)
|
||||||
|
|
||||||
|
def test_docker_timeout_bad_type_falls_back(self):
|
||||||
|
# executor.timeout 非数字:min() 抛错后回落 task_timeout,
|
||||||
|
# 任务仍正常跑完(不炸 _run_attempt)。
|
||||||
|
w = self._docker_worker(timeout="bogus")
|
||||||
|
result, _ = w._run_attempt(self._task_dict("t-dbt"))
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
|
||||||
|
|
||||||
|
class RunLoopPrJobsTest(Worker3Base):
|
||||||
|
def test_run_loop_zero_and_run_all_empty(self):
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
self.assertEqual(w.run_loop(max_seconds=0), [])
|
||||||
|
self.assertEqual(w.run_all(), [])
|
||||||
|
|
||||||
|
def test_run_one_with_explicit_task_dict(self):
|
||||||
|
_insert_task(self.db_path, "t-exp", status="queued")
|
||||||
|
w = self._worker(executor=FakeSync())
|
||||||
|
task = w.get_task("t-exp")
|
||||||
|
result = w.run_one(task)
|
||||||
|
self.assertEqual(result["status"], "completed")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in new issue
Block a user