forked from pytorch/pytorch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
render_junit.py
107 lines (89 loc) · 3.22 KB
/
render_junit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
81
82
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
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import os
from typing import Any
try:
from junitparser import ( # type: ignore[import]
Error,
Failure,
JUnitXml,
TestCase,
TestSuite,
)
except ImportError as e:
raise ImportError(
"junitparser not found, please install with 'pip install junitparser'"
) from e
try:
import rich
except ImportError:
print("rich not found, for color output use 'pip install rich'")
def parse_junit_reports(path_to_reports: str) -> list[TestCase]: # type: ignore[no-any-unimported]
def parse_file(path: str) -> list[TestCase]: # type: ignore[no-any-unimported]
try:
return convert_junit_to_testcases(JUnitXml.fromfile(path))
except Exception as err:
rich.print(
f":Warning: [yellow]Warning[/yellow]: Failed to read {path}: {err}"
)
return []
if not os.path.exists(path_to_reports):
raise FileNotFoundError(f"Path '{path_to_reports}', not found")
# Return early if the path provided is just a file
if os.path.isfile(path_to_reports):
return parse_file(path_to_reports)
ret_xml = []
if os.path.isdir(path_to_reports):
for root, _, files in os.walk(path_to_reports):
for fname in [f for f in files if f.endswith("xml")]:
ret_xml += parse_file(os.path.join(root, fname))
return ret_xml
def convert_junit_to_testcases(xml: JUnitXml | TestSuite) -> list[TestCase]: # type: ignore[no-any-unimported]
testcases = []
for item in xml:
if isinstance(item, TestSuite):
testcases.extend(convert_junit_to_testcases(item))
else:
testcases.append(item)
return testcases
def render_tests(testcases: list[TestCase]) -> None: # type: ignore[no-any-unimported]
num_passed = 0
num_skipped = 0
num_failed = 0
for testcase in testcases:
if not testcase.result:
num_passed += 1
continue
for result in testcase.result:
if isinstance(result, Error):
icon = ":rotating_light: [white on red]ERROR[/white on red]:"
num_failed += 1
elif isinstance(result, Failure):
icon = ":x: [white on red]Failure[/white on red]:"
num_failed += 1
else:
num_skipped += 1
continue
rich.print(
f"{icon} [bold red]{testcase.classname}.{testcase.name}[/bold red]"
)
print(f"{result.text}")
rich.print(f":white_check_mark: {num_passed} [green]Passed[green]")
rich.print(f":dash: {num_skipped} [grey]Skipped[grey]")
rich.print(f":rotating_light: {num_failed} [grey]Failed[grey]")
def parse_args() -> Any:
parser = argparse.ArgumentParser(
description="Render xunit output for failed tests",
)
parser.add_argument(
"report_path",
help="Base xunit reports (single file or directory) to compare to",
)
return parser.parse_args()
def main() -> None:
options = parse_args()
testcases = parse_junit_reports(options.report_path)
render_tests(testcases)
if __name__ == "__main__":
main()