timewarrior/test/problems
Shaun Ruffell 6852fd2924 test/problems: Report when tests do not run properly
When some of the individual tests fail to run, the `make test` target would
still pass since test/problems would not return an error which could hide the
fact that there are problems in the test.

With this change, if you do not have the python dateutil package, you will now
get output like:

  'test_totals.t' failed to run any tests.
  Passed:                           975
  Failed:                             1
  Unexpected successes:               0
  Skipped:                            0
  Expected failures:                  0
  Runtime:                         4.83 seconds

Or, if you do not have UTF-8 encoding set in your language you will get
something like:

  'track.t' failed to run all tests.
  'stop.t' failed to run all tests.
  'start.t' failed to run all tests.
  'test_totals.t' failed to run all tests.
  Passed:                           941
  Failed:                            50
  Unexpected successes:               0
  Skipped:                            0
  Expected failures:                  0
  Runtime:                         4.55 seconds
2020-01-26 22:38:52 +01:00

175 lines
5.4 KiB
Python
Executable file

#!/usr/bin/env python3
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(r"^# (?:./)?(\S+\.t)(?:\.exe)?$")
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
expected_test_count -= 1
continue
match = unexpected_pass.match(line)
if match:
unexpected[filename] += 1
expected_test_count -= 1
continue
match = skip.match(line)
if match:
skipped[filename] += 1
expected_test_count -= 1
continue
# It's important these come last, since they're subpatterns of the above
match = ok.match(line)
if match:
passed[filename] += 1
expected_test_count -= 1
continue
match = not_ok.match(line)
if match:
errors[filename] += 1
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)
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)