AgentEvalTool/tests/unit/test_task_queue_requeue.py
sinohqb eb4944a8bd feat(intelligent-eval): terminal-state discipline watchdogs (ADR-0011)
常见故障自愈有上限,超限收敛终态且可见:任务 attempts 上限、会话过期、
planning 双闸、executing 超窗兜底、触发失败计数判死、孤儿 agent 双管、
fire-and-forget 触发;open_session 预算硬闸门、settle 按终态区分、报告
scores 归一化;cron 池遗留面全删。
2026-08-20 14:34:17 +08:00

131 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Stale-assigned requeue tests (worker crash recovery).
方案③ worker 由平台触发 openclaw agentcron=manual-run非真实 cron
fault_tolerance 的 stuck 检测不适用;任务可能永久卡 assigned。requeue
把「assigned 超时且评估仍 executing」的任务重置为 pending。
"""
from datetime import timedelta
from agenteval.intelligent_eval.models import IntelligentEvalStatus
from agenteval.intelligent_eval.task_queue import (
STALE_ASSIGNED_MINUTES,
requeue_stale_assigned_tasks,
)
from agenteval.storage.db import (
IntelligentEvalDB,
IntelligentEvalTaskQueueDB,
utc_now,
)
from sqlmodel import Session
def _make_executing_eval(db_session: Session) -> IntelligentEvalDB:
ev = IntelligentEvalDB(
name="req-eval",
target_id="t1",
status=IntelligentEvalStatus.EXECUTING.value,
started_at=utc_now(),
)
db_session.add(ev)
db_session.commit()
return ev
def test_requeue_stale_assigned_task(db_session: Session):
ev = _make_executing_eval(db_session)
task = IntelligentEvalTaskQueueDB(
eval_id=ev.id,
status="assigned",
priority=1,
reason="slot_due",
assigned_cron_id="manual-run-x",
assigned_at=utc_now() - timedelta(minutes=STALE_ASSIGNED_MINUTES + 5),
)
db_session.add(task)
db_session.commit()
assert requeue_stale_assigned_tasks(db_session) == 1
db_session.refresh(task)
assert task.status == "pending"
assert task.assigned_cron_id is None
assert task.assigned_at is None
def test_fresh_assigned_not_requeued(db_session: Session):
ev = _make_executing_eval(db_session)
task = IntelligentEvalTaskQueueDB(
eval_id=ev.id,
status="assigned",
priority=1,
reason="slot_due",
assigned_cron_id="manual-run-y",
assigned_at=utc_now(), # fresh
)
db_session.add(task)
db_session.commit()
assert requeue_stale_assigned_tasks(db_session) == 0
db_session.refresh(task)
assert task.status == "assigned"
def test_stale_assigned_non_executing_not_requeued(db_session: Session):
ev = IntelligentEvalDB(
name="cancelled-eval",
target_id="t1",
status=IntelligentEvalStatus.CANCELLED.value,
)
db_session.add(ev)
db_session.commit()
task = IntelligentEvalTaskQueueDB(
eval_id=ev.id,
status="assigned",
priority=1,
reason="slot_due",
assigned_cron_id="manual-run-z",
assigned_at=utc_now() - timedelta(minutes=STALE_ASSIGNED_MINUTES + 5),
)
db_session.add(task)
db_session.commit()
assert requeue_stale_assigned_tasks(db_session) == 0
db_session.refresh(task)
assert task.status == "assigned"
def test_stale_assigned_abandoned_at_attempts_cap(db_session: Session):
"""ADR-0011attempts 达到上限后任务置 failed不再重入队并补录决策日志。"""
from agenteval.intelligent_eval.task_queue import MAX_TASK_ATTEMPTS
from agenteval.storage.db import IntelligentEvalDecisionLogDB
from sqlmodel import select
ev = _make_executing_eval(db_session)
task = IntelligentEvalTaskQueueDB(
eval_id=ev.id,
status="assigned",
priority=1,
reason="slot_due",
assigned_cron_id="manual-run-cap",
assigned_at=utc_now() - timedelta(minutes=STALE_ASSIGNED_MINUTES + 5),
attempts=MAX_TASK_ATTEMPTS - 1,
)
db_session.add(task)
db_session.commit()
assert requeue_stale_assigned_tasks(db_session) == 0
db_session.refresh(task)
assert task.status == "failed"
assert task.attempts == MAX_TASK_ATTEMPTS
assert task.completed_at is not None
assert "放弃" in task.error
logs = db_session.exec(
select(IntelligentEvalDecisionLogDB).where(
IntelligentEvalDecisionLogDB.eval_id == ev.id,
IntelligentEvalDecisionLogDB.decision_type == "task_abandoned",
)
).all()
assert len(logs) == 1