1#!/usr/bin/env python3
2
3import sys
4import re
5import argparse
6from collections import defaultdict
7
8
9def color(text, c):
10    """
11    Add color on the keyword that identifies the state of the test
12    """
13    if sys.stdout.isatty():
14        clear = "\033[0m"
15
16        colors = {
17            "red": "\033[1m\033[91m",
18            "yellow": "\033[1m\033[93m",
19            "green": "\033[1m\033[92m",
20        }
21        return colors[c] + text + clear
22    else:
23        return text
24
25
26def parse_args():
27    parser = argparse.ArgumentParser(description="Report on test results")
28    parser.add_argument('--summary', action="store_true",
29                        help="Display only the totals in each category")
30    parser.add_argument('tapfile', default="all.log", nargs="?",
31                        help="File containing TAP output")
32    return parser.parse_args()
33
34
35def print_category(tests):
36    if not cmd_args.summary:
37        for key in sorted(tests):
38            print("%-32s %4d" % (key, tests[key]))
39
40
41def pad(i):
42    return " " * i
43
44
45if __name__ == "__main__":
46    cmd_args = parse_args()
47
48    errors = defaultdict(int)
49    skipped = defaultdict(int)
50    expected = defaultdict(int)
51    unexpected = defaultdict(int)
52    passed = defaultdict(int)
53
54    file = re.compile("^# (?:./)?(\S+\.t)(?:\.exe)?$")
55    timestamp = re.compile("^# (\d+(?:\.\d+)?) ==>.*$")
56
57    expected_fail = re.compile(r"^not ok.*?#\s*TODO", re.I)
58    unexpected_pass = re.compile(r"^ok .*?#\s*TODO", re.I)
59    skip = re.compile(r"^ok .*?#\s*skip", re.I)
60    ok = re.compile(r"^ok ", re.I)
61    not_ok = re.compile(r"^not ok", re.I)
62    comment = re.compile(r"^#")
63    plan = re.compile(r"^1..\d+\s*(?:#.*)?$")
64
65    start = None
66    stop = None
67
68    with open(cmd_args.tapfile) as fh:
69        for line in fh:
70            if start is None:
71                # First line contains the starting timestamp
72                start = float(timestamp.match(line).group(1))
73                continue
74
75            match = file.match(line)
76            if match:
77                filename = match.group(1)
78
79            elif expected_fail.match(line):
80                expected[filename] += 1
81
82            elif unexpected_pass.match(line):
83                unexpected[filename] += 1
84
85            elif skip.match(line):
86                skipped[filename] += 1
87
88            # It's important these come last, since they're subpatterns of the above
89
90            elif ok.match(line):
91                passed[filename] += 1
92
93            elif not_ok.match(line):
94                errors[filename] += 1
95
96            elif comment.match(line):
97                pass
98
99            elif plan.match(line):
100                pass
101
102            else:
103                # Uncomment if you want to see malformed things we caught as well...
104                # print(color("Malformed TAP (" + filename + "): " + line, "red"))
105                pass
106
107        # Last line contains the ending timestamp
108        stop = float(timestamp.match(line).group(1))
109
110    v = "{0:>5d}"
111    passed_str = "Passed:" + pad(24)
112    passed_int = v.format(sum(passed.values()))
113    error_str = "Failed:" + pad(24)
114    error_int = v.format(sum(errors.values()))
115    unexpected_str = "Unexpected successes:" + pad(10)
116    unexpected_int = v.format(sum(unexpected.values()))
117    skipped_str = "Skipped:" + pad(23)
118    skipped_int = v.format(sum(skipped.values()))
119    expected_str = "Expected failures:" + pad(13)
120    expected_int = v.format(sum(expected.values()))
121    runtime_str = "Runtime:" + pad(20)
122    runtime_int = "{0:>8.2f} seconds".format(stop - start)
123
124    if cmd_args.summary:
125        print(color(passed_str, "green"), passed_int)
126        print(color(error_str, "red"), error_int)
127        print(color(unexpected_str, "red"), unexpected_int)
128        print(color(skipped_str, "yellow"), skipped_int)
129        print(color(expected_str, "yellow"), expected_int)
130        print(runtime_str, runtime_int)
131
132    else:
133        print(color(error_str, "red"))
134        print_category(errors)
135        print()
136        print(color(unexpected_str, "red"))
137        print_category(unexpected)
138        print()
139        print(color(skipped_str, "yellow"))
140        print_category(skipped)
141        print()
142        print(color(expected_str, "yellow"))
143        print_category(expected)
144
145    # If we encoutered any failures, return non-zero code
146    sys.exit(1 if int(error_int) or int(unexpected_int) else 0)
147