Skip to content

Reports

Rendering helpers for SuiteResult — console tables, JSON and JUnit XML.

modeltest.core.report

Report rendering: console table, JSON, and JUnit XML (for CI/CD).

render_report(result, style='table')

Render a SuiteResult in the requested style.

Args: result: The suite outcome to render. style: "table" (console, default), "json" or "junit".

Returns: The rendered report as a string.

Raises: ValueError: Via the JSON encoder path if the payload is not serializable (should not happen for standard results).

Source code in modeltest/core/report.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def render_report(result: SuiteResult, style: str = "table") -> str:
    """Render a ``SuiteResult`` in the requested style.

    Args:
        result: The suite outcome to render.
        style: ``"table"`` (console, default), ``"json"`` or ``"junit"``.

    Returns:
        The rendered report as a string.

    Raises:
        ValueError: Via the JSON encoder path if the payload is not
            serializable (should not happen for standard results).
    """
    if style == "json":
        return to_json(result)
    if style == "junit":
        return to_junit_xml(result)
    return _render_table(result)

to_json(result)

Serialize a SuiteResult to a JSON string.

Includes the suite name, aggregate counts and one object per test with name, status, detail, duration and recorded metrics.

Args: result: The suite outcome to serialize.

Returns: Pretty-printed JSON (2-space indent).

Source code in modeltest/core/report.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def to_json(result: SuiteResult) -> str:
    """Serialize a ``SuiteResult`` to a JSON string.

    Includes the suite name, aggregate counts and one object per test with
    name, status, detail, duration and recorded metrics.

    Args:
        result: The suite outcome to serialize.

    Returns:
        Pretty-printed JSON (2-space indent).
    """
    payload = {
        "suite": result.suite_name,
        "passed": result.passed,
        "num_passed": result.num_passed,
        "num_failed": result.num_failed,
        "tests": [
            {
                "name": r.name,
                "status": r.status.value,
                "detail": r.detail,
                "duration_ms": r.duration_ms,
                "metrics": r.metrics,
            }
            for r in result.results
        ],
    }
    return json.dumps(payload, indent=2)

to_junit_xml(result)

Serialize a SuiteResult to JUnit XML.

The output wraps everything in the standard <testsuites> element expected by CI test reporters (GitHub Actions, Jenkins, GitLab...): FAILED tests become <failure> nodes, ERROR tests <error>.

Args: result: The suite outcome to serialize.

Returns: The XML document as a string.

Source code in modeltest/core/report.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def to_junit_xml(result: SuiteResult) -> str:
    """Serialize a ``SuiteResult`` to JUnit XML.

    The output wraps everything in the standard ``<testsuites>`` element
    expected by CI test reporters (GitHub Actions, Jenkins, GitLab...):
    FAILED tests become ``<failure>`` nodes, ERROR tests ``<error>``.

    Args:
        result: The suite outcome to serialize.

    Returns:
        The XML document as a string.
    """
    failures = sum(1 for r in result.results if r.status == TestStatus.FAILED)
    errors = sum(1 for r in result.results if r.status == TestStatus.ERROR)
    total = len(result.results)
    timestamp = datetime.now(timezone.utc).isoformat(timespec="seconds")

    xml = ['<?xml version="1.0" encoding="UTF-8"?>']
    xml.append("<testsuites>")
    xml.append(
        f'<testsuite name="{_esc(result.suite_name)}" tests="{total}" '
        f'failures="{failures}" errors="{errors}" skipped="0" '
        f'time="{sum(r.duration_ms for r in result.results) / 1000:.3f}" '
        f'timestamp="{timestamp}">'
    )
    for r in result.results:
        xml.append(
            f'  <testcase classname="modeltest" name="{_esc(r.name)}" '
            f'time="{r.duration_ms / 1000:.3f}">'
        )
        if r.status == TestStatus.FAILED:
            xml.append(f'    <failure message="{_esc(r.detail)}" />')
        elif r.status == TestStatus.ERROR:
            xml.append(f'    <error message="{_esc(r.detail)}" />')
        xml.append("  </testcase>")
    xml.append("</testsuite>")
    xml.append("</testsuites>")
    return "\n".join(xml)