Files
AirPlan-V2/test_ctx.py
AirPlan 6130478c96 feat: AirPlan V2 — 全专家插件强制路由 + 事件系统规范化
P0-8 扩大: do_mode.py finish_worker 全专家插件强制路由
- GUI→XDB, network→NDB, C/C++→SDB, done→Rvr, blocked/failed→Dbg
- 证据去重: 已有 xdbSessions/ndbSessions/sdbReports/rvrReviewed 则跳过

P1-GAP17: 事件 emit 规范化
- 新增 7 个事件常量 (TASK_ENTERED, TASK_FINISHED, ENGINE_ENTERED 等)
- 全部 emit 调用替换字符串字面量为常量,零残留
- 30 个事件类型常量全部定义且唯一

P1-GAP18: 事件日志原子轮转
- emit 计数器每 128 次检查轮转,避免每次 emit 读文件
- 清除未使用的 _emit_with_completion/_pending_merge_complete
- 原子轮转: tempfile+os.replace 保证不损坏

eng 极端接管: 强制调用全部专家插件 (Dbg/XDB/NDB/SDB/Rvr)
commands/do.md: 更新为全专家插件路由文档

全量测试: 69 通过, 0 失败

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:56:44 +08:00

248 lines
7.7 KiB
Python
Executable File
Raw 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
"""Ctx Mode 功能测试"""
import sys
import os
import tempfile
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "lib"))
def test_ctx_enter():
"""测试上下文管理器初始化"""
from air_runtime.modes.ctx_mode import ctx_enter
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)
result = ctx_enter(project_root)
assert "state_path" in result
assert Path(result["state_path"]).exists()
print("✓ 上下文管理器初始化测试通过")
return True
def test_ctx_estimate_tokens():
"""测试 Token 估算"""
from air_runtime.modes.ctx_mode import estimate_tokens
# 测试中文
chinese_text = "这是一个中文测试文本用于估算 Token 数量"
tokens = estimate_tokens(chinese_text)
assert tokens > 0
# 测试英文
english_text = "This is an English text for token estimation"
tokens_en = estimate_tokens(english_text)
assert tokens_en > 0
# 测试代码
code_text = "def main():\n print('hello world')\n return 0"
tokens_code = estimate_tokens(code_text)
assert tokens_code > 0
print(f"✓ Token 估算测试通过,中文: {tokens}, 英文: {tokens_en}, 代码: {tokens_code}")
return True
def test_ctx_validate_compression():
"""测试压缩质量校验"""
from air_runtime.modes.ctx_mode import validate_compression
original = """
# ADR-0001 决策
我们选择使用 PostgreSQL 数据库
# TODO 实现
- [ ] 创建数据库连接池
- [ ] 实现 CRUD 接口
# INV-1 约束
必须使用原子写入
"""
# 好的摘要(包含关键信息)
good_summary = "ADR-0001: PostgreSQL, TODO: 连接池/CRUD, INV-1: 原子写入"
result = validate_compression(original, good_summary)
assert result["ok"] == True
# 坏的摘要(丢失关键信息)
bad_summary = "选择了 PostgreSQL"
result_bad = validate_compression(original, bad_summary)
# 可能不 ok因为丢失了 TODO 和 INV
print("✓ 压缩质量校验测试通过")
return True
def test_ctx_compress_fallback():
"""测试三级降级压缩"""
from air_runtime.modes.ctx_mode import compress_with_fallback, CompressionLevel
# 创建大文本
long_text = "这是测试内容\n" * 1000
result = compress_with_fallback(long_text, max_tokens=100)
# 应该返回结果
assert "level" in result
assert "result" in result
assert result["level"] in [CompressionLevel.RETRY, CompressionLevel.FALLBACK_MODEL, CompressionLevel.TRUNCATE]
print(f"✓ 三级降级压缩测试通过,压缩级别: {result['level']}")
return True
def test_ctx_lock():
"""测试 compactor 锁"""
from air_runtime.modes.ctx_mode import acquire_compactor_lock
with tempfile.TemporaryDirectory() as tmpdir:
lock_path = Path(tmpdir) / "test.lock"
# 首次获取锁应该成功
acquired = acquire_compactor_lock(lock_path)
assert acquired == True
assert lock_path.exists()
# 再次获取应该失败(锁已被持有)
acquired2 = acquire_compactor_lock(lock_path)
assert acquired2 == False
print("✓ Compactor 锁测试通过")
return True
def test_event_log_emit():
"""P1-GAP17: 测试事件日志 emit 和读取"""
from air_runtime.events import EventLog, TASK_COMPLETED, TASK_ENTERED, TASK_FINISHED
with tempfile.TemporaryDirectory() as tmpdir:
log_path = Path(tmpdir) / "events.jsonl"
log = EventLog(log_path)
log.emit(TASK_ENTERED, {"taskId": "T-001"})
log.emit(TASK_FINISHED, {"taskId": "T-001"})
log.emit(TASK_COMPLETED, {"taskId": "T-001", "status": "done"})
assert log_path.exists()
lines = log_path.read_text().strip().split("\n")
assert len(lines) == 3
entries = [json.loads(line) for line in lines]
assert entries[0]["type"] == "task.entered"
assert entries[1]["type"] == "task.finished"
assert entries[2]["type"] == "task.completed"
# 检查 ts 字段均为 ISO 格式
for e in entries:
assert "ts" in e
assert "T" in e["ts"]
print("✓ 事件日志 emit 测试通过")
return True
def test_event_log_rotate():
"""P1-GAP18: 测试事件日志原子轮转"""
from air_runtime.events import EventLog, TASK_COMPLETED
with tempfile.TemporaryDirectory() as tmpdir:
log_path = Path(tmpdir) / "events.jsonl"
# 小上限 + 密集写入触发轮转
log = EventLog(log_path, max_lines=50)
# 改写 ROTATE_CHECK_EVERY 为小值确保多次检查
log.ROTATE_CHECK_EVERY = 10
# 写入 200 条,因 ROTATE_CHECK_EVERY=10 会多次触发轮转
for i in range(200):
log.emit(TASK_COMPLETED, {"taskId": f"T-{i:03d}"})
lines = log_path.read_text().strip().split("\n")
# 经过多次轮转后,行数不应超过 max_lines * 2两倍内都合理
assert len(lines) < 200, f"轮转后应少于写入量 200实际: {len(lines)}"
assert len(lines) >= 1, f"轮转后应有数据, 实际: {len(lines)}"
# 验证写入操作后文件仍然有效 JSONL
for line in lines:
entry = json.loads(line)
assert entry["type"] == "task.completed"
assert "ts" in entry
print(f"✓ 事件日志原子轮转测试通过,轮转后保留 {len(lines)}")
return True
def test_event_constants_defined():
"""P1-GAP17: 验证所有事件类型常量均已定义且唯一"""
import air_runtime.events as evt
expected_names = [
"TASK_DISPATCHED", "TASK_ENTERED", "TASK_FINISHED",
"TASK_COMPLETED", "TASK_BLOCKED",
"MERGE_STARTED", "MERGE_COMPLETED",
"REPAIR_CREATED", "REPAIR_RESOLVED",
"INTERVENTION_STALL", "XDB_CAPTURED", "DEBUG_SESSION",
"CONTEXT_COMPACTED", "DEPLOY_COMPLETED", "TEST_RUN", "SEC_SCAN", "REVIEW_SESSION",
"ENGINE_ENTERED", "ENGINE_CYCLE", "ENG_REPLAN_TRIGGERED", "ENG_BLOCKED",
"WORKER_TIMEOUT", "WORKTREE_MERGE_CONFLICT",
"ARC_REPLANNED",
"ADR_CHANGE_DETECTED", "ADR_INVALIDATION", "ADR_UNFREEZED",
"LOCK_ACQUIRED", "LOCK_RELEASED", "STALE_LOCK_CLEANED",
]
values = []
for name in expected_names:
val = getattr(evt, name, None)
assert val is not None, f"缺少事件常量: {name}"
values.append(val)
# 无重复值
assert len(set(values)) == len(values), f"事件常量存在重复值: {len(set(values))} vs {len(values)}"
print(f"✓ 事件常量定义测试通过,共 {len(values)} 个事件类型")
return True
def main():
print("=" * 50)
print("Ctx Mode 功能测试")
print("=" * 50)
tests = [
("上下文管理器初始化", test_ctx_enter),
("Token 估算", test_ctx_estimate_tokens),
("压缩质量校验", test_ctx_validate_compression),
("三级降级压缩", test_ctx_compress_fallback),
("Compactor 锁", test_ctx_lock),
("事件日志 emit", test_event_log_emit),
("事件日志原子轮转", test_event_log_rotate),
("事件常量定义", test_event_constants_defined),
]
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)