runall.py (98 lines of code) (raw):

#!/usr/bin/env python3 import sys import os import subprocess import argparse import yaml import time import re if __name__ == '__main__': PYTHON3 = sys.executable parser = argparse.ArgumentParser(description='Command line options.') parser.add_argument('--rootdir', dest='rootdir', type=str, required=True, help="Root directory of Apache Pony Mail") parser.add_argument('--load', dest='load', type=str, nargs='+', help="Load only specific yaml spec files instead of all test specs") parser.add_argument('--ttype', dest='ttype', type=str, nargs='+', help="Run only specified test types (generators, parsing, etc)") parser.add_argument('--gtype', dest='gtype', type=str, nargs='+', help="Run only specified generators (medium, cluster, dkim, full, etc)") parser.add_argument('--yamldir', dest='yamldir', type=str, action='store', help="Load yaml specs from alternate directory") parser.add_argument('--dropin', dest='dropin', type=str, action='store', help="If set to a specific generator name, replaces its unit test results with the current " "output in the yaml tests") parser.add_argument('--nomboxo', dest = 'nomboxo', action='store_true', help = 'Skip Mboxo processing') parser.add_argument('--fof', dest='failonfail', action='store_true', help="Stop running more tests if an error is encountered") parser.add_argument('--skipnodate', dest='skipnodate', action='store_true', help="Skip generator tests with no Date: header") args = parser.parse_args() yamldir = args.yamldir or "yaml" if args.load: spec_files = args.load else: spec_files = [os.path.join(yamldir, x) for x in os.listdir(yamldir) if x.endswith('.yaml')] tests_success = 0 tests_failure = 0 tests_total = 0 sub_success = 0 sub_failure = 0 sub_skipped = 0 now = time.time() failbreak = False for spec_file in spec_files: with open(spec_file, 'r') as f: yml = yaml.safe_load(f) env = os.environ # always pass parent environ for test_type in yml: if args.ttype and test_type not in args.ttype: print("Skipping test type %s due to --ttype flag" % test_type) continue if test_type == 'args': # Environment variable override, e.g. MOCK_GMTIME env_ = yml[test_type].get("env", None) if env_: for key, val in env_.items(): env[key] = val continue tests_total += 1 # Use stderr so appears in correct sequence in logs; flush seems to be necessary for GitHub actions print("Running '%s' tests from %s..." % (test_type, spec_file), file=sys.stderr, flush=True) try: cliargs = [PYTHON3, 'tests/test-%s.py' % test_type, '--rootdir', args.rootdir, '--load', spec_file,] if args.nomboxo: cliargs.append('--nomboxo') if args.gtype and test_type == 'generators': cliargs.append('--generators') cliargs.extend(args.gtype) if args.dropin: cliargs.extend(['--dropin', args.dropin]) if args.skipnodate and test_type == 'generators': cliargs.append('--skipnodate') rv = subprocess.check_output(cliargs, env=env) tests_success += 1 except subprocess.CalledProcessError as e: rv = e.output print("FAIL: %s test from %s failed with code %d" % (test_type, spec_file, e.returncode), file=sys.stderr, flush=True) tests_failure += 1 if args.failonfail: failbreak = True break finally: # Fetch successes and failures from this spec run, add to total m = re.search(r"^\[DONE\] (\d+) tests run, (\d+) failed\.( Skipped (\d+)\.)?", rv.decode('ascii'), re.MULTILINE) if m: sub_success += int(m.group(1)) - int(m.group(2)) sub_failure += int(m.group(2)) if m.group(4) is not None: sub_skipped += int(m.group(4)) if failbreak: break # No need for stderr at end of run print("-------------------------------------") print("Done with %u specification%s in %.2f seconds" % (tests_total, 's' if tests_total != 1 else '', time.time() - now)) print("Specs processed: %4u" % tests_total) print("Total tests run: %4u" % (sub_success+sub_failure)) print("Tests succeeded: %4u" % sub_success) print("Tests failed: %4u" % sub_failure) print("Tests skipped: %4u" % sub_skipped) print("-------------------------------------") if tests_failure: sys.exit(-1)