139 lines
5.1 KiB
Python
139 lines
5.1 KiB
Python
"""LLM 后台任务生命周期直测(架构保养第二轮候选 1)。
|
||
|
||
分析 / 对比 / judge 复核三条 LLM 任务链收进各自的 TaskRegistry:
|
||
强引用防 GC、按 id 幂等、shutdown 统一收敛;启动清扫把滞留的
|
||
generating 行标记为 failed。
|
||
"""
|
||
|
||
import asyncio
|
||
|
||
import pytest
|
||
from agenteval.evaluation import analysis, comparison
|
||
from agenteval.exploration import judge
|
||
from agenteval.storage.repository import (
|
||
CampaignAnalysisRepository,
|
||
CampaignPeriodComparisonRepository,
|
||
)
|
||
from sqlmodel import Session, SQLModel, create_engine
|
||
|
||
|
||
@pytest.fixture()
|
||
def db_session(tmp_path):
|
||
from agenteval.storage.db import ( # noqa: F401
|
||
CampaignAnalysisDB,
|
||
CampaignDB,
|
||
CampaignPeriodComparisonDB,
|
||
)
|
||
|
||
engine = create_engine(
|
||
f"sqlite:///{tmp_path / 'llmtasks.db'}",
|
||
connect_args={"check_same_thread": False},
|
||
)
|
||
SQLModel.metadata.create_all(engine)
|
||
session = Session(engine)
|
||
try:
|
||
yield session
|
||
finally:
|
||
session.close()
|
||
engine.dispose()
|
||
|
||
|
||
async def test_start_analysis_registers_in_registry(monkeypatch):
|
||
monkeypatch.setattr(analysis, "execute_campaign_analysis", lambda *a, **kw: asyncio.sleep(0))
|
||
analysis.start_campaign_analysis("c-1", triggered_by="manual")
|
||
assert analysis.analysis_registry.is_running("c-1")
|
||
await analysis.analysis_registry.shutdown_all()
|
||
assert not analysis.analysis_registry.is_running("c-1")
|
||
|
||
|
||
async def test_start_comparison_registers_in_registry(monkeypatch):
|
||
monkeypatch.setattr(comparison, "execute_campaign_comparison", lambda *a, **kw: asyncio.sleep(0))
|
||
comparison.start_campaign_comparison("c-2", triggered_by="manual")
|
||
assert comparison.comparison_registry.is_running("c-2")
|
||
await comparison.comparison_registry.shutdown_all()
|
||
|
||
|
||
async def test_start_judge_registers_in_registry(monkeypatch):
|
||
monkeypatch.setattr(judge, "execute_judge_review", lambda *a, **kw: asyncio.sleep(0))
|
||
judge.start_judge_review("s-1")
|
||
assert judge.judge_registry.is_running("s-1")
|
||
await judge.judge_registry.shutdown_all()
|
||
|
||
|
||
async def test_launch_is_idempotent_for_live_id(monkeypatch):
|
||
gate = asyncio.Event()
|
||
|
||
async def hang(*args, **kwargs):
|
||
await gate.wait()
|
||
|
||
monkeypatch.setattr(analysis, "execute_campaign_analysis", hang)
|
||
analysis.start_campaign_analysis("c-dup", triggered_by="manual")
|
||
analysis.start_campaign_analysis("c-dup", triggered_by="manual")
|
||
assert len(analysis.analysis_registry._tasks) == 1
|
||
gate.set()
|
||
await analysis.analysis_registry.shutdown_all()
|
||
|
||
|
||
async def test_shutdown_all_cancels_hanging_task(monkeypatch):
|
||
async def hang(*args, **kwargs):
|
||
await asyncio.Event().wait()
|
||
|
||
monkeypatch.setattr(comparison, "execute_campaign_comparison", hang)
|
||
comparison.start_campaign_comparison("c-hang", triggered_by="manual")
|
||
assert comparison.comparison_registry.is_running("c-hang")
|
||
await comparison.comparison_registry.shutdown_all()
|
||
assert not comparison.comparison_registry.is_running("c-hang")
|
||
|
||
|
||
def test_mark_orphans_failed_flips_generating_analysis(db_session):
|
||
repo = CampaignAnalysisRepository(db_session)
|
||
repo.upsert("c-gen", status="generating", triggered_by="auto")
|
||
repo.upsert("c-done", status="completed", result={"ok": True}, triggered_by="auto")
|
||
|
||
count = repo.mark_orphans_failed()
|
||
|
||
assert count == 1
|
||
assert repo.get_by_campaign("c-gen").status == "failed"
|
||
assert repo.get_by_campaign("c-gen").error
|
||
assert repo.get_by_campaign("c-done").status == "completed"
|
||
|
||
|
||
def test_enqueue_analysis_persists_before_launch(db_session, monkeypatch):
|
||
from agenteval.evaluation import analysis
|
||
|
||
launched = []
|
||
monkeypatch.setattr(analysis, "get_session", lambda: db_session)
|
||
monkeypatch.setattr(analysis, "start_campaign_analysis", lambda cid, *, triggered_by: launched.append((cid, triggered_by)))
|
||
|
||
analysis.enqueue_campaign_analysis("c-queued", triggered_by="auto")
|
||
|
||
assert launched == [("c-queued", "auto")]
|
||
row = CampaignAnalysisRepository(db_session).get_by_campaign("c-queued")
|
||
assert row.status == "queued"
|
||
assert row.triggered_by == "auto"
|
||
|
||
|
||
def test_resume_queued_analysis_relaunches_persisted_jobs(db_session, monkeypatch):
|
||
from agenteval.evaluation import analysis
|
||
|
||
repo = CampaignAnalysisRepository(db_session)
|
||
repo.enqueue("c-queued", triggered_by="auto")
|
||
launched = []
|
||
monkeypatch.setattr(analysis, "start_campaign_analysis", lambda cid, *, triggered_by: launched.append((cid, triggered_by)))
|
||
|
||
assert analysis.resume_queued_campaign_analysis(db_session) == 1
|
||
assert launched == [("c-queued", "auto")]
|
||
|
||
|
||
def test_mark_orphans_failed_flips_generating_comparison(db_session):
|
||
repo = CampaignPeriodComparisonRepository(db_session)
|
||
repo.upsert("c-gen", status="generating", baseline_campaign_id="b-1", triggered_by="auto")
|
||
repo.upsert("c-done", status="completed", baseline_campaign_id="b-2", result={"ok": True}, triggered_by="auto")
|
||
|
||
count = repo.mark_orphans_failed()
|
||
|
||
assert count == 1
|
||
assert repo.get_by_campaign("c-gen").status == "failed"
|
||
assert repo.get_by_campaign("c-gen").error
|
||
assert repo.get_by_campaign("c-done").status == "completed"
|