from __future__ import annotations import argparse import json import re import subprocess import sys from pathlib import Path from auto_test_lib import load_yaml_config, now_slug ALL_DOMAINS = [ "company", "supplier", "project", "contract", "contract_change", "construction_task", "construction_task_detail", "material", "material_detail", "contract_settlement", "contract_settlement_detail", "personnel", "personnel_detail", "preparation", "production", "lar", "lar_change", "project_detail_data", "units", ] def _list_report_dirs(report_base: Path) -> set[Path]: if not report_base.exists(): return set() return {path for path in report_base.iterdir() if path.is_dir()} def _extract_report_dir(stdout: str, stderr: str) -> Path | None: combined = "\n".join(part for part in [stdout, stderr] if part) match = re.search(r"报告已生成:\s*(.+?/report\.md)", combined) if not match: return None return Path(match.group(1).strip()).parent def _build_test_summary(domain_reports: list[dict]) -> dict: cases: list[dict] = [] passed_domains = 0 failed_domains = 0 for domain in domain_reports: if domain.get("passed"): passed_domains += 1 else: failed_domains += 1 for round_report in domain.get("rounds", []): cases.append( { "nodeid": f"{domain['domain']}::{round_report['name']}", "domain": domain["domain"], "case": round_report["name"], "purpose": round_report.get("purpose", ""), "passed": bool(round_report.get("passed")), "result": "PASSED" if round_report.get("passed") else "FAILED", "returncode": round_report.get("returncode"), "issue_count": len(round_report.get("issues", [])), "issues": round_report.get("issues", []), } ) passed_cases = sum(1 for case in cases if case["passed"]) return { "total_domains": len(domain_reports), "passed_domains": passed_domains, "failed_domains": failed_domains, "total_cases": len(cases), "passed_cases": passed_cases, "failed_cases": len(cases) - passed_cases, "cases": cases, } def _build_markdown_report(run_id: str, summary: dict, domain_reports: list[dict], run_records: list[dict]) -> str: lines = [ f"# 自动测试聚合报告 {run_id}", "", "## 运行方式", "", "- 本报告由 `scripts/run_auto_test_all_domains.py` 聚合生成", "- 每个 domain 独立初始化并单独执行,避免跨 domain 状态污染", "", "## Pytest 风格汇总", "", f"collected {summary['total_cases']} cases across {summary['total_domains']} domains", "", ] for case in summary["cases"]: lines.append(f"- {case['nodeid']} {case['result']}") lines.extend( [ "", f"== {summary['passed_cases']} passed, {summary['failed_cases']} failed, {summary['passed_domains']} passed domains, {summary['failed_domains']} failed domains ==", "", "## Domain 汇总", "", "| Domain | 结果 | 单域报告 |", "| --- | --- | --- |", ] ) run_record_by_domain = {item["domain"]: item for item in run_records} for domain in domain_reports: run_record = run_record_by_domain.get(domain["domain"], {}) lines.append( f"| {domain['domain']} | {'通过' if domain.get('passed') else '失败'} | {run_record.get('report_dir', '')} |" ) lines.append("") for domain in domain_reports: lines.append(f"## Domain: {domain['domain']}") if domain.get("description"): lines.extend(["", domain["description"]]) lines.append("") lines.append(f"- 结果: {'通过' if domain.get('passed') else '失败'}") record = run_record_by_domain.get(domain["domain"], {}) if record.get("report_dir"): lines.append(f"- 单域报告目录: {record['report_dir']}") if record.get("returncode") not in (None, 0): lines.append(f"- 单域脚本返回码: {record['returncode']}") if domain.get("issues"): lines.append("- 问题:") for issue in domain["issues"]: lines.append(f" - {issue}") lines.append("") return "\n".join(lines) def main() -> int: parser = argparse.ArgumentParser(description="一轮执行所有已纳入 auto-test 的 domain") parser.add_argument( "--config", default="tests/fixtures/auto_sync/auto_test_config.yaml", help="自动测试配置文件路径", ) parser.add_argument( "--skip-init", action="store_true", help="透传给 run_auto_test.py", ) parser.add_argument( "--clear-only", action="store_true", help="透传给 run_auto_test.py", ) args = parser.parse_args() runner = Path(__file__).with_name("run_auto_test.py") config = load_yaml_config(args.config) report_base = Path(config["paths"]["report_dir"]) aggregate_run_id = now_slug() aggregate_report_dir = report_base / aggregate_run_id aggregate_report_dir.mkdir(parents=True, exist_ok=True) domain_reports: list[dict] = [] run_records: list[dict] = [] overall_returncode = 0 for domain in ALL_DOMAINS: command = [sys.executable, str(runner), "--config", args.config, "--domain", domain] if args.skip_init: command.append("--skip-init") if args.clear_only: command.append("--clear-only") before_dirs = _list_report_dirs(report_base) result = subprocess.run(command, check=False, text=True, capture_output=True) after_dirs = _list_report_dirs(report_base) new_dirs = sorted(after_dirs - before_dirs) report_dir = _extract_report_dir(result.stdout, result.stderr) if report_dir is None and new_dirs: report_dir = new_dirs[-1] run_record = { "domain": domain, "returncode": result.returncode, "report_dir": str(report_dir) if report_dir else None, "stdout_tail": (result.stdout or "")[-4000:], "stderr_tail": (result.stderr or "")[-4000:], } run_records.append(run_record) if result.returncode != 0: overall_returncode = result.returncode if report_dir is None: domain_reports.append( { "domain": domain, "description": "", "passed": False, "issues": ["未找到单域报告目录"], "rounds": [], } ) continue domain_report_path = report_dir / f"{domain}.json" if not domain_report_path.exists(): domain_reports.append( { "domain": domain, "description": "", "passed": False, "issues": [f"缺少单域报告文件: {domain_report_path}"], "rounds": [], } ) overall_returncode = overall_returncode or 1 continue domain_reports.append(json.loads(domain_report_path.read_text(encoding="utf-8"))) summary = _build_test_summary(domain_reports) detail = { "mode": "isolated-per-domain", "aggregate_run_id": aggregate_run_id, "summary": summary, "runs": run_records, "domains": domain_reports, } (aggregate_report_dir / "summary.json").write_text( json.dumps(detail, ensure_ascii=False, indent=2), encoding="utf-8", ) (aggregate_report_dir / "report.md").write_text( _build_markdown_report(aggregate_run_id, summary, domain_reports, run_records), encoding="utf-8", ) print(f"聚合报告已生成: {aggregate_report_dir / 'report.md'}") if summary["failed_domains"]: return overall_returncode or 1 return 0 if __name__ == "__main__": raise SystemExit(main())