test(scheduler): 补全触发失败落账与错误路径测试(+9 用例)

覆盖 scheduler.py 中等难度边缘分支:
- _trigger_openclaw_agent 非零退出码 → on_failure 回调
- _trigger_openclaw_agent subprocess 异常 → on_failure 回调
- _record_worker_trigger_failure 成功/跳过/DB 异常兜底
- _record_planner_trigger_failure 成功/跳过/DB 异常兜底
- _fire_and_forget cancelled task 不记录异常

scheduler.py 覆盖率 75% → 81%
后端总计 963 passed
This commit is contained in:
sinohqb 2026-08-24 17:18:52 +08:00
parent 3705945a7d
commit 90ebbd5ff4

View File

@ -329,3 +329,187 @@ def test_fire_and_forget_does_not_block_and_logs_errors(monkeypatch):
logger.removeHandler(handler)
logger.disabled = was_disabled
assert any("触发爆炸" in r.getMessage() for r in records)
def test_fire_and_forget_cancelled_task_does_not_log(monkeypatch):
"""_fire_and_forget: cancelled task 直接 return不记录异常。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
async def long_running():
await asyncio.sleep(100)
async def main():
records: list[logging.LogRecord] = []
handler = logging.Handler()
handler.emit = records.append
logger = logging.getLogger("agenteval")
was_disabled = logger.disabled
logger.disabled = False
logger.addHandler(handler)
try:
scheduler_mod._fire_and_forget(long_running(), "Worker")
await asyncio.sleep(0.01) # 让 task 启动
# 取消所有非守护 task
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for t in tasks:
t.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
await asyncio.sleep(0.01) # 让完成回调执行
finally:
logger.removeHandler(handler)
logger.disabled = was_disabled
# cancelled task 不应记录异常
assert not any("触发任务异常" in r.getMessage() for r in records)
asyncio.run(main())
def test_trigger_openclaw_agent_nonzero_exit_calls_on_failure(monkeypatch):
"""_trigger_openclaw_agent: 子进程非零退出码 → on_failure 回调。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
def fake_run(cmd, **kwargs):
return MagicMock(returncode=1, stderr="docker: command not found")
monkeypatch.setattr(subprocess, "run", fake_run)
failures: list[str] = []
asyncio.run(scheduler_mod._trigger_openclaw_agent("Worker", "test", "msg", failures.append))
assert len(failures) == 1
assert "exit=1" in failures[0]
assert "docker: command not found" in failures[0]
def test_trigger_openclaw_agent_exception_calls_on_failure(monkeypatch):
"""_trigger_openclaw_agent: subprocess.run 抛异常 → on_failure 回调。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
def fake_run(cmd, **kwargs):
raise FileNotFoundError("docker not found")
monkeypatch.setattr(subprocess, "run", fake_run)
failures: list[str] = []
asyncio.run(scheduler_mod._trigger_openclaw_agent("Worker", "test", "msg", failures.append))
assert len(failures) == 1
assert "docker not found" in failures[0]
def test_record_worker_trigger_failure_records_to_pending_evals(monkeypatch):
"""_record_worker_trigger_failure: 有待认领任务的评估 → 落账触发失败。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
mock_session = MagicMock()
monkeypatch.setattr(scheduler_mod, "get_session", lambda: mock_session)
monkeypatch.setattr(scheduler_mod, "eval_ids_with_pending_worker_tasks", lambda session: ["ev-1", "ev-2"])
recorded: list = []
monkeypatch.setattr(
scheduler_mod,
"record_trigger_failures",
lambda session, channel, eval_ids, error: recorded.append((channel, eval_ids, error)),
)
scheduler_mod._record_worker_trigger_failure("test error")
assert len(recorded) == 1
assert recorded[0] == ("worker", ["ev-1", "ev-2"], "test error")
mock_session.close.assert_called_once()
def test_record_worker_trigger_failure_skips_when_no_pending(monkeypatch):
"""_record_worker_trigger_failure: 无待认领任务 → 不落账。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
mock_session = MagicMock()
monkeypatch.setattr(scheduler_mod, "get_session", lambda: mock_session)
monkeypatch.setattr(scheduler_mod, "eval_ids_with_pending_worker_tasks", lambda session: [])
recorded: list = []
monkeypatch.setattr(
scheduler_mod,
"record_trigger_failures",
lambda session, channel, eval_ids, error: recorded.append((channel, eval_ids, error)),
)
scheduler_mod._record_worker_trigger_failure("test error")
assert len(recorded) == 0
mock_session.close.assert_called_once()
def test_record_worker_trigger_failure_swallows_db_errors(monkeypatch):
"""_record_worker_trigger_failure: DB 异常不传播(兜底日志)。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
mock_session = MagicMock()
monkeypatch.setattr(scheduler_mod, "get_session", lambda: mock_session)
monkeypatch.setattr(
scheduler_mod,
"eval_ids_with_pending_worker_tasks",
lambda session: (_ for _ in ()).throw(RuntimeError("DB error")),
)
# 不应抛异常
scheduler_mod._record_worker_trigger_failure("test error")
mock_session.close.assert_called_once()
def test_record_planner_trigger_failure_records_to_planning_evals(monkeypatch):
"""_record_planner_trigger_failure: 有 planning 评估 → 落账触发失败。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
mock_session = MagicMock()
mock_row = MagicMock()
mock_row.id = "ev-plan-1"
mock_session.exec.return_value.all.return_value = [mock_row]
monkeypatch.setattr(scheduler_mod, "get_session", lambda: mock_session)
recorded: list = []
monkeypatch.setattr(
scheduler_mod,
"record_trigger_failures",
lambda session, channel, eval_ids, error: recorded.append((channel, eval_ids, error)),
)
scheduler_mod._record_planner_trigger_failure("planner error")
assert len(recorded) == 1
assert recorded[0] == ("planner", ["ev-plan-1"], "planner error")
mock_session.close.assert_called_once()
def test_record_planner_trigger_failure_skips_when_no_planning(monkeypatch):
"""_record_planner_trigger_failure: 无 planning 评估 → 不落账。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
mock_session = MagicMock()
mock_session.exec.return_value.all.return_value = []
monkeypatch.setattr(scheduler_mod, "get_session", lambda: mock_session)
recorded: list = []
monkeypatch.setattr(
scheduler_mod,
"record_trigger_failures",
lambda session, channel, eval_ids, error: recorded.append((channel, eval_ids, error)),
)
scheduler_mod._record_planner_trigger_failure("planner error")
assert len(recorded) == 0
mock_session.close.assert_called_once()
def test_record_planner_trigger_failure_swallows_db_errors(monkeypatch):
"""_record_planner_trigger_failure: DB 异常不传播(兜底日志)。"""
import agenteval.intelligent_eval.scheduler as scheduler_mod
mock_session = MagicMock()
mock_session.exec.side_effect = RuntimeError("DB error")
monkeypatch.setattr(scheduler_mod, "get_session", lambda: mock_session)
# 不应抛异常
scheduler_mod._record_planner_trigger_failure("planner error")
mock_session.close.assert_called_once()