#!/usr/bin/env python3 from __future__ import print_function import sys import re import argparse 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('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 pad(i): return " " * i if __name__ == "__main__": cmd_args = parse_args() errors = defaultdict(int) skipped = defaultdict(int) expected = defaultdict(int) unexpected = defaultdict(int) passed = defaultdict(int) file = re.compile("^# (?:./)?(\S+\.t)(?:\.exe)?$") timestamp = re.compile("^# (\d+(?:\.\d+)?) ==>.*$") expected_fail = re.compile(r"^not ok.*?#\s*TODO", re.I) unexpected_pass = re.compile(r"^not ok .*?#\s*FIXED", 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 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: filename = match.group(1) elif expected_fail.match(line): expected[filename] += 1 elif unexpected_pass.match(line): unexpected[filename] += 1 elif skip.match(line): skipped[filename] += 1 # It's important these come last, since they're subpatterns of the above elif ok.match(line): passed[filename] += 1 elif not_ok.match(line): errors[filename] += 1 elif comment.match(line): pass elif plan.match(line): pass else: # Uncomment if you want to see malformed things we caught as well... # print(color("Malformed TAP (" + filename + "): " + line, "red")) pass # 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) 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 encoutered any failures, return non-zero code sys.exit(1 if int(error_int) or int(unexpected_int) else 0)