Files
AirPlan-V2/test_t_121_122.py
AirPlan a60d1a0c04 fix: Windows兼容性修复 + P1-24弱模型优化 + 3.2.9b禁止降级方案 + AirRvr三层审查放行标准
- lock.py: 跨平台进程锁(Unix fcntl / Windows msvcrt / O_CREAT|O_EXCL降级)
- eng_mode.py/eng_orchestrator.py: hasattr(os, "getloadavg") Windows防护
- arc_mode.py: 路径分隔符 replace("\\", "/") Windows兼容
- deploy_runtime.py: 修复语法错误(清理 import tempfile 残留)
- P1-24(3.2.18): AMBIGUOUS_VERBS歧义词检测 + SAFE_VERBS安全动词 + validate_task_description()
- TaskNode.keep_constraints 保留约束字段 + JSON序列化
- _build_graph_from_todo 返回歧义警告 + Arc自检集成
- 3.2.9b: FORBIDDEN_DEGRADATION_PATTERNS + check_forbidden_degradation()
- AirRvr三层审查放行标准: ReviewVerdict + evaluate_review_pass() + is_forbidden_pass_reason()
- commands/arc.md: 弱模型安全重写(操作类型拆分+保留约束+自检)
- commands/do.md/eng.md/rvr.md: 禁止降级方案 + 三层审查标准
- 测试: 7个新测试 + 74全量通过

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 09:58:29 +08:00

294 lines
13 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.
#!/usr/bin/env python3
"""T-1.22 + T-1.23 端到端测试arc → eng dispatch → do → merge → task-graph sync 全链路
T-1.21 (弱模型优化) 测试见 test_arc.py (test_validate_task_description 等)
T-1.22: Dispatch → Worker 桥接spawn_workers + 全专家插件路由)
T-1.23: Merge → TaskGraph 状态同步merge 后更新 task-graph.json 节点 status
"""
import sys
import tempfile
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "lib"))
def test_spawn_workers():
"""测试 spawn_workers 返回正确的 Skill 调用参数"""
from air_runtime.task_graph import TaskGraph, TaskNode
from air_runtime.modes.arc_mode import _export_task_graph_json
from air_runtime.modes.eng_mode import spawn_workers
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
arc_dir = root / "AirPlan" / "state" / "airarc" / "reviews"
arc_dir.mkdir(parents=True, exist_ok=True)
graph = TaskGraph()
graph.add_node(TaskNode(id="G-001", status="TODO", task="实现视频解码器",
files_dirs="src/decoder.cpp"))
graph.add_node(TaskNode(id="G-002", status="TODO", task="实现音频解码器",
files_dirs="src/audio.cpp"))
_export_task_graph_json(graph, arc_dir / "task-graph.json")
instructions = spawn_workers(root, ["G-001", "G-002"])
assert len(instructions) == 2
assert instructions[0]["subagent_type"] == "general-purpose"
assert "实现视频解码器" in instructions[0]["prompt"]
assert instructions[0]["description"] == "Do Worker: G-001"
assert instructions[1]["taskId"] == "G-002"
print("✓ spawn_workers 测试通过")
return True
def test_merge_syncs_task_graph():
"""P1-24: 测试 merge 后 task-graph.json 节点状态同步"""
from air_runtime.task_graph import TaskGraph, TaskNode
from air_runtime.modes.arc_mode import _export_task_graph_json, ArcPhaseGate
from air_runtime.modes.eng_mode import enter_engine, merge_worker_result
from air_runtime.modes.do_mode import enter_worker, finish_worker
from air_runtime.contracts import WorkerResult
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
# Arc 准备
from air_runtime.modes.arc_mode import enter_mode, parallel_review_mode
enter_mode(root)
gate = ArcPhaseGate(root / "AirPlan" / "state" / "airarc" / "state.json")
gate.advance_to("confirmed")
todo = root / "AirPlan" / "todo.md"
todo.parent.mkdir(parents=True, exist_ok=True)
todo.write_text("| Task | Status | Files/Dirs | Done When |\n"
"|------|--------|------------|-----------|\n"
"| G-001 解码器 | TODO | src/decoder.cpp | 编译通过 |\n")
parallel_review_mode(root, todo)
# Eng 初始化
enter_engine(root)
# 验证 merge 前 task-graph 中 G-001 是 TODO
tg_json = root / "AirPlan" / "state" / "airarc" / "reviews" / "task-graph.json"
graph = TaskGraph.load(tg_json)
assert "G-001" in graph.nodes
assert graph.nodes["G-001"].status == "TODO"
print(f" merge 前: G-001 status = {graph.nodes['G-001'].status}")
# Do Worker 执行
w = enter_worker(root, "G-001", task_text="G-001 解码器")
wr = WorkerResult(task_id="G-001", status="done", summary="完成",
files_changed=["src/decoder.cpp"], validations="编译通过")
rpath = root / "AirPlan" / "state" / "airdo" / "tasks" / "G-001" / "result.json"
rpath.parent.mkdir(parents=True, exist_ok=True)
rpath.write_text(json.dumps(wr.to_dict()))
f = finish_worker(root, "G-001", rpath)
print(f" Do finish: status={f['status']}, route={f['routingDecision']['target']}")
# Eng merge
m = merge_worker_result(root, rpath)
print(f" Eng merge: task={m['taskId']}, status={m['status']}")
# 验证 merge 后 task-graph 中 G-001 是 DONE
graph_after = TaskGraph.load(tg_json)
assert graph_after.nodes["G-001"].status == "DONE", \
f"merge 后 G-001 应为 DONE实际: {graph_after.nodes['G-001'].status}"
print(f" merge 后: G-001 status = {graph_after.nodes['G-001'].status}")
# 验证不会重复派发ready_tasks 不包含已完成的 G-001
ready = graph_after.ready_tasks()
assert "G-001" not in ready, f"G-001 不应在 ready 队列中: {ready}"
print(f" ready queue: {ready}")
print("✓ merge 同步 task-graph 状态测试通过")
return True
def test_full_pipeline_e2e():
"""完整端到端流水线arc → eng plan → dispatch → do → merge → task-graph sync"""
from air_runtime.task_graph import TaskGraph
from air_runtime.modes.arc_mode import enter_mode, parallel_review_mode, ArcPhaseGate, _export_task_graph_json
from air_runtime.modes.eng_mode import (enter_engine, build_engine_plan,
dispatch_worker_group, spawn_workers,
merge_worker_result)
from air_runtime.modes.do_mode import enter_worker, finish_worker
from air_runtime.contracts import WorkerResult
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
# ============ ARC ============
enter_mode(root)
gate = ArcPhaseGate(root / "AirPlan" / "state" / "airarc" / "state.json")
gate.advance_to("confirmed")
todo = root / "AirPlan" / "todo.md"
todo.parent.mkdir(parents=True, exist_ok=True)
todo.write_text("| Task | Status | Files/Dirs | Done When |\n"
"|------|--------|------------|-----------|\n"
"| H-000 前置 | DONE | | 完成 |\n"
"| G-001 解码器 | TODO | src/decoder.cpp | 编译通过 |\n"
"| G-002 编码器 | TODO | src/encoder.cpp | 编译通过 |\n")
r = parallel_review_mode(root, todo)
assert not r.get("blocked"), f"Arc blocked: {r.get('reason')}"
print("1. ARC parallel-review ✓")
# ============ ENG PLAN ============
enter_engine(root)
plan = build_engine_plan(root, todo)
assert plan["planningSource"] == "airarc-execution-plan"
assert "G-001" in plan["selectedTasks"]
assert "G-002" in plan["selectedTasks"]
print(f"2. ENG plan: tasks={plan['selectedTasks']}")
# ============ ENG DISPATCH ============
dispatch = dispatch_worker_group(root)
assert not dispatch.get("blocked"), f"dispatch blocked: {dispatch.get('reason')}"
assert len(dispatch["taskIds"]) >= 2
print(f"3. ENG dispatch: wave={dispatch['waveId']}, tasks={dispatch['taskIds']}")
# ============ SPAWN INSTRUCTIONS ============
instructions = spawn_workers(root, dispatch["taskIds"])
assert len(instructions) >= 2
for inst in instructions:
assert inst["subagent_type"] == "general-purpose"
assert inst["description"].startswith("Do Worker:")
assert inst["taskId"] in inst.get("prompt", "") # prompt 中包含 task ID
# 至少有一个 G-001
g001_prompts = [i for i in instructions if "G-001" in i.get("prompt", "")]
assert len(g001_prompts) >= 1
print(f"4. spawn_workers: {len(instructions)} 个 Worker 指令准备完成")
for inst in instructions:
print(f" - {inst['taskId']}: {inst['taskText'][:40]}")
# ============ DO WORKER (模拟 Agent 调 Skill含全专家插件路由) ============
for inst in instructions:
tid = inst["taskId"]
task_text = inst["taskText"]
w = enter_worker(root, tid, task_text=task_text)
wr = WorkerResult(task_id=tid, status="done", summary="完成",
files_changed=[f"src/{tid.lower()}.cpp"],
validations="编译通过")
rpath = root / "AirPlan" / "state" / "airdo" / "tasks" / tid / "result.json"
rpath.parent.mkdir(parents=True, exist_ok=True)
rpath.write_text(json.dumps(wr.to_dict()))
f = finish_worker(root, tid, rpath)
# C++ 文件触发 SDB 路由done 触发 Rvr 路由
targets = [d["target"] for d in f["routingDecisions"]]
assert "airsdb" in targets, f"C++ 任务应路由到 SDB实际: {targets}"
assert "airrvr" in targets, f"done 任务应路由到 Rvr实际: {targets}"
# 模拟 SDB + Rvr 完成后
wr_with_evidence = WorkerResult(task_id=tid, status="done", summary="完成",
files_changed=[f"src/{tid.lower()}.cpp"],
validations="编译通过")
wr_dict = wr_with_evidence.to_dict()
wr_dict["sdbReports"] = [{"status": "passed"}]
wr_dict["rvrReviewed"] = True
rpath.write_text(json.dumps(wr_dict))
f2 = finish_worker(root, tid, rpath)
assert f2["routingDecision"]["target"] == "merge", \
f"有全证据后应 merge实际: {f2['routingDecision']['target']}"
print("5. DO Workers 全专家插件路由完成 (SDB→Rvr→merge)")
# ============ ENG MERGE ============
for inst in instructions:
tid = inst["taskId"]
rpath = root / "AirPlan" / "state" / "airdo" / "tasks" / tid / "result.json"
m = merge_worker_result(root, rpath)
assert m["status"] == "done"
print("6. ENG merge 全部完成")
# ============ VERIFY TASK-GRAPH SYNC ============
tg_json = root / "AirPlan" / "state" / "airarc" / "reviews" / "task-graph.json"
graph_after = TaskGraph.load(tg_json)
assert graph_after.nodes["G-001"].status == "DONE", \
f"G-001 应为 DONE实际: {graph_after.nodes['G-001'].status}"
assert graph_after.nodes["G-002"].status == "DONE", \
f"G-002 应为 DONE实际: {graph_after.nodes['G-002'].status}"
print(f"7. task-graph sync: G-001={graph_after.nodes['G-001'].status}, "
f"G-002={graph_after.nodes['G-002'].status}")
# ============ VERIFY NO RE-DISPATCH ============
ready_after = graph_after.ready_tasks()
assert "G-001" not in ready_after
assert "G-002" not in ready_after
# H-000 是 DONE也不在 ready
# 只有 T-TEST-* 还在 TODO
print(f"8. 无重复派发: ready={ready_after}")
# 再次 dispatch 不应包含已完成任务
dispatch2 = dispatch_worker_group(root)
for done_tid in ["G-001", "G-002", "H-000"]:
assert done_tid not in dispatch2.get("taskIds", []), \
f"已完成任务 {done_tid} 不应再次被派发"
print(f"9. 二次 dispatch: tasks={dispatch2.get('taskIds', [])} (无已完成任务)")
print("✓ 完整端到端流水线测试通过")
return True
def test_dispatch_no_workers_means_no_dispatch():
"""测试所有任务完成后 dispatch 返回空"""
from air_runtime.task_graph import TaskGraph, TaskNode
from air_runtime.modes.arc_mode import _export_task_graph_json
from air_runtime.modes.eng_mode import dispatch_worker_group, enter_engine
from air_runtime.modes.arc_mode import enter_mode
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
enter_mode(root)
enter_engine(root)
# 所有任务都是 DONE
arc_dir = root / "AirPlan" / "state" / "airarc" / "reviews"
arc_dir.mkdir(parents=True, exist_ok=True)
graph = TaskGraph()
graph.add_node(TaskNode(id="G-001", status="DONE", task="已完成"))
graph.add_node(TaskNode(id="G-002", status="DONE", task="已完成"))
_export_task_graph_json(graph, arc_dir / "task-graph.json")
result = dispatch_worker_group(root)
assert result["taskIds"] == [], f"全部完成时应为空,实际: {result['taskIds']}"
print("✓ 全部完成时 dispatch 返回空测试通过")
return True
def main():
print("=" * 50)
print("T-1.22 + T-1.23 端到端全链路测试")
print("=" * 50)
tests = [
("spawn_workers", test_spawn_workers),
("merge 同步 task-graph", test_merge_syncs_task_graph),
("完整端到端流水线", test_full_pipeline_e2e),
("全完成时 dispatch 返回空", test_dispatch_no_workers_means_no_dispatch),
]
passed = 0
failed = 0
for name, test_fn in tests:
try:
test_fn()
passed += 1
except Exception as e:
print(f"{name} 失败: {e}")
import traceback
traceback.print_exc()
failed += 1
print("=" * 50)
print(f"测试结果: {passed} 通过, {failed} 失败")
print("=" * 50)
return failed == 0
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)