init the project
This commit is contained in:
+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