Ctrl K
ComunidadGuías…

scripts/test-table.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""
Reads `go test -json` output from stdin and renders a readable ASCII table.

Usage:
    go test -json -tags integration -count=1 -run TestE2E ./cmd/server/ | python3 scripts/test-table.py
"""

import json
import sys

# ANSI colours
GREEN  = "\033[32m"
RED    = "\033[31m"
YELLOW = "\033[33m"
BOLD   = "\033[1m"
RESET  = "\033[0m"


def strip_prefix(name: str) -> str:
    """Remove the top-level test function prefix (TestE2E_) for brevity."""
    if name.startswith("TestE2E_"):
        return name[len("TestE2E_"):]
    if name.startswith("TestE2E/"):
        return name[len("TestE2E/"):]
    return name


def main():
    # Collect events keyed by test name
    results: dict[str, dict] = {}        # name -> {status, elapsed, parent, output}
    order: list[str] = []                # insertion order
    package_elapsed: float = 0.0
    build_error_lines: list[str] = []
    in_build_error = False

    for raw in sys.stdin:
        raw = raw.rstrip("\n")
        if not raw:
            continue

        # Pass through non-JSON lines (e.g. log output before tests start)
        try:
            ev = json.loads(raw)
        except json.JSONDecodeError:
            # Raw stderr from the test binary
            if "FAIL" in raw or "build failed" in raw.lower():
                build_error_lines.append(raw)
            continue

        action = ev.get("Action", "")
        test   = ev.get("Test", "")
        pkg    = ev.get("Package", "")

        if action == "build-fail":
            build_error_lines.append(ev.get("Output", "build failed"))
            continue

        if not test:
            # Package-level event
            if action == "pass" and "Elapsed" in ev:
                package_elapsed = ev["Elapsed"]
            if action == "fail":
                package_elapsed = ev.get("Elapsed", 0.0)
            continue

        if test not in results:
            results[test] = {"status": "run", "elapsed": 0.0, "output": []}
            order.append(test)

        if action == "output":
            line = ev.get("Output", "").rstrip("\n")
            # Only keep lines that look like user log output (not === RUN / --- PASS etc.)
            if not (line.startswith("=== RUN") or
                    line.startswith("=== PAUSE") or
                    line.startswith("=== CONT") or
                    line.startswith("--- PASS") or
                    line.startswith("--- FAIL") or
                    line.startswith("--- SKIP") or
                    line.strip() == "" or
                    line.startswith("PASS") or
                    line.startswith("FAIL") or
                    line.startswith("ok ")):
                results[test]["output"].append(line)
        elif action in ("pass", "fail", "skip"):
            results[test]["status"]  = action
            results[test]["elapsed"] = ev.get("Elapsed", 0.0)

    if build_error_lines:
        print(f"{RED}{BOLD}Build failed:{RESET}")
        for l in build_error_lines:
            print(f"  {l}")
        sys.exit(1)

    if not results:
        print(f"{YELLOW}No test results found.{RESET}")
        sys.exit(0)

    # ---- Separate top-level tests from sub-tests ----------------------------
    top_level  = [n for n in order if "/" not in n]
    sub_tests  = [n for n in order if "/" in n]

    def parent_of(name: str) -> str:
        return name.rsplit("/", 1)[0]

    def short_sub(name: str) -> str:
        """Return indented sub-test label."""
        parts = name.split("/")
        label = parts[-1].replace("_", " ")
        depth = len(parts) - 1
        indent = "    " * depth
        return f"{indent}└─ {label}"

    # ---- Build display rows -------------------------------------------------
    # Each row: (label, status, elapsed_str, is_sub)
    rows = []
    for top in top_level:
        r = results[top]
        rows.append((strip_prefix(top), r["status"], r["elapsed"], False))
        # attach sub-tests that belong to this top-level test
        for sub in sub_tests:
            if parent_of(sub) == top or parent_of(sub).startswith(top + "/"):
                rs = results[sub]
                depth = sub.count("/")
                short = "    " * depth + "└─ " + sub.split("/")[-1].replace("_", " ")
                rows.append((short, rs["status"], rs["elapsed"], True))

    # ---- Column widths ------------------------------------------------------
    col1_min = 45
    col1_w = max(col1_min, max(len(r[0]) for r in rows) + 2)
    col2_w = 8   # " ✓ PASS "
    col3_w = 9   # " 0.000s  "

    def fmt_status(s: str) -> str:
        if s == "pass":
            return f"{GREEN}✓ PASS{RESET}"
        if s == "fail":
            return f"{RED}✗ FAIL{RESET}"
        if s == "skip":
            return f"{YELLOW}⊘ SKIP{RESET}"
        return s

    def fmt_time(t: float) -> str:
        if t < 0.001:
            return "  —    "
        return f"{t:.3f}s"

    # ---- Draw table ---------------------------------------------------------
    h_line  = "─" * col1_w
    h_line2 = "─" * col2_w
    h_line3 = "─" * col3_w

    top_border    = f"┌{h_line}┬{h_line2}┬{h_line3}┐"
    header_sep    = f"├{h_line}┼{h_line2}┼{h_line3}┤"
    row_sep       = f"├{h_line}┼{h_line2}┼{h_line3}┤"
    bottom_border = f"└{h_line}┴{h_line2}┴{h_line3}┘"

    def row_line(label: str, status_str: str, time_str: str) -> str:
        # Pad label — but ANSI codes add invisible chars, so pad before colouring
        label_pad = label.ljust(col1_w - 2)
        # status_str and time_str may contain ANSI; measure visible length
        visible_status = status_str.replace(GREEN,"").replace(RED,"").replace(YELLOW,"").replace(BOLD,"").replace(RESET,"")
        visible_time   = time_str
        status_pad = status_str + " " * (col2_w - len(visible_status))
        time_pad   = " " + time_str.rjust(col3_w - 1)
        return f"│ {label_pad} │{status_pad}│{time_pad}│"

    print()
    print(top_border)
    print(row_line(f"{BOLD}Test{RESET}", f"{BOLD}Status{RESET}", f"{BOLD}Time{RESET}"))
    print(header_sep)

    passed = 0
    failed = 0
    for i, (label, status, elapsed, is_sub) in enumerate(rows):
        if not is_sub and i > 0:
            print(row_sep)
        status_str = fmt_status(status)
        time_str   = fmt_time(elapsed)
        print(row_line(label, status_str, time_str))
        if status == "pass": passed += 1
        elif status == "fail": failed += 1

    # ---- Summary row --------------------------------------------------------
    print(header_sep)
    total_top = sum(1 for r in rows if not r[3])
    if failed == 0:
        summary_label = f"{GREEN}{BOLD}{passed} passed{RESET}  ·  {failed} failed"
    else:
        summary_label = f"{GREEN}{passed} passed{RESET}  ·  {RED}{BOLD}{failed} failed{RESET}"
    print(row_line(summary_label, "", fmt_time(package_elapsed)))
    print(bottom_border)
    print()

    if failed > 0:
        # Print captured output for failed tests
        print(f"{RED}{BOLD}Failed test output:{RESET}")
        for name, r in results.items():
            if r["status"] == "fail" and r["output"]:
                print(f"\n  {BOLD}{strip_prefix(name)}{RESET}")
                for line in r["output"]:
                    print(f"    {line}")
        print()
        sys.exit(1)


if __name__ == "__main__":
    main()