78 lines
3.0 KiB
Python
78 lines
3.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""统一测试报告:PASS/FAIL/SKIP/SENT 四态 + Markdown 输出 + 退出码."""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from dataclasses import dataclass, field
|
||
from datetime import datetime
|
||
|
||
STATUSES = ("PASS", "FAIL", "SKIP", "SENT")
|
||
|
||
|
||
@dataclass
|
||
class CaseResult:
|
||
suite: str
|
||
name: str
|
||
status: str
|
||
detail: str = ""
|
||
|
||
|
||
@dataclass
|
||
class HarnessReport:
|
||
title: str = "K1 HIL 自动化测试报告"
|
||
cases: list[CaseResult] = field(default_factory=list)
|
||
notes: list[str] = field(default_factory=list)
|
||
started: datetime = field(default_factory=datetime.now)
|
||
|
||
def add(self, suite: str, name: str, status: str, detail: str = "") -> None:
|
||
status = status.upper()
|
||
assert status in STATUSES, f"bad status {status}"
|
||
self.cases.append(CaseResult(suite, name, status, detail))
|
||
print(f"[{status:>4}] {suite}/{name}" + (f" | {detail}" if detail else ""), flush=True)
|
||
|
||
def extend_rows(self, suite: str, rows) -> None:
|
||
"""吸收 test_protocol_app_sim.Results 风格的 (name, status, detail) 行."""
|
||
for name, status, detail in rows:
|
||
self.add(suite, name, status, detail)
|
||
|
||
def counts(self) -> dict:
|
||
return {s: sum(1 for c in self.cases if c.status == s) for s in STATUSES}
|
||
|
||
def exit_code(self) -> int:
|
||
return 1 if self.counts()["FAIL"] else 0
|
||
|
||
def summary_text(self) -> str:
|
||
n = self.counts()
|
||
return f"PASS={n['PASS']} FAIL={n['FAIL']} SKIP={n['SKIP']} SENT={n['SENT']}"
|
||
|
||
def print_summary(self) -> None:
|
||
print("\n===== 汇总 =====", flush=True)
|
||
for suite in dict.fromkeys(c.suite for c in self.cases):
|
||
sub = [c for c in self.cases if c.suite == suite]
|
||
n = {s: sum(1 for c in sub if c.status == s) for s in STATUSES}
|
||
print(f" {suite:<12} PASS={n['PASS']} FAIL={n['FAIL']} SKIP={n['SKIP']} SENT={n['SENT']}", flush=True)
|
||
print(f" {'TOTAL':<12} {self.summary_text()}", flush=True)
|
||
for c in self.cases:
|
||
if c.status == "FAIL":
|
||
print(f" FAIL: {c.suite}/{c.name} | {c.detail}", flush=True)
|
||
|
||
def write_markdown(self, path: str) -> str:
|
||
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
||
lines = [
|
||
f"# {self.title}",
|
||
"",
|
||
f"- 时间: {self.started.isoformat(timespec='seconds')}",
|
||
f"- 结果: {self.summary_text()}",
|
||
]
|
||
for note in self.notes:
|
||
lines.append(f"- {note}")
|
||
lines += ["", "| 套件 | 用例 | 结果 | 详情 |", "|------|------|------|------|"]
|
||
for c in self.cases:
|
||
detail = c.detail.replace("|", "\\|").replace("\n", " ")
|
||
lines.append(f"| {c.suite} | {c.name} | {c.status} | {detail} |")
|
||
lines.append("")
|
||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||
f.write("\n".join(lines))
|
||
print(f"报告已保存: {os.path.abspath(path)}", flush=True)
|
||
return path
|