xref: /aosp_15_r20/external/pytorch/tools/render_junit.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1*da0073e9SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*da0073e9SAndroid Build Coastguard Worker
3*da0073e9SAndroid Build Coastguard Workerfrom __future__ import annotations
4*da0073e9SAndroid Build Coastguard Worker
5*da0073e9SAndroid Build Coastguard Workerimport argparse
6*da0073e9SAndroid Build Coastguard Workerimport os
7*da0073e9SAndroid Build Coastguard Workerfrom typing import Any
8*da0073e9SAndroid Build Coastguard Worker
9*da0073e9SAndroid Build Coastguard Worker
10*da0073e9SAndroid Build Coastguard Workertry:
11*da0073e9SAndroid Build Coastguard Worker    from junitparser import (  # type: ignore[import]
12*da0073e9SAndroid Build Coastguard Worker        Error,
13*da0073e9SAndroid Build Coastguard Worker        Failure,
14*da0073e9SAndroid Build Coastguard Worker        JUnitXml,
15*da0073e9SAndroid Build Coastguard Worker        TestCase,
16*da0073e9SAndroid Build Coastguard Worker        TestSuite,
17*da0073e9SAndroid Build Coastguard Worker    )
18*da0073e9SAndroid Build Coastguard Workerexcept ImportError as e:
19*da0073e9SAndroid Build Coastguard Worker    raise ImportError(
20*da0073e9SAndroid Build Coastguard Worker        "junitparser not found, please install with 'pip install junitparser'"
21*da0073e9SAndroid Build Coastguard Worker    ) from e
22*da0073e9SAndroid Build Coastguard Worker
23*da0073e9SAndroid Build Coastguard Workertry:
24*da0073e9SAndroid Build Coastguard Worker    import rich
25*da0073e9SAndroid Build Coastguard Workerexcept ImportError:
26*da0073e9SAndroid Build Coastguard Worker    print("rich not found, for color output use 'pip install rich'")
27*da0073e9SAndroid Build Coastguard Worker
28*da0073e9SAndroid Build Coastguard Worker
29*da0073e9SAndroid Build Coastguard Workerdef parse_junit_reports(path_to_reports: str) -> list[TestCase]:  # type: ignore[no-any-unimported]
30*da0073e9SAndroid Build Coastguard Worker    def parse_file(path: str) -> list[TestCase]:  # type: ignore[no-any-unimported]
31*da0073e9SAndroid Build Coastguard Worker        try:
32*da0073e9SAndroid Build Coastguard Worker            return convert_junit_to_testcases(JUnitXml.fromfile(path))
33*da0073e9SAndroid Build Coastguard Worker        except Exception as err:
34*da0073e9SAndroid Build Coastguard Worker            rich.print(
35*da0073e9SAndroid Build Coastguard Worker                f":Warning: [yellow]Warning[/yellow]: Failed to read {path}: {err}"
36*da0073e9SAndroid Build Coastguard Worker            )
37*da0073e9SAndroid Build Coastguard Worker            return []
38*da0073e9SAndroid Build Coastguard Worker
39*da0073e9SAndroid Build Coastguard Worker    if not os.path.exists(path_to_reports):
40*da0073e9SAndroid Build Coastguard Worker        raise FileNotFoundError(f"Path '{path_to_reports}', not found")
41*da0073e9SAndroid Build Coastguard Worker    # Return early if the path provided is just a file
42*da0073e9SAndroid Build Coastguard Worker    if os.path.isfile(path_to_reports):
43*da0073e9SAndroid Build Coastguard Worker        return parse_file(path_to_reports)
44*da0073e9SAndroid Build Coastguard Worker    ret_xml = []
45*da0073e9SAndroid Build Coastguard Worker    if os.path.isdir(path_to_reports):
46*da0073e9SAndroid Build Coastguard Worker        for root, _, files in os.walk(path_to_reports):
47*da0073e9SAndroid Build Coastguard Worker            for fname in [f for f in files if f.endswith("xml")]:
48*da0073e9SAndroid Build Coastguard Worker                ret_xml += parse_file(os.path.join(root, fname))
49*da0073e9SAndroid Build Coastguard Worker    return ret_xml
50*da0073e9SAndroid Build Coastguard Worker
51*da0073e9SAndroid Build Coastguard Worker
52*da0073e9SAndroid Build Coastguard Workerdef convert_junit_to_testcases(xml: JUnitXml | TestSuite) -> list[TestCase]:  # type: ignore[no-any-unimported]
53*da0073e9SAndroid Build Coastguard Worker    testcases = []
54*da0073e9SAndroid Build Coastguard Worker    for item in xml:
55*da0073e9SAndroid Build Coastguard Worker        if isinstance(item, TestSuite):
56*da0073e9SAndroid Build Coastguard Worker            testcases.extend(convert_junit_to_testcases(item))
57*da0073e9SAndroid Build Coastguard Worker        else:
58*da0073e9SAndroid Build Coastguard Worker            testcases.append(item)
59*da0073e9SAndroid Build Coastguard Worker    return testcases
60*da0073e9SAndroid Build Coastguard Worker
61*da0073e9SAndroid Build Coastguard Worker
62*da0073e9SAndroid Build Coastguard Workerdef render_tests(testcases: list[TestCase]) -> None:  # type: ignore[no-any-unimported]
63*da0073e9SAndroid Build Coastguard Worker    num_passed = 0
64*da0073e9SAndroid Build Coastguard Worker    num_skipped = 0
65*da0073e9SAndroid Build Coastguard Worker    num_failed = 0
66*da0073e9SAndroid Build Coastguard Worker    for testcase in testcases:
67*da0073e9SAndroid Build Coastguard Worker        if not testcase.result:
68*da0073e9SAndroid Build Coastguard Worker            num_passed += 1
69*da0073e9SAndroid Build Coastguard Worker            continue
70*da0073e9SAndroid Build Coastguard Worker        for result in testcase.result:
71*da0073e9SAndroid Build Coastguard Worker            if isinstance(result, Error):
72*da0073e9SAndroid Build Coastguard Worker                icon = ":rotating_light: [white on red]ERROR[/white on red]:"
73*da0073e9SAndroid Build Coastguard Worker                num_failed += 1
74*da0073e9SAndroid Build Coastguard Worker            elif isinstance(result, Failure):
75*da0073e9SAndroid Build Coastguard Worker                icon = ":x: [white on red]Failure[/white on red]:"
76*da0073e9SAndroid Build Coastguard Worker                num_failed += 1
77*da0073e9SAndroid Build Coastguard Worker            else:
78*da0073e9SAndroid Build Coastguard Worker                num_skipped += 1
79*da0073e9SAndroid Build Coastguard Worker                continue
80*da0073e9SAndroid Build Coastguard Worker            rich.print(
81*da0073e9SAndroid Build Coastguard Worker                f"{icon} [bold red]{testcase.classname}.{testcase.name}[/bold red]"
82*da0073e9SAndroid Build Coastguard Worker            )
83*da0073e9SAndroid Build Coastguard Worker            print(f"{result.text}")
84*da0073e9SAndroid Build Coastguard Worker    rich.print(f":white_check_mark: {num_passed} [green]Passed[green]")
85*da0073e9SAndroid Build Coastguard Worker    rich.print(f":dash: {num_skipped} [grey]Skipped[grey]")
86*da0073e9SAndroid Build Coastguard Worker    rich.print(f":rotating_light: {num_failed} [grey]Failed[grey]")
87*da0073e9SAndroid Build Coastguard Worker
88*da0073e9SAndroid Build Coastguard Worker
89*da0073e9SAndroid Build Coastguard Workerdef parse_args() -> Any:
90*da0073e9SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
91*da0073e9SAndroid Build Coastguard Worker        description="Render xunit output for failed tests",
92*da0073e9SAndroid Build Coastguard Worker    )
93*da0073e9SAndroid Build Coastguard Worker    parser.add_argument(
94*da0073e9SAndroid Build Coastguard Worker        "report_path",
95*da0073e9SAndroid Build Coastguard Worker        help="Base xunit reports (single file or directory) to compare to",
96*da0073e9SAndroid Build Coastguard Worker    )
97*da0073e9SAndroid Build Coastguard Worker    return parser.parse_args()
98*da0073e9SAndroid Build Coastguard Worker
99*da0073e9SAndroid Build Coastguard Worker
100*da0073e9SAndroid Build Coastguard Workerdef main() -> None:
101*da0073e9SAndroid Build Coastguard Worker    options = parse_args()
102*da0073e9SAndroid Build Coastguard Worker    testcases = parse_junit_reports(options.report_path)
103*da0073e9SAndroid Build Coastguard Worker    render_tests(testcases)
104*da0073e9SAndroid Build Coastguard Worker
105*da0073e9SAndroid Build Coastguard Worker
106*da0073e9SAndroid Build Coastguard Workerif __name__ == "__main__":
107*da0073e9SAndroid Build Coastguard Worker    main()
108