feat: Vollstaendige Demo-Doku — Safety, Manuals, Reports, API-Doc
Validate / build-test (macos-latest) (push) Failing after 4s
Validate / build-test (windows-latest) (push) Failing after 15s
Validate / build-test (ubuntu-latest) (push) Failing after 15s
Validate / reports (push) Has been skipped
Release / release (push) Successful in 50s
Validate / build-test (macos-latest) (push) Failing after 4s
Validate / build-test (windows-latest) (push) Failing after 15s
Validate / build-test (ubuntu-latest) (push) Failing after 15s
Validate / reports (push) Has been skipped
Release / release (push) Successful in 50s
Neue Word-Dokumente (alle aus Markdown via pandoc): Safety (docs/safety/): - HARA.docx — Hazard Analysis & Risk Assessment, leitet ASIL-D ab - Safety-Case.docx — Argumentation pro Safety Goal (GSN-Stil) - FMEDA.docx — Pro-Komponente Failure Modes + Diagnostic Coverage - MISRA-Compliance-Statement.docx — formaler MISRA-Nachweis - Verification-Report.docx — V-Modell rechte Seite Zusammenfassung - Tool-Qualification-Cppcheck.docx — Tool-Qual (TCL2/ASIL-D) Manuals (docs/manuals/): - User-Manual.docx — Fahrerhandbuch-Auszug - Service-Manual.docx — Werkstatt-Doku mit UDS-DTCs CI-Erweiterungen: - Doxyfile + `make docs` — API-Dokumentation aus src/ - tools/generate_test_report.py + `make test-report` — Test-Summary HTML - validate.yml: Doxygen + Test-Report als CI-Artefakte - release.yml: alle Word-Docs + Engineering-Artefakte ins Release-Bundle README: - Komplette Tour durch alle Artefakte - Repo-Struktur-Diagramm aktualisiert
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Erzeugt einen Test-Summary-Report aus dem Output unserer Unit-Tests.
|
||||
|
||||
Liest die Test-Output-Datei (build/test-output.txt) und erzeugt:
|
||||
- build/test-report.md
|
||||
- build/test-report.html
|
||||
|
||||
Workflow:
|
||||
make test > build/test-output.txt 2>&1
|
||||
python3 tools/generate_test_report.py
|
||||
|
||||
Oder: `make test-report` macht beides.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import html
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
BUILD = REPO / "build"
|
||||
TEST_OUTPUT = BUILD / "test-output.txt"
|
||||
|
||||
|
||||
def reqs_for(test_name: str) -> list[str]:
|
||||
src = REPO / "tests" / "unit" / f"{test_name}.c"
|
||||
if not src.exists():
|
||||
return []
|
||||
head = src.read_text()[:400]
|
||||
m = re.search(r"@reqs\s+([A-Z0-9 \-,]+)", head)
|
||||
return re.findall(r"[A-Z]+-\d+", m.group(1)) if m else []
|
||||
|
||||
|
||||
def parse_output(text: str) -> list[dict]:
|
||||
"""Split into suite-blocks separated by '== test_xxx ==' headers."""
|
||||
suites = []
|
||||
cur = None
|
||||
for line in text.splitlines():
|
||||
m = re.match(r"==\s+(test_\w+)\s+==", line)
|
||||
if m:
|
||||
if cur is not None:
|
||||
suites.append(cur)
|
||||
cur = {"binary": m.group(1), "tests": []}
|
||||
continue
|
||||
if cur is None:
|
||||
continue
|
||||
m = re.match(r"\s+TEST\s+(.+?)\s+\.\.\.\s+(\w+)", line)
|
||||
if m:
|
||||
cur["tests"].append((m.group(1).strip(), m.group(2)))
|
||||
if cur is not None:
|
||||
suites.append(cur)
|
||||
for s in suites:
|
||||
s["total"] = len(s["tests"])
|
||||
s["failed"] = sum(1 for _, st in s["tests"] if st.lower() != "ok")
|
||||
s["passed"] = s["total"] - s["failed"]
|
||||
return suites
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not TEST_OUTPUT.exists():
|
||||
print(f"ERROR: {TEST_OUTPUT} fehlt. Bitte zuerst `make test > {TEST_OUTPUT.relative_to(REPO)} 2>&1` ausfuehren.")
|
||||
return 1
|
||||
|
||||
output = TEST_OUTPUT.read_text()
|
||||
results = parse_output(output)
|
||||
if not results:
|
||||
print("ERROR: keine Test-Suite im Output gefunden.")
|
||||
return 1
|
||||
|
||||
total = sum(r["total"] for r in results)
|
||||
failed = sum(r["failed"] for r in results)
|
||||
passed = total - failed
|
||||
now = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
|
||||
# Markdown
|
||||
md = [f"# demo-epb — Test Summary Report\n\n",
|
||||
f"**Datum:** {now}\n\n",
|
||||
f"**Gesamt:** {total} Tests, {passed} bestanden, {failed} fehlgeschlagen\n\n",
|
||||
f"**Status:** {'PASS' if failed == 0 else 'FAIL'}\n\n",
|
||||
"## Pro Test-Suite\n\n",
|
||||
"| Suite | Anzahl | Bestanden | Fehlgeschlagen | Anforderungen |\n",
|
||||
"|-------|--------|-----------|-----------------|---------------|\n"]
|
||||
for r in results:
|
||||
reqs = ", ".join(reqs_for(r["binary"])) or "—"
|
||||
md.append(f"| `{r['binary']}` | {r['total']} | {r['passed']} | "
|
||||
f"{r['failed']} | {reqs} |\n")
|
||||
md.append("\n## Details\n\n")
|
||||
for r in results:
|
||||
md.append(f"### `{r['binary']}`\n\n")
|
||||
md.append("| # | Test | Status |\n|---|------|--------|\n")
|
||||
for i, (name, status) in enumerate(r["tests"], 1):
|
||||
md.append(f"| {i} | {name} | {status} |\n")
|
||||
md.append("\n")
|
||||
(BUILD / "test-report.md").write_text("".join(md))
|
||||
|
||||
# HTML
|
||||
badge_cls = "pass-badge" if failed == 0 else "fail-badge"
|
||||
badge_txt = "PASS" if failed == 0 else "FAIL"
|
||||
h = [
|
||||
"<!doctype html><html lang='de'><head>",
|
||||
"<meta charset='utf-8'><title>demo-epb Test Report</title>",
|
||||
"<style>",
|
||||
"body{font-family:-apple-system,Segoe UI,sans-serif;padding:20px}",
|
||||
"h1{color:#1f3864}h2{color:#1f3864;margin-top:30px}",
|
||||
"table{border-collapse:collapse;width:100%;font-size:14px;margin:10px 0}",
|
||||
"th,td{border:1px solid #ccc;padding:6px 10px;text-align:left}",
|
||||
"th{background:#f0f0f0}",
|
||||
".pass{color:#0a0;font-weight:bold}.fail{color:#c00;font-weight:bold}",
|
||||
".badge{display:inline-block;padding:4px 10px;border-radius:4px;color:#fff;font-weight:bold}",
|
||||
".pass-badge{background:#0a0}.fail-badge{background:#c00}",
|
||||
"</style></head><body>",
|
||||
"<h1>demo-epb — Test Summary Report</h1>",
|
||||
f"<p><strong>Datum:</strong> {now}</p>",
|
||||
f"<p><strong>Gesamt:</strong> {total} Tests, {passed} bestanden, {failed} fehlgeschlagen — "
|
||||
f"<span class='badge {badge_cls}'>{badge_txt}</span></p>",
|
||||
"<h2>Pro Test-Suite</h2>",
|
||||
"<table><tr><th>Suite</th><th>Anzahl</th><th>Bestanden</th>"
|
||||
"<th>Fehlgeschlagen</th><th>Anforderungen</th></tr>",
|
||||
]
|
||||
for r in results:
|
||||
reqs = ", ".join(reqs_for(r["binary"])) or "—"
|
||||
h.append(
|
||||
f"<tr><td><code>{html.escape(r['binary'])}</code></td>"
|
||||
f"<td>{r['total']}</td><td>{r['passed']}</td>"
|
||||
f"<td>{r['failed']}</td><td>{html.escape(reqs)}</td></tr>"
|
||||
)
|
||||
h.append("</table>")
|
||||
for r in results:
|
||||
h.append(f"<h2><code>{html.escape(r['binary'])}</code></h2>")
|
||||
h.append("<table><tr><th>#</th><th>Test</th><th>Status</th></tr>")
|
||||
for i, (name, status) in enumerate(r["tests"], 1):
|
||||
cls = "pass" if status.lower() == "ok" else "fail"
|
||||
h.append(
|
||||
f"<tr><td>{i}</td><td>{html.escape(name)}</td>"
|
||||
f"<td class='{cls}'>{html.escape(status)}</td></tr>"
|
||||
)
|
||||
h.append("</table>")
|
||||
h.append("</body></html>")
|
||||
(BUILD / "test-report.html").write_text("\n".join(h))
|
||||
|
||||
print(f"Wrote {BUILD / 'test-report.md'}")
|
||||
print(f"Wrote {BUILD / 'test-report.html'}")
|
||||
print(f"\n{total} tests: {passed} passed, {failed} failed.")
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user