timewarrior/test/problems
Thomas Lauf 4a0ab1236d Add --details option
- extracts and prints affected tests

Signed-off-by: Thomas Lauf <thomas.lauf@tngtech.com>
2022-12-29 13:56:21 +01:00

208 lines
6.7 KiB
Python
Executable file

#!/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)