Files
projectAIpopular/tests/test_worker.py
T

180 lines
5.6 KiB
Python

"""T5 WorkerLoop + 接地验证单测(封闭,注入假 generate / 假沙箱 runner)。"""
import pytest
from router_system.knowledge import KnowledgeBase
from router_system.verifier import (
Verifier,
detect_artifact_language,
extract_code_block,
run_code_sandbox,
)
from router_system.worker import WorkerLoop, artifact_name_for
from router_system.workspace import Workspace
def _brief(domain="code"):
return {
"goal": "实现快排",
"constraints": ["标准库"],
"tags": [domain],
"acceptance": [{"id": "a1", "check": "可运行", "machine_checkable": True}],
"plan": [
{"id": "s1", "task": "实现快排", "deps": [], "done_criteria": "函数可运行"},
{"id": "s2", "task": "写测试", "deps": ["s1"], "done_criteria": "3/3 通过"},
],
}
def _ws(domain="code"):
ws = Workspace.new(request_id="abc123def456", query="写个快排",
api_token_cap=8000, rounds_cap=6)
ws.apply_brief(_brief(domain))
return ws
def _fake_gen(texts):
"""顺序返回预设生成文本的假 generate。"""
it = iter(texts)
async def _g(prompt):
try:
return next(it)
except StopIteration:
return ""
return _g
# ---------- verifier: 代码沙箱 ----------
def test_verifier_code_pass(tmp_path):
v = Verifier(sandbox_runner=lambda cmd, env, t: (0, "ok", ""))
passed, det = v.verify("code", "s1.py", "def f():\n return 1\n", "q")
assert passed is True
assert any("沙箱运行通过" in d for d in det)
def test_verifier_code_fail_then_structure():
# 沙箱失败 + 无 facts + 文本过短 -> False
v = Verifier(sandbox_runner=lambda cmd, env, t: (1, "", "boom"))
passed, det = v.verify("code", "s1.py", "def f(): pass", "q")
assert passed is False
def test_verifier_facts_hit(kb):
v = Verifier()
# medical 域:回答命中"高血压"事实关键词
passed, det = v.verify("medical", "s1.md",
"高血压患者应低盐低脂饮食、控制体重、规律运动", "高血压饮食", kb=kb)
assert passed is True
assert any("facts" in d for d in det)
def test_verifier_structure_pass():
v = Verifier()
passed, _ = v.verify("general", "s1.md", "这是一段足够长的、非占位的正常回答内容。", "q")
assert passed is True
def test_verifier_placeholder_fail():
v = Verifier()
passed, det = v.verify("general", "s1.md", "待实现", "q")
assert passed is False
# ---------- run_code_sandbox ----------
def test_run_code_sandbox_good_code():
rc, out, err = run_code_sandbox("print(1 + 1)")
assert rc == 0
assert out.strip() == "2"
def test_run_code_sandbox_bad_code():
rc, out, err = run_code_sandbox("raise ValueError('x')")
assert rc != 0
assert "ValueError" in (err or "")
# ---------- extract_code_block ----------
def test_extract_code_block():
bt = chr(96) * 3
text = f"前言\n{bt}python\ndef f(): return 1\n{bt}\n后记"
assert extract_code_block(text) == "def f(): return 1"
def test_extract_code_block_no_fence():
assert extract_code_block("print(1)") == "print(1)"
# ---------- detect_artifact_language ----------
def test_detect_language():
assert detect_artifact_language("s1.py") == "python"
assert detect_artifact_language("s1.json") == "json"
assert detect_artifact_language("s1.md") == "text"
# ---------- artifact_name ----------
def test_artifact_name():
assert artifact_name_for("s1", "code") == "s1.py"
assert artifact_name_for("s1", "legal") == "s1.md"
# ---------- WorkerLoop 三路径 ----------
def test_worker_one_pass():
code = "def quicksort(arr):\n return sorted(arr)"
ws = _ws()
w = WorkerLoop(_fake_gen([code]),
verifier=Verifier(sandbox_runner=lambda c, e, t: (0, "", "")),
max_fix_attempts=2)
out = asyncio_run(w.run_step(ws, "s1"))
assert out.status == "done"
assert out.attempts == 1
assert out.artifact_text == code
assert len(ws["progress"]) == 1
assert ws["progress"][0]["status"] == "done"
def test_worker_self_fix_success():
bad = "def quicksort(arr):\n return arr[0] # 错"
good = "def quicksort(arr):\n return sorted(arr)"
# 第一次沙箱失败,第二次通过
results = iter([(1, "", "IndexError"), (0, "", "")])
def fake_run(cmd, env, t):
return next(results)
ws = _ws()
w = WorkerLoop(_fake_gen([bad, good]),
verifier=Verifier(sandbox_runner=fake_run),
max_fix_attempts=2)
out = asyncio_run(w.run_step(ws, "s1"))
assert out.status == "done"
assert out.attempts == 2
assert len(ws["progress"]) == 1
def test_worker_issue_after_exhaust():
bad = "def quicksort(arr):\n return arr[0]"
ws = _ws()
w = WorkerLoop(_fake_gen([bad, bad]),
verifier=Verifier(sandbox_runner=lambda c, e, t: (1, "", "Err")),
max_fix_attempts=2)
out = asyncio_run(w.run_step(ws, "s1"))
assert out.status == "issue"
assert out.issue_id is not None
assert len(ws["issues"]) == 1
assert ws["issues"][0]["step"] == "s1"
def test_worker_medical_facts_pass(kb):
# 医疗域:模型给出含事实关键词的回答 -> facts 验证通过
answer = "高血压患者应低盐低脂饮食、控制体重、规律运动、戒烟限酒"
ws = _ws(domain="medical")
w = WorkerLoop(_fake_gen([answer]), kb=kb, max_fix_attempts=2)
out = asyncio_run(w.run_step(ws, "s1"))
assert out.status == "done"
def asyncio_run(coro):
import asyncio
return asyncio.run(coro)