aboutsummaryrefslogtreecommitdiffstats
path: root/.github/scripts/tests/generate-summary.py
blob: e7dbca46eb2b61f82458da160c8f797a0bb1d1e9 (plain) (blame)
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env python3
import argparse
import os
import glob
import dataclasses
import sys
from typing import Optional, List
from xml.etree import ElementTree as ET
from junit_utils import get_property_value


@dataclasses.dataclass
class SummaryEntry:
    target: str
    log_url: Optional[str]
    reason = ""
    is_failure: bool = False
    is_error: bool = False
    is_muted: bool = False
    is_skipped: bool = False

    @property
    def display_status(self):
        if self.is_error:
            return "Error"
        elif self.is_failure:
            return "Failure"
        elif self.is_muted:
            return "Muted"
        elif self.is_skipped:
            return "Skipped"

        return "?"


def iter_xml_files(folder_or_file):
    if os.path.isfile(folder_or_file):
        files = [folder_or_file]
    else:
        files = glob.glob(os.path.join(folder_or_file, "*.xml"))

    for fn in files:
        tree = ET.parse(fn)
        root = tree.getroot()

        if root.tag == "testsuite":
            suites = [root]
        elif root.tag == "testsuites":
            suites = root.findall("testsuite")
        else:
            raise ValueError(f"Invalid root tag {root.tag}")
        for suite in suites:
            for case in suite.findall("testcase"):
                yield fn, suite, case


def parse_junit(folder_or_file):
    result = []
    for fn, suite, case in iter_xml_files(folder_or_file):
        is_failure = case.find("failure") is not None
        is_error = case.find("error") is not None
        is_muted = get_property_value(case, "mute") is not None
        is_skipped = is_muted is False and case.find("skipped") is not None

        if any([is_failure, is_muted, is_skipped, is_error]):
            cls, method = case.attrib["classname"], case.attrib["name"]
            log_url = get_property_value(case, "url:Log")
            target = f"{ cls }::{ method }" if cls != method else cls

            result.append(
                SummaryEntry(
                    target=target,
                    log_url=log_url,
                    is_skipped=is_skipped,
                    is_muted=is_muted,
                    is_failure=is_failure,
                    is_error=is_error,
                )
            )
    return result


def generate_summary(summary: List[SummaryEntry]):
    log_icon = ":floppy_disk:"
    mute_icon = ":white_check_mark:"

    text = [
        "| Test  | Muted | Log |",
        "| ----: | :---: | --: |",
    ]

    for entry in summary:
        if entry.log_url:
            log_url = f"[{log_icon}]({entry.log_url})"
        else:
            log_url = ""

        mute_target = mute_icon if entry.is_muted else ""

        text.append(f"| {entry.target} | {mute_target} |  {log_url} |")

    return text


def write_summary(title, lines: List[str]):
    summary_fn = os.environ.get("GITHUB_STEP_SUMMARY")
    if summary_fn:
        fp = open(summary_fn, "at")
    else:
        fp = sys.stdout

    if title:
        fp.write(f"{title}\n")
    for line in lines:
        fp.write(f"{line}\n")
    fp.write("\n")

    if summary_fn:
        fp.close()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-t", "--title")
    parser.add_argument("folder_or_file")

    args = parser.parse_args()

    summary = parse_junit(args.folder_or_file)

    if summary:
        text = generate_summary(summary)
        write_summary(args.title, text)


if __name__ == "__main__":
    main()