init the project
This commit is contained in:
+238
@@ -0,0 +1,238 @@
|
||||
"""
|
||||
Tests for Stage 1 (Semantic Index).
|
||||
|
||||
Validates that the generated semantic_index.json meets all completeness
|
||||
and structural requirements.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Allow running from project root or tests/ directory
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
import config
|
||||
|
||||
|
||||
PASS = "[PASS]"
|
||||
FAIL = "[FAIL]"
|
||||
WARN = "[WARN]"
|
||||
|
||||
|
||||
def load_inputs():
|
||||
"""Load semantic_index.json and the original parsed document."""
|
||||
try:
|
||||
si = config.load_json(config.SEMANTIC_INDEX_JSON)
|
||||
except FileNotFoundError:
|
||||
print(f"{FAIL} semantic_index.json 未找到: {config.SEMANTIC_INDEX_JSON}")
|
||||
print(" 请先运行 step1_semantic_index.py")
|
||||
sys.exit(1)
|
||||
|
||||
doc = config.load_input_document()
|
||||
return si, doc
|
||||
|
||||
|
||||
def build_image_index(doc: dict) -> dict[str, dict]:
|
||||
"""Build lookup: image rId -> image_analysis entry."""
|
||||
idx = {}
|
||||
for img in doc.get("image_analysis", []):
|
||||
rid = img.get("rid", "")
|
||||
if rid:
|
||||
idx[rid] = img
|
||||
return idx
|
||||
|
||||
|
||||
def build_logic_tree_node_index(doc: dict) -> dict[str, set[str]]:
|
||||
"""Build lookup: image rId -> set of all node IDs in that logic_tree."""
|
||||
idx = {}
|
||||
for img in doc.get("image_analysis", []):
|
||||
rid = img.get("rid", "")
|
||||
lt = img.get("logic_tree")
|
||||
if lt and rid:
|
||||
node_ids = {n["id"] for n in lt.get("nodes", [])}
|
||||
idx[rid] = node_ids
|
||||
return idx
|
||||
|
||||
|
||||
def check_unit_ids(units: list[dict]) -> list[str]:
|
||||
"""Check that every function_unit has a non-empty unit_id and name."""
|
||||
errors = []
|
||||
seen_ids = set()
|
||||
for i, fu in enumerate(units):
|
||||
uid = fu.get("unit_id", "")
|
||||
name = fu.get("name", "")
|
||||
if not uid:
|
||||
errors.append(f"function_unit[{i}]: unit_id 为空")
|
||||
elif uid in seen_ids:
|
||||
errors.append(f"function_unit[{i}]: unit_id '{uid}' 重复")
|
||||
seen_ids.add(uid)
|
||||
if not name:
|
||||
errors.append(f"function_unit[{i}] ({uid}): name 为空")
|
||||
return errors
|
||||
|
||||
|
||||
def check_sources_exist(
|
||||
units: list[dict], image_index: dict[str, dict], node_index: dict[str, set[str]]
|
||||
) -> list[str]:
|
||||
"""Check that all source references point to real content."""
|
||||
errors = []
|
||||
|
||||
for fu in units:
|
||||
uid = fu.get("unit_id", "?")
|
||||
sources = fu.get("sources", [])
|
||||
if not sources:
|
||||
errors.append(f"{uid}: sources 为空,必须至少引用一张图片或一段文字")
|
||||
continue
|
||||
|
||||
has_text = False
|
||||
has_image = False
|
||||
|
||||
for j, src in enumerate(sources):
|
||||
src_type = src.get("type", "")
|
||||
if src_type in ("table", "para"):
|
||||
has_text = True
|
||||
section = src.get("section", "")
|
||||
if not section:
|
||||
errors.append(f"{uid}.sources[{j}]: 缺少 section")
|
||||
elif src_type == "logic_tree":
|
||||
has_image = True
|
||||
image_id = src.get("image_id", "")
|
||||
if not image_id:
|
||||
errors.append(f"{uid}.sources[{j}]: logic_tree 缺少 image_id")
|
||||
continue
|
||||
|
||||
# Check image exists
|
||||
if image_id not in image_index:
|
||||
errors.append(
|
||||
f"{uid}.sources[{j}]: image_id '{image_id}' "
|
||||
f"在 image_analysis 中不存在"
|
||||
)
|
||||
continue
|
||||
|
||||
# Check logic_tree_nodes if provided
|
||||
node_ids = src.get("logic_tree_nodes", [])
|
||||
if node_ids and image_id in node_index:
|
||||
valid_nodes = node_index[image_id]
|
||||
for nid in node_ids:
|
||||
if nid not in valid_nodes:
|
||||
errors.append(
|
||||
f"{uid}.sources[{j}]: 节点 '{nid}' 在 "
|
||||
f"{image_id} 的逻辑树中不存在"
|
||||
)
|
||||
elif not node_ids:
|
||||
errors.append(
|
||||
f"{uid}.sources[{j}]: logic_tree 类型但未提供 logic_tree_nodes"
|
||||
)
|
||||
|
||||
if not has_text and not has_image:
|
||||
errors.append(f"{uid}: 必须至少引用一个文本或图片来源")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_logic_tree_coverage(
|
||||
units: list[dict], node_index: dict[str, set[str]]
|
||||
) -> list[str]:
|
||||
"""Check that decision and action nodes in logic trees are covered."""
|
||||
warnings = []
|
||||
|
||||
for image_id, all_nodes in node_index.items():
|
||||
# Collect all nodes referenced across all function_units for this image
|
||||
referenced = set()
|
||||
for fu in units:
|
||||
for src in fu.get("sources", []):
|
||||
if src.get("image_id") == image_id:
|
||||
for nid in src.get("logic_tree_nodes", []):
|
||||
referenced.add(nid)
|
||||
|
||||
uncovered = all_nodes - referenced
|
||||
if uncovered:
|
||||
# Get node types from the document
|
||||
doc = config.load_input_document()
|
||||
node_types = {}
|
||||
for img in doc.get("image_analysis", []):
|
||||
if img.get("rid") == image_id:
|
||||
lt = img.get("logic_tree", {})
|
||||
for n in lt.get("nodes", []):
|
||||
node_types[n["id"]] = n.get("type", "?")
|
||||
break
|
||||
|
||||
decision_action_uncovered = [
|
||||
n for n in uncovered if node_types.get(n) in ("decision", "action")
|
||||
]
|
||||
if decision_action_uncovered:
|
||||
warnings.append(
|
||||
f"{image_id}: {len(decision_action_uncovered)} 个 "
|
||||
f"decision/action 节点未被引用: {decision_action_uncovered}"
|
||||
)
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
print("=" * 60)
|
||||
print("Step 1 自检测试")
|
||||
print("=" * 60)
|
||||
|
||||
si, doc = load_inputs()
|
||||
units = si.get("function_units", [])
|
||||
image_index = build_image_index(doc)
|
||||
node_index = build_logic_tree_node_index(doc)
|
||||
|
||||
all_errors = []
|
||||
all_warnings = []
|
||||
|
||||
# Test 1: unit_id and name validity
|
||||
errors = check_unit_ids(units)
|
||||
if errors:
|
||||
print(f"\n{FAIL} unit_id/name 检查: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} unit_id/name 检查: 全部通过 ({len(units)} 个功能单元)")
|
||||
|
||||
# Test 2: source references exist
|
||||
errors = check_sources_exist(units, image_index, node_index)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 来源引用检查: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 来源引用检查: 全部通过")
|
||||
|
||||
# Test 3: Logic tree coverage
|
||||
warnings = check_logic_tree_coverage(units, node_index)
|
||||
if warnings:
|
||||
print(f"\n{WARN} 逻辑树节点覆盖率: {len(warnings)} 个警告")
|
||||
for w in warnings:
|
||||
print(f" - {w}")
|
||||
all_warnings.extend(warnings)
|
||||
else:
|
||||
print(f"\n{PASS} 逻辑树节点覆盖率: 全部通过")
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
total_failures = len(all_errors)
|
||||
total_warnings = len(all_warnings)
|
||||
|
||||
if total_failures == 0 and total_warnings == 0:
|
||||
print(f"{PASS} 所有测试通过!")
|
||||
elif total_failures == 0:
|
||||
print(f"{WARN} 全部通过但有 {total_warnings} 个警告")
|
||||
else:
|
||||
print(f"{FAIL} 测试失败: {total_failures} 个错误, {total_warnings} 个警告")
|
||||
print("\n请检查 LLM 输出质量,可能需要调整 Prompt 并重新运行 step1_semantic_index.py")
|
||||
|
||||
print(f"\n统计:")
|
||||
print(f" 功能单元数: {len(units)}")
|
||||
print(f" 概念数: {len(si.get('concepts', []))}")
|
||||
print(f" 逻辑树图片数: {len(node_index)}")
|
||||
|
||||
return total_failures == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
Tests for Stage 2 (IR Extraction).
|
||||
|
||||
Validates that ir_fragments.json meets quality and structural requirements:
|
||||
- All fragments have non-empty rules
|
||||
- All rules have source references with logic tree nodes
|
||||
- All trigger conditions have signal/operator/value
|
||||
- No duplicate rule_ids (across all fragments)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
import config
|
||||
|
||||
|
||||
PASS = "[PASS]"
|
||||
FAIL = "[FAIL]"
|
||||
WARN = "[WARN]"
|
||||
|
||||
|
||||
def load_fragments():
|
||||
"""Load ir_fragments.json."""
|
||||
try:
|
||||
return config.load_json(config.IR_FRAGMENTS_JSON)
|
||||
except FileNotFoundError:
|
||||
print(f"{FAIL} ir_fragments.json 未找到: {config.IR_FRAGMENTS_JSON}")
|
||||
print(" 请先运行 step2_ir_extraction.py")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_non_empty_rules(fragments: list[dict]) -> list[str]:
|
||||
"""Every fragment must have at least one rule."""
|
||||
errors = []
|
||||
for f in fragments:
|
||||
uid = f.get("unit_id", "?")
|
||||
rules = f.get("rules", [])
|
||||
if not rules:
|
||||
if f.get("error"):
|
||||
errors.append(f"{uid}: 提取失败 — {f['error']}")
|
||||
else:
|
||||
errors.append(f"{uid}: rules 为空")
|
||||
return errors
|
||||
|
||||
|
||||
def check_sources_have_logic_tree_nodes(fragments: list[dict]) -> list[str]:
|
||||
"""Every rule should reference at least one logic tree node in its sources."""
|
||||
errors = []
|
||||
for f in fragments:
|
||||
uid = f.get("unit_id", "?")
|
||||
for j, rule in enumerate(f.get("rules", [])):
|
||||
rid = rule.get("rule_id", f"rule[{j}]")
|
||||
sources = rule.get("sources", [])
|
||||
has_logic_tree = any(
|
||||
src.get("type") == "logic_tree" and src.get("node_ids")
|
||||
for src in sources
|
||||
)
|
||||
# NOTE: Some rules might only reference text (e.g., switch-off rules),
|
||||
# so we flag as warning rather than error
|
||||
if not has_logic_tree:
|
||||
has_text = any(
|
||||
src.get("type") in ("table", "para") for src in sources
|
||||
)
|
||||
if not has_text:
|
||||
errors.append(f"{rid}: sources 中既无逻辑树引用也无文字引用")
|
||||
return errors
|
||||
|
||||
|
||||
def check_trigger_conditions(fragments: list[dict]) -> list[str]:
|
||||
"""Every trigger condition must have signal, operator, value."""
|
||||
errors = []
|
||||
for f in fragments:
|
||||
uid = f.get("unit_id", "?")
|
||||
for j, rule in enumerate(f.get("rules", [])):
|
||||
rid = rule.get("rule_id", f"rule[{j}]")
|
||||
trigger = rule.get("trigger", {})
|
||||
conditions = trigger.get("conditions", [])
|
||||
|
||||
# Check if trigger uses 'event' instead of conditions
|
||||
if trigger.get("event") is not None:
|
||||
continue # event-based trigger is valid
|
||||
|
||||
for k, cond in enumerate(conditions):
|
||||
signal = cond.get("signal", "")
|
||||
operator = cond.get("operator", "")
|
||||
# value can be 0, False, "", so check with 'in'
|
||||
has_value = "value" in cond
|
||||
|
||||
if not signal:
|
||||
errors.append(f"{rid}.condition[{k}]: 缺少 signal")
|
||||
if not operator:
|
||||
errors.append(f"{rid}.condition[{k}]: 缺少 operator")
|
||||
if not has_value:
|
||||
errors.append(f"{rid}.condition[{k}]: 缺少 value")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_duplicate_rule_ids(fragments: list[dict]) -> list[str]:
|
||||
"""Check for duplicate rule_ids across all fragments."""
|
||||
all_rule_ids = []
|
||||
for f in fragments:
|
||||
for rule in f.get("rules", []):
|
||||
rid = rule.get("rule_id", "")
|
||||
if rid:
|
||||
all_rule_ids.append(rid)
|
||||
|
||||
duplicates = [rid for rid, count in Counter(all_rule_ids).items() if count > 1]
|
||||
errors = []
|
||||
if duplicates:
|
||||
errors.append(f"重复 rule_id: {duplicates}")
|
||||
return errors
|
||||
|
||||
|
||||
def check_action_types(fragments: list[dict]) -> list[str]:
|
||||
"""Verify that actions have valid types."""
|
||||
valid_types = {"system", "user_interaction"}
|
||||
errors = []
|
||||
for f in fragments:
|
||||
for j, rule in enumerate(f.get("rules", [])):
|
||||
rid = rule.get("rule_id", f"rule[{j}]")
|
||||
for k, action in enumerate(rule.get("actions", [])):
|
||||
atype = action.get("type", "")
|
||||
if atype not in valid_types:
|
||||
errors.append(
|
||||
f"{rid}.action[{k}]: type='{atype}' 无效, "
|
||||
f"应为 {valid_types}"
|
||||
)
|
||||
if atype == "user_interaction" and "content" not in action:
|
||||
errors.append(
|
||||
f"{rid}.action[{k}]: user_interaction 类型缺少 content 字段"
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
print("=" * 60)
|
||||
print("Step 2 自检测试")
|
||||
print("=" * 60)
|
||||
|
||||
fragments = load_fragments()
|
||||
all_errors = []
|
||||
total_units = len(fragments)
|
||||
total_rules = sum(len(f.get("rules", [])) for f in fragments)
|
||||
|
||||
# Test 1: Non-empty rules
|
||||
errors = check_non_empty_rules(fragments)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 非空规则检查: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 非空规则检查: 全部通过 ({total_units} 个片段)")
|
||||
|
||||
# Test 2: Sources have logic tree references
|
||||
errors = check_sources_have_logic_tree_nodes(fragments)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 来源节点引用: {len(errors)} 个规则缺少来源引用")
|
||||
for e in errors[:10]: # Show first 10
|
||||
print(f" - {e}")
|
||||
if len(errors) > 10:
|
||||
print(f" ... 还有 {len(errors) - 10} 个")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 来源节点引用: 全部通过")
|
||||
|
||||
# Test 3: Trigger conditions completeness
|
||||
errors = check_trigger_conditions(fragments)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 触发条件完整性: {len(errors)} 个条件不完整")
|
||||
for e in errors[:10]:
|
||||
print(f" - {e}")
|
||||
if len(errors) > 10:
|
||||
print(f" ... 还有 {len(errors) - 10} 个")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 触发条件完整性: 全部通过")
|
||||
|
||||
# Test 4: No duplicate rule_ids
|
||||
errors = check_duplicate_rule_ids(fragments)
|
||||
if errors:
|
||||
print(f"\n{FAIL} rule_id 唯一性: 发现重复")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} rule_id 唯一性: 全部通过")
|
||||
|
||||
# Test 5: Valid action types
|
||||
errors = check_action_types(fragments)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 动作类型检查: {len(errors)} 个问题")
|
||||
for e in errors[:10]:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 动作类型检查: 全部通过")
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
total_failures = len(all_errors)
|
||||
|
||||
if total_failures == 0:
|
||||
print(f"{PASS} 所有测试通过!")
|
||||
else:
|
||||
print(f"{FAIL} 测试失败: {total_failures} 个错误")
|
||||
print("\n建议:")
|
||||
print(" 1. 检查 ir_fragments.json 中出错的规则")
|
||||
print(" 2. 如果某些功能单元的规则为空,检查上下文包是否丢失了关键信息")
|
||||
print(" 3. 调整 Prompt(prompts/step2_ir_extraction.txt)或上下文提取逻辑后重新运行")
|
||||
|
||||
print(f"\n统计:")
|
||||
print(f" 功能单元数: {total_units}")
|
||||
print(f" 规则总数: {total_rules}")
|
||||
error_units = sum(1 for f in fragments if f.get("error"))
|
||||
if error_units:
|
||||
print(f" 提取失败的单元: {error_units}")
|
||||
|
||||
return total_failures == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
Tests for Stage 3 (Merge & Audit).
|
||||
|
||||
Validates:
|
||||
- ir_final.json exists and is well-formed
|
||||
- No duplicate rule_ids
|
||||
- All rule_ids follow naming convention
|
||||
- ir_audit_report.md exists and contains required sections
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
import config
|
||||
|
||||
|
||||
PASS = "[PASS]"
|
||||
FAIL = "[FAIL]"
|
||||
WARN = "[WARN]"
|
||||
|
||||
|
||||
def load_ir_final():
|
||||
"""Load ir_final.json."""
|
||||
try:
|
||||
return config.load_json(config.IR_FINAL_JSON)
|
||||
except FileNotFoundError:
|
||||
print(f"{FAIL} ir_final.json 未找到: {config.IR_FINAL_JSON}")
|
||||
print(" 请先运行 step3_merge_and_audit.py")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def load_audit_report():
|
||||
"""Load ir_audit_report.md if it exists."""
|
||||
try:
|
||||
with open(config.IR_AUDIT_REPORT_MD, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"{FAIL} ir_audit_report.md 未找到: {config.IR_AUDIT_REPORT_MD}")
|
||||
print(" 请先运行 step3_merge_and_audit.py")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_rule_ids(ir: dict) -> list[str]:
|
||||
"""Check for duplicate rule_ids and naming convention."""
|
||||
errors = []
|
||||
rules = ir.get("rules", [])
|
||||
rule_ids = [r.get("rule_id", "") for r in rules]
|
||||
|
||||
# No duplicates
|
||||
duplicates = [rid for rid, count in Counter(rule_ids).items() if count > 1]
|
||||
if duplicates:
|
||||
errors.append(f"重复 rule_id: {duplicates}")
|
||||
|
||||
# Naming convention: FEATURE-TYPE-CAT-NN
|
||||
pattern = re.compile(r"^[A-Z]+-\d{3}-(SYS|UI|SDK)-FG-\d{2}$")
|
||||
for rid in rule_ids:
|
||||
if rid and not pattern.match(rid):
|
||||
errors.append(f"rule_id 命名不规范: '{rid}' (期望格式: DRL-001-SYS-FG-01)")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_top_level_structure(ir: dict) -> list[str]:
|
||||
"""Check that ir_final has the required top-level fields."""
|
||||
errors = []
|
||||
for field in ["feature", "feature_id", "rules"]:
|
||||
if field not in ir:
|
||||
errors.append(f"ir_final 缺少顶层字段: {field}")
|
||||
|
||||
if not isinstance(ir.get("rules"), list):
|
||||
errors.append("ir_final.rules 必须是数组")
|
||||
elif len(ir["rules"]) == 0:
|
||||
errors.append("ir_final.rules 为空")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_rule_completeness(rules: list[dict]) -> list[str]:
|
||||
"""Check each rule has all required fields."""
|
||||
errors = []
|
||||
required_fields = ["rule_id", "description", "priority", "sources", "trigger", "actions"]
|
||||
for i, rule in enumerate(rules):
|
||||
rid = rule.get("rule_id", f"rule[{i}]")
|
||||
for field in required_fields:
|
||||
if field not in rule:
|
||||
errors.append(f"{rid}: 缺少字段 '{field}'")
|
||||
# sources must be non-empty
|
||||
if not rule.get("sources"):
|
||||
errors.append(f"{rid}: sources 为空")
|
||||
# actions must be non-empty
|
||||
if not rule.get("actions"):
|
||||
errors.append(f"{rid}: actions 为空")
|
||||
return errors
|
||||
|
||||
|
||||
def check_audit_report(report: str) -> list[str]:
|
||||
"""Check audit report has required sections."""
|
||||
errors = []
|
||||
|
||||
required_sections = [
|
||||
"逻辑树节点覆盖率",
|
||||
"表格枚举覆盖",
|
||||
"开关状态",
|
||||
]
|
||||
for section in required_sections:
|
||||
if section not in report:
|
||||
errors.append(f"审计报告缺少章节: {section}")
|
||||
|
||||
# Should have at least one coverage percentage
|
||||
if "覆盖率" not in report and "%" not in report:
|
||||
errors.append("审计报告中未找到覆盖率统计")
|
||||
|
||||
# Should have the human review notice
|
||||
if "人工审查" not in report:
|
||||
errors.append("审计报告缺少人工审查提示")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
print("=" * 60)
|
||||
print("Step 3 自检测试")
|
||||
print("=" * 60)
|
||||
|
||||
ir = load_ir_final()
|
||||
report = load_audit_report()
|
||||
rules = ir.get("rules", [])
|
||||
all_errors = []
|
||||
|
||||
# Test 1: Top-level structure
|
||||
errors = check_top_level_structure(ir)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 顶层结构检查: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 顶层结构检查: 通过 (feature={ir.get('feature')}, feature_id={ir.get('feature_id')})")
|
||||
|
||||
# Test 2: rule_id uniqueness and naming
|
||||
errors = check_rule_ids(ir)
|
||||
if errors:
|
||||
print(f"\n{FAIL} rule_id 检查: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} rule_id 检查: 全部通过 ({len(rules)} 个唯一 ID)")
|
||||
|
||||
# Test 3: Rule field completeness
|
||||
errors = check_rule_completeness(rules)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 规则字段完整性: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 规则字段完整性: 全部通过")
|
||||
|
||||
# Test 4: Audit report content
|
||||
errors = check_audit_report(report)
|
||||
if errors:
|
||||
print(f"\n{FAIL} 审计报告检查: {len(errors)} 个错误")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
all_errors.extend(errors)
|
||||
else:
|
||||
print(f"\n{PASS} 审计报告检查: 全部通过")
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
total_failures = len(all_errors)
|
||||
|
||||
if total_failures == 0:
|
||||
print(f"{PASS} 所有测试通过!")
|
||||
print(f"\n最终交付物:")
|
||||
print(f" - {config.IR_FINAL_JSON} ({len(rules)} 条规则)")
|
||||
print(f" - {config.IR_AUDIT_REPORT_MD}")
|
||||
else:
|
||||
print(f"{FAIL} 测试失败: {total_failures} 个错误")
|
||||
print("\n建议: 检查 ir_fragments.json 和合并逻辑,修复问题后重新运行 step3_merge_and_audit.py")
|
||||
|
||||
return total_failures == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user