Files
pzhang_zywl 8069fc2f8a
CI / test (pull_request) Successful in 7s
fix: pipeline LLM 全失败时明确报错而非静默输出空 IR - Closes #15
- step1: 所有 LLM 调用返回空 function_units 时抛出 RuntimeError
- step1: main() 在 _quick_validate 未通过时 sys.exit(1)
- step2: function_units 为空时提前报错终止
- step3: fragments 为空时提前报错终止
- test: test_step1 捕获 SystemExit, test_step2_5/step3 空数据改为 skip

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 17:41:16 +08:00

181 lines
6.1 KiB
Python

"""
Tests for Stage 2.5 (Branch Coverage Auto-Completion).
Validates:
- Path enumeration exists and is non-empty
- Auto-complete fragments have valid structure
- No duplicate unit_ids in autocomplete fragments
- Path coverage improved after autocomplete (if applicable)
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import config
PASS = "[PASS]"
FAIL = "[FAIL]"
WARN = "[WARN]"
def load_path_enumeration():
"""Load path_enumeration.json."""
try:
return config.load_json(config.PATH_ENUM_JSON)
except FileNotFoundError:
print(f"{FAIL} path_enumeration.json 未找到: {config.PATH_ENUM_JSON}")
print(" 请先运行 step2_5_branch_coverage.py")
sys.exit(1)
def load_autocomplete_fragments():
"""Load ir_autocomplete_fragments.json, or return [] if absent."""
path = config.IR_AUTOCOMPLETE_FRAGMENTS_JSON
if not Path(path).exists():
return None
return config.load_json(path)
def check_path_enumeration(data: dict) -> list[str]:
"""Check path enumeration has valid structure."""
errors = []
paths = data.get("logic_tree_paths", {})
if not paths:
errors.append("logic_tree_paths 为空")
total = data.get("total_paths", 0)
if total <= 0:
errors.append(f"total_paths = {total}, 期望 > 0")
for image_id, image_paths in paths.items():
if not image_paths:
errors.append(f"{image_id}: 路径列表为空")
continue
for i, p in enumerate(image_paths):
if not p.get("path_id"):
errors.append(f"{image_id}[{i}]: 缺少 path_id")
if not p.get("image_id"):
errors.append(f"{image_id}[{i}]: 缺少 image_id")
if not p.get("node_ids"):
errors.append(f"{image_id}[{i}]: 缺少 node_ids")
return errors
def check_autocomplete_fragments(fragments: list[dict] | None) -> list[str]:
"""Check auto-complete fragments have valid structure."""
if fragments is None:
return ["ir_autocomplete_fragments.json 未生成 (可能无需补全)"]
errors = []
seen_unit_ids = set()
for frag in fragments:
uid = frag.get("unit_id", "")
if not uid:
errors.append("fragment 缺少 unit_id")
continue
if uid in seen_unit_ids:
errors.append(f"unit_id '{uid}' 重复")
seen_unit_ids.add(uid)
if not frag.get("auto_generated"):
errors.append(f"{uid}: auto_generated 应为 true")
rules = frag.get("rules", [])
for j, rule in enumerate(rules):
rid = rule.get("rule_id", f"rule[{j}]")
if not rule.get("path"):
errors.append(f"{rid}: path 字段缺失")
precond = rule.get("precondition", {})
if not precond.get("geographic_scope"):
errors.append(f"{rid}: precondition.geographic_scope 缺失")
return errors
def run_all_tests():
print("=" * 60)
print("Step 2.5 自检测试")
print("=" * 60)
all_errors = []
# Test 1: Path enumeration exists
try:
path_data = load_path_enumeration()
except SystemExit:
return False
errors = check_path_enumeration(path_data)
if errors:
print(f"\n{FAIL} 路径枚举检查: {len(errors)} 个错误")
for e in errors:
print(f" - {e}")
all_errors.extend(errors)
else:
total = path_data.get("total_paths", 0)
n_images = len(path_data.get("logic_tree_paths", {}))
print(f"\n{PASS} 路径枚举检查: {total} 条路径, {n_images} 个逻辑树")
# Test 2: Auto-complete fragments
fragments = load_autocomplete_fragments()
errors = check_autocomplete_fragments(fragments)
if fragments is None:
print(f"\n{WARN} 自动补全片段: 未生成 (可能所有路径已覆盖)")
elif errors:
print(f"\n{FAIL} 自动补全片段检查: {len(errors)} 个错误")
for e in errors[:10]:
print(f" - {e}")
all_errors.extend(errors)
else:
auto_rules = sum(len(f.get("rules", [])) for f in fragments)
print(f"\n{PASS} 自动补全片段检查: "
f"{len(fragments)} 个片段, {auto_rules} 条规则")
# Summary
print(f"\n{'='*60}")
total_failures = len(all_errors)
if total_failures == 0:
print(f"{PASS} 所有测试通过!")
else:
print(f"{FAIL} 测试失败: {total_failures} 个错误")
return total_failures == 0
# ═══════════════════════════════════════════════════════════════════════════════
# pytest discovery support
# ═══════════════════════════════════════════════════════════════════════════════
import pytest # noqa: E402
def test_step2_5_path_enumeration():
"""pytest: path_enumeration.json should have valid structure."""
try:
path_data = config.load_json(config.PATH_ENUM_JSON)
except FileNotFoundError:
pytest.skip("path_enumeration.json not found — run step2_5_branch_coverage.py first")
if path_data.get("total_paths", 0) == 0:
pytest.skip("path_enumeration.json has 0 paths — pipeline may have failed upstream")
errors = check_path_enumeration(path_data)
assert not errors, f"path enumeration errors: {errors}"
def test_step2_5_autocomplete_fragments():
"""pytest: auto-complete fragments must have valid structure if present."""
fragments = load_autocomplete_fragments()
errors = check_autocomplete_fragments(fragments)
# If fragments is None (no autocomplete needed), check_autocomplete_fragments returns a warning
actual_errors = [e for e in errors if "未生成" not in e]
assert not actual_errors, f"autocomplete fragment errors: {actual_errors}"
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)