first commit
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
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())
|
||||
Reference in New Issue
Block a user