#!/usr/bin/env python3 """Render a per-test-project table from .trx files for a Gitea job summary (#136). Reads every *.trx in the given directory (default: TestResults), pulls each project's counters + assembly name, and prints a GitHub/Gitea-flavoured markdown table to stdout. The CI step redirects that into $GITHUB_STEP_SUMMARY. Stdlib only. """ import glob import os import sys import xml.etree.ElementTree as ET NS = {"t": "http://microsoft.com/schemas/VisualStudio/TeamTest/2010"} def project_name(root): # The test assembly path, e.g. …/services/domain/Big.Tests/bin/…/big.tests.dll. Prefer the # owning service folder (services/) so "domain" shows rather than the opaque "big.tests"; # fall back to the assembly basename for projects outside services/ (e.g. tests/acceptance). ut = root.find(".//t:TestDefinitions/t:UnitTest", NS) storage = ut.get("storage") if ut is not None else None if not storage: return None parts = storage.replace("\\", "/").split("/") if "services" in parts: return parts[parts.index("services") + 1] base = os.path.basename(parts[-1]) return base[:-4] if base.lower().endswith(".dll") else base def parse(path): root = ET.parse(path).getroot() c = root.find(".//t:ResultSummary/t:Counters", NS) if c is None: return None total = int(c.get("total", 0)) if total == 0: # e.g. the Integration project, filtered out of the unit run return None executed = int(c.get("executed", 0)) passed = int(c.get("passed", 0)) failed = int(c.get("failed", 0)) + int(c.get("error", 0)) skipped = total - executed return { "name": project_name(root) or os.path.basename(path), "passed": passed, "failed": failed, "skipped": skipped, "total": total, } def main(results_dir): rows = [r for r in (parse(p) for p in sorted(glob.glob(os.path.join(results_dir, "*.trx")))) if r] if not rows: print("_No test results found._") return 0 rows.sort(key=lambda r: r["name"]) print("## ✅ Unit tests\n") print("| Project | Result | Passed | Failed | Skipped | Total |") print("| ------- | :----: | -----: | -----: | ------: | ----: |") for r in rows: status = "❌" if r["failed"] else "✅" print(f"| {r['name']} | {status} | {r['passed']} | {r['failed']} | {r['skipped']} | {r['total']} |") return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "TestResults"))