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