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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
#!/usr/bin/env python3
import argparse
import re
import sys
from collections import defaultdict
def color(text, c):
"""
Add color on the keyword that identifies the state of the test
"""
if sys.stdout.isatty():
clear = "\033[0m"
colors = {
"red": "\033[1m\033[91m",
"yellow": "\033[1m\033[93m",
"green": "\033[1m\033[92m",
}
return colors[c] + text + clear
else:
return text
def parse_args():
parser = argparse.ArgumentParser(description="Report on test results")
parser.add_argument('--summary', action="store_true",
help="Display only the totals in each category")
parser.add_argument('--details', action="store_true",
help="Display details in each category")
parser.add_argument('tapfile', default="all.log", nargs="?",
help="File containing TAP output")
return parser.parse_args()
def print_category(tests):
if not cmd_args.summary:
for key in sorted(tests):
print("%-32s %4d" % (key, tests[key]))
def print_category_with_details(tests, details):
if not cmd_args.summary:
for key in sorted(tests):
print("%s (%d):" % (key, tests[key]))
print(details[key])
def pad(i):
return " " * i
if __name__ == "__main__":
cmd_args = parse_args()
errors = defaultdict(int)
details_errors = defaultdict(str)
skipped = defaultdict(int)
details_skipped = defaultdict(str)
expected = defaultdict(int)
details_expected = defaultdict(str)
unexpected = defaultdict(int)
details_unexpected = defaultdict(str)
passed = defaultdict(int)
details_passed = defaultdict(str)
file = re.compile(r"^# (?:./)?(\S+\.t)(?:\.exe)?$")
detail = re.compile("^[^:]+?: (.+)$")
timestamp = re.compile(r"^# (\d+(?:\.\d+)?) ==>.*$")
expected_fail = re.compile(r"^not ok.*?#\s*TODO", re.I)
unexpected_pass = re.compile(r"^ok .*?#\s*TODO", re.I)
skip = re.compile(r"^ok .*?#\s*skip", re.I)
ok = re.compile(r"^ok ", re.I)
not_ok = re.compile(r"^not ok", re.I)
comment = re.compile(r"^#")
plan = re.compile(r"^1..(\d+)\s*(?:#.*)?$")
start = None
stop = None
filename = None
expected_test_count = 0
need_plan = False
with open(cmd_args.tapfile) as fh:
for line in fh:
if start is None:
# First line contains the starting timestamp
start = float(timestamp.match(line).group(1))
continue
match = file.match(line)
if match:
if filename:
if expected_test_count > 0:
print(color("'{}' failed to run all tests.".format(filename), "red"), file=sys.stderr)
errors[filename] += expected_test_count
elif need_plan:
print(color("'{}' failed to run any tests.".format(filename), "red"), file=sys.stderr)
errors[filename] += 1
filename = match.group(1)
need_plan = True
continue
match = plan.match(line)
if match:
expected_test_count = int(match.group(1))
need_plan = False
continue
match = expected_fail.match(line)
if match:
expected[filename] += 1
details_expected[filename] += line
expected_test_count -= 1
continue
match = unexpected_pass.match(line)
if match:
unexpected[filename] += 1
details_unexpected[filename] += line
expected_test_count -= 1
continue
match = skip.match(line)
if match:
skipped[filename] += 1
details_skipped[filename] += line
expected_test_count -= 1
continue
# It's important these come last, since they're sub-patterns of the above
match = ok.match(line)
if match:
passed[filename] += 1
details_passed[filename] += line
expected_test_count -= 1
continue
match = not_ok.match(line)
if match:
errors[filename] += 1
details_errors[filename] += " - " + detail.match(line).group(1) + "\n"
expected_test_count -= 1
continue
match = comment.match(line)
if match:
continue
# Uncomment if you want to see malformed things we caught as well...
# print(color("Malformed TAP (" + filename + "): " + line, "red"))
# Last line contains the ending timestamp
stop = float(timestamp.match(line).group(1))
v = "{0:>5d}"
passed_str = "Passed:" + pad(24)
passed_int = v.format(sum(passed.values()))
error_str = "Failed:" + pad(24)
error_int = v.format(sum(errors.values()))
unexpected_str = "Unexpected successes:" + pad(10)
unexpected_int = v.format(sum(unexpected.values()))
skipped_str = "Skipped:" + pad(23)
skipped_int = v.format(sum(skipped.values()))
expected_str = "Expected failures:" + pad(13)
expected_int = v.format(sum(expected.values()))
runtime_str = "Runtime:" + pad(20)
runtime_int = "{0:>8.2f} seconds".format(stop - start)
if cmd_args.summary:
print(color(passed_str, "green"), passed_int)
print(color(error_str, "red"), error_int)
print(color(unexpected_str, "red"), unexpected_int)
print(color(skipped_str, "yellow"), skipped_int)
print(color(expected_str, "yellow"), expected_int)
print(runtime_str, runtime_int)
elif cmd_args.details:
print(color(error_str, "red"))
print_category_with_details(errors, details_errors)
print()
print(color(unexpected_str, "red"))
print_category_with_details(unexpected, details_unexpected)
print()
print(color(skipped_str, "yellow"))
print_category_with_details(skipped, details_skipped)
print()
print(color(expected_str, "yellow"))
print_category_with_details(expected, details_expected)
else:
print(color(error_str, "red"))
print_category(errors)
print()
print(color(unexpected_str, "red"))
print_category(unexpected)
print()
print(color(skipped_str, "yellow"))
print_category(skipped)
print()
print(color(expected_str, "yellow"))
print_category(expected)
# If we encountered any failures, return non-zero code
sys.exit(1 if int(error_int) or int(unexpected_int) else 0)
|