|
@@ -3,22 +3,30 @@
|
|
|
# Script to compile and runs tests.
|
|
# Script to compile and runs tests.
|
|
|
#
|
|
#
|
|
|
|
|
|
|
|
|
|
+import collections as co
|
|
|
|
|
+import errno
|
|
|
import glob
|
|
import glob
|
|
|
import itertools as it
|
|
import itertools as it
|
|
|
import math as m
|
|
import math as m
|
|
|
import os
|
|
import os
|
|
|
|
|
+import pty
|
|
|
import re
|
|
import re
|
|
|
|
|
+import shlex
|
|
|
import shutil
|
|
import shutil
|
|
|
|
|
+import subprocess as sp
|
|
|
|
|
+import threading as th
|
|
|
|
|
+import time
|
|
|
import toml
|
|
import toml
|
|
|
|
|
|
|
|
|
|
+
|
|
|
TEST_PATHS = ['tests_']
|
|
TEST_PATHS = ['tests_']
|
|
|
|
|
+RUNNER_PATH = './runners/test_runner'
|
|
|
|
|
|
|
|
SUITE_PROLOGUE = """
|
|
SUITE_PROLOGUE = """
|
|
|
#include "runners/test_runner.h"
|
|
#include "runners/test_runner.h"
|
|
|
#include <stdio.h>
|
|
#include <stdio.h>
|
|
|
"""
|
|
"""
|
|
|
CASE_PROLOGUE = """
|
|
CASE_PROLOGUE = """
|
|
|
-lfs_t lfs;
|
|
|
|
|
"""
|
|
"""
|
|
|
CASE_EPILOGUE = """
|
|
CASE_EPILOGUE = """
|
|
|
"""
|
|
"""
|
|
@@ -194,13 +202,13 @@ class TestSuite:
|
|
|
|
|
|
|
|
def id(self):
|
|
def id(self):
|
|
|
return self.name
|
|
return self.name
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
|
|
def compile(**args):
|
|
def compile(**args):
|
|
|
# find .toml files
|
|
# find .toml files
|
|
|
paths = []
|
|
paths = []
|
|
|
- for path in args['test_paths']:
|
|
|
|
|
|
|
+ for path in args.get('test_paths', TEST_PATHS):
|
|
|
if os.path.isdir(path):
|
|
if os.path.isdir(path):
|
|
|
path = path + '/*.toml'
|
|
path = path + '/*.toml'
|
|
|
|
|
|
|
@@ -429,26 +437,411 @@ def compile(**args):
|
|
|
f.write('const size_t test_suite_count = %d;\n'
|
|
f.write('const size_t test_suite_count = %d;\n'
|
|
|
% len(suites))
|
|
% len(suites))
|
|
|
|
|
|
|
|
|
|
+def runner(**args):
|
|
|
|
|
+ cmd = args['runner'].copy()
|
|
|
|
|
+ # TODO multiple paths?
|
|
|
|
|
+ if 'test_paths' in args:
|
|
|
|
|
+ cmd.extend(args.get('test_paths'))
|
|
|
|
|
+
|
|
|
|
|
+ if args.get('normal'): cmd.append('-n')
|
|
|
|
|
+ if args.get('reentrant'): cmd.append('-r')
|
|
|
|
|
+ if args.get('valgrind'): cmd.append('-V')
|
|
|
|
|
+ if args.get('geometry'):
|
|
|
|
|
+ cmd.append('-G%s' % args.get('geometry'))
|
|
|
|
|
+ if args.get('define'):
|
|
|
|
|
+ for define in args.get('define'):
|
|
|
|
|
+ cmd.append('-D%s' % define)
|
|
|
|
|
+
|
|
|
|
|
+ return cmd
|
|
|
|
|
+
|
|
|
|
|
+def list_(**args):
|
|
|
|
|
+ cmd = runner(**args)
|
|
|
|
|
+ if args.get('summary'): cmd.append('--summary')
|
|
|
|
|
+ if args.get('list_suites'): cmd.append('--list-suites')
|
|
|
|
|
+ if args.get('list_cases'): cmd.append('--list-cases')
|
|
|
|
|
+ if args.get('list_paths'): cmd.append('--list-paths')
|
|
|
|
|
+ if args.get('list_defines'): cmd.append('--list-defines')
|
|
|
|
|
+ if args.get('list_geometries'): cmd.append('--list-geometries')
|
|
|
|
|
+
|
|
|
|
|
+ if args.get('verbose'):
|
|
|
|
|
+ print(' '.join(shlex.quote(c) for c in cmd))
|
|
|
|
|
+ sys.exit(sp.call(cmd))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def find_cases(runner_, **args):
|
|
|
|
|
+ # first get suite/case/perm counts
|
|
|
|
|
+ cmd = runner_ + ['--list-cases']
|
|
|
|
|
+ if args.get('verbose'):
|
|
|
|
|
+ print(' '.join(shlex.quote(c) for c in cmd))
|
|
|
|
|
+ proc = sp.Popen(cmd,
|
|
|
|
|
+ stdout=sp.PIPE,
|
|
|
|
|
+ stderr=sp.PIPE if not args.get('verbose') else None,
|
|
|
|
|
+ universal_newlines=True,
|
|
|
|
|
+ errors='replace')
|
|
|
|
|
+ expected_suite_perms = co.defaultdict(lambda: 0)
|
|
|
|
|
+ expected_case_perms = co.defaultdict(lambda: 0)
|
|
|
|
|
+ expected_perms = 0
|
|
|
|
|
+ total_perms = 0
|
|
|
|
|
+ pattern = re.compile(
|
|
|
|
|
+ '^(?P<id>(?P<suite>[^#]+)#[^ #]+) +'
|
|
|
|
|
+ '[^ ]+ +[^ ]+ +[^ ]+ +'
|
|
|
|
|
+ '(?P<filtered>[0-9]+)/(?P<perms>[0-9]+)$')
|
|
|
|
|
+ # skip the first line
|
|
|
|
|
+ next(proc.stdout)
|
|
|
|
|
+ for line in proc.stdout:
|
|
|
|
|
+ m = pattern.match(line)
|
|
|
|
|
+ if m:
|
|
|
|
|
+ filtered = int(m.group('filtered'))
|
|
|
|
|
+ expected_suite_perms[m.group('suite')] += filtered
|
|
|
|
|
+ expected_case_perms[m.group('id')] += filtered
|
|
|
|
|
+ expected_perms += filtered
|
|
|
|
|
+ total_perms += int(m.group('perms'))
|
|
|
|
|
+ proc.wait()
|
|
|
|
|
+ if proc.returncode != 0:
|
|
|
|
|
+ if not args.get('verbose'):
|
|
|
|
|
+ for line in proc.stderr:
|
|
|
|
|
+ sys.stdout.write(line)
|
|
|
|
|
+ sys.exit(-1)
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ expected_suite_perms,
|
|
|
|
|
+ expected_case_perms,
|
|
|
|
|
+ expected_perms,
|
|
|
|
|
+ total_perms)
|
|
|
|
|
+
|
|
|
|
|
+class TestFailure(Exception):
|
|
|
|
|
+ def __init__(self, id, returncode, stdout, assert_=None):
|
|
|
|
|
+ self.id = id
|
|
|
|
|
+ self.returncode = returncode
|
|
|
|
|
+ self.stdout = stdout
|
|
|
|
|
+ self.assert_ = assert_
|
|
|
|
|
+
|
|
|
|
|
+def run_step(name, runner_, **args):
|
|
|
|
|
+ # get expected suite/case/perm counts
|
|
|
|
|
+ expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
|
|
|
|
|
+ find_cases(runner_, **args))
|
|
|
|
|
+
|
|
|
|
|
+ # TODO persist/trace
|
|
|
|
|
+ # TODO valgrind/gdb/exec
|
|
|
|
|
+ passed_suite_perms = co.defaultdict(lambda: 0)
|
|
|
|
|
+ passed_case_perms = co.defaultdict(lambda: 0)
|
|
|
|
|
+ passed_perms = 0
|
|
|
|
|
+ failures = []
|
|
|
|
|
+
|
|
|
|
|
+ pattern = re.compile('^(?:'
|
|
|
|
|
+ '(?P<op>running|finished|skipped) '
|
|
|
|
|
+ '(?P<id>(?P<case>(?P<suite>[^#]+)#[^\s#]+)[^\s]*)'
|
|
|
|
|
+ '|' '(?P<path>[^:]+):(?P<lineno>[0-9]+):(?P<op_>assert):'
|
|
|
|
|
+ ' *(?P<message>.*)' ')$')
|
|
|
|
|
+ locals = th.local()
|
|
|
|
|
+ # TODO use process group instead of this set?
|
|
|
|
|
+ children = set()
|
|
|
|
|
+
|
|
|
|
|
+ def run_runner(runner_):
|
|
|
|
|
+ nonlocal passed_suite_perms
|
|
|
|
|
+ nonlocal passed_case_perms
|
|
|
|
|
+ nonlocal passed_perms
|
|
|
|
|
+ nonlocal locals
|
|
|
|
|
+
|
|
|
|
|
+ # run the tests!
|
|
|
|
|
+ cmd = runner_
|
|
|
|
|
+ if args.get('verbose'):
|
|
|
|
|
+ print(' '.join(shlex.quote(c) for c in cmd))
|
|
|
|
|
+ mpty, spty = pty.openpty()
|
|
|
|
|
+ proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
|
|
|
|
+ os.close(spty)
|
|
|
|
|
+ mpty = os.fdopen(mpty, 'r', 1)
|
|
|
|
|
+ children.add(proc)
|
|
|
|
|
+
|
|
|
|
|
+ last_id = None
|
|
|
|
|
+ last_stdout = []
|
|
|
|
|
+ last_assert = None
|
|
|
|
|
+ try:
|
|
|
|
|
+ while True:
|
|
|
|
|
+ # parse a line for state changes
|
|
|
|
|
+ try:
|
|
|
|
|
+ line = mpty.readline()
|
|
|
|
|
+ except OSError as e:
|
|
|
|
|
+ if e.errno == errno.EIO:
|
|
|
|
|
+ break
|
|
|
|
|
+ raise
|
|
|
|
|
+ if not line:
|
|
|
|
|
+ break
|
|
|
|
|
+ last_stdout.append(line)
|
|
|
|
|
+ if args.get('verbose'):
|
|
|
|
|
+ sys.stdout.write(line)
|
|
|
|
|
+
|
|
|
|
|
+ m = pattern.match(line)
|
|
|
|
|
+ if m:
|
|
|
|
|
+ op = m.group('op') or m.group('op_')
|
|
|
|
|
+ if op == 'running':
|
|
|
|
|
+ locals.seen_perms += 1
|
|
|
|
|
+ last_id = m.group('id')
|
|
|
|
|
+ last_stdout = []
|
|
|
|
|
+ last_assert = None
|
|
|
|
|
+ elif op == 'finished':
|
|
|
|
|
+ passed_suite_perms[m.group('suite')] += 1
|
|
|
|
|
+ passed_case_perms[m.group('case')] += 1
|
|
|
|
|
+ passed_perms += 1
|
|
|
|
|
+ elif op == 'skipped':
|
|
|
|
|
+ locals.seen_perms += 1
|
|
|
|
|
+ elif op == 'assert':
|
|
|
|
|
+ last_assert = (
|
|
|
|
|
+ m.group('path'),
|
|
|
|
|
+ int(m.group('lineno')),
|
|
|
|
|
+ m.group('message'))
|
|
|
|
|
+ # TODO why is kill _so_ much faster than terminate?
|
|
|
|
|
+ proc.kill()
|
|
|
|
|
+ except KeyboardInterrupt:
|
|
|
|
|
+ raise TestFailure(last_id, 1, last_stdout)
|
|
|
|
|
+ finally:
|
|
|
|
|
+ children.remove(proc)
|
|
|
|
|
+ mpty.close()
|
|
|
|
|
+
|
|
|
|
|
+ proc.wait()
|
|
|
|
|
+ if proc.returncode != 0:
|
|
|
|
|
+ raise TestFailure(
|
|
|
|
|
+ last_id,
|
|
|
|
|
+ proc.returncode,
|
|
|
|
|
+ last_stdout,
|
|
|
|
|
+ last_assert)
|
|
|
|
|
+
|
|
|
|
|
+ def run_job(runner, skip=None, every=None):
|
|
|
|
|
+ nonlocal failures
|
|
|
|
|
+ nonlocal locals
|
|
|
|
|
+
|
|
|
|
|
+ while (skip or 0) < total_perms:
|
|
|
|
|
+ runner_ = runner.copy()
|
|
|
|
|
+ if skip is not None:
|
|
|
|
|
+ runner_.append('--skip=%d' % skip)
|
|
|
|
|
+ if every is not None:
|
|
|
|
|
+ runner_.append('--every=%d' % every)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # run the tests
|
|
|
|
|
+ locals.seen_perms = 0
|
|
|
|
|
+ run_runner(runner_)
|
|
|
|
|
+
|
|
|
|
|
+ except TestFailure as failure:
|
|
|
|
|
+ # race condition for multiple failures?
|
|
|
|
|
+ if failures and not args.get('keep_going'):
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ failures.append(failure)
|
|
|
|
|
+
|
|
|
|
|
+ if args.get('keep_going'):
|
|
|
|
|
+ # resume after failed test
|
|
|
|
|
+ skip = (skip or 0) + locals.seen_perms*(every or 1)
|
|
|
|
|
+ continue
|
|
|
|
|
+ else:
|
|
|
|
|
+ # stop other tests
|
|
|
|
|
+ for child in children:
|
|
|
|
|
+ # TODO why is kill _so_ much faster than terminate?
|
|
|
|
|
+ child.kill()
|
|
|
|
|
+
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ # parallel jobs?
|
|
|
|
|
+ runners = []
|
|
|
|
|
+ if 'jobs' in args:
|
|
|
|
|
+ for job in range(args['jobs']):
|
|
|
|
|
+ runners.append(th.Thread(
|
|
|
|
|
+ target=run_job, args=(runner_, job, args['jobs'])))
|
|
|
|
|
+ else:
|
|
|
|
|
+ runners.append(th.Thread(
|
|
|
|
|
+ target=run_job, args=(runner_, None, None)))
|
|
|
|
|
+
|
|
|
|
|
+ for r in runners:
|
|
|
|
|
+ r.start()
|
|
|
|
|
+
|
|
|
|
|
+ needs_newline = False
|
|
|
|
|
+ try:
|
|
|
|
|
+ while any(r.is_alive() for r in runners):
|
|
|
|
|
+ time.sleep(0.01)
|
|
|
|
|
+
|
|
|
|
|
+ if not args.get('verbose'):
|
|
|
|
|
+ sys.stdout.write('\r\x1b[K'
|
|
|
|
|
+ 'running \x1b[%dm%s\x1b[m: '
|
|
|
|
|
+ '%d/%d suites, %d/%d cases, %d/%d perms%s '
|
|
|
|
|
+ % (32 if not failures else 31,
|
|
|
|
|
+ name,
|
|
|
|
|
+ sum(passed_suite_perms[k] == v
|
|
|
|
|
+ for k, v in expected_suite_perms.items()),
|
|
|
|
|
+ len(expected_suite_perms),
|
|
|
|
|
+ sum(passed_case_perms[k] == v
|
|
|
|
|
+ for k, v in expected_case_perms.items()),
|
|
|
|
|
+ len(expected_case_perms),
|
|
|
|
|
+ passed_perms,
|
|
|
|
|
+ expected_perms,
|
|
|
|
|
+ ', \x1b[31m%d/%d failures\x1b[m'
|
|
|
|
|
+ % (len(failures), expected_perms)
|
|
|
|
|
+ if failures else ''))
|
|
|
|
|
+ sys.stdout.flush()
|
|
|
|
|
+ needs_newline = True
|
|
|
|
|
+ finally:
|
|
|
|
|
+ if needs_newline:
|
|
|
|
|
+ print()
|
|
|
|
|
+
|
|
|
|
|
+ for r in runners:
|
|
|
|
|
+ r.join()
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ expected_perms,
|
|
|
|
|
+ passed_perms,
|
|
|
|
|
+ failures)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def run(**args):
|
|
def run(**args):
|
|
|
- pass
|
|
|
|
|
|
|
+ start = time.time()
|
|
|
|
|
+
|
|
|
|
|
+ runner_ = runner(**args)
|
|
|
|
|
+ print('using runner `%s`'
|
|
|
|
|
+ % ' '.join(shlex.quote(c) for c in runner_))
|
|
|
|
|
+ expected_suite_perms, expected_case_perms, expected_perms, total_perms = (
|
|
|
|
|
+ find_cases(runner_, **args))
|
|
|
|
|
+ print('found %d suites, %d cases, %d/%d permutations'
|
|
|
|
|
+ % (len(expected_suite_perms),
|
|
|
|
|
+ len(expected_case_perms),
|
|
|
|
|
+ expected_perms,
|
|
|
|
|
+ total_perms))
|
|
|
|
|
+ print()
|
|
|
|
|
+
|
|
|
|
|
+ expected = 0
|
|
|
|
|
+ passed = 0
|
|
|
|
|
+ failures = []
|
|
|
|
|
+ if args.get('by_suites'):
|
|
|
|
|
+ for type in ['normal', 'reentrant', 'valgrind']:
|
|
|
|
|
+ for suite in expected_suite_perms.keys():
|
|
|
|
|
+ expected_, passed_, failures_ = run_step(
|
|
|
|
|
+ '%s %s' % (type, suite),
|
|
|
|
|
+ runner_ + ['--%s' % type, suite],
|
|
|
|
|
+ **args)
|
|
|
|
|
+ expected += expected_
|
|
|
|
|
+ passed += passed_
|
|
|
|
|
+ failures.extend(failures_)
|
|
|
|
|
+ if failures and not args.get('keep_going'):
|
|
|
|
|
+ break
|
|
|
|
|
+ if failures and not args.get('keep_going'):
|
|
|
|
|
+ break
|
|
|
|
|
+ elif args.get('by_cases'):
|
|
|
|
|
+ for type in ['normal', 'reentrant', 'valgrind']:
|
|
|
|
|
+ for case in expected_case_perms.keys():
|
|
|
|
|
+ expected_, passed_, failures_ = run_step(
|
|
|
|
|
+ '%s %s' % (type, case),
|
|
|
|
|
+ runner_ + ['--%s' % type, case],
|
|
|
|
|
+ **args)
|
|
|
|
|
+ expected += expected_
|
|
|
|
|
+ passed += passed_
|
|
|
|
|
+ failures.extend(failures_)
|
|
|
|
|
+ if failures and not args.get('keep_going'):
|
|
|
|
|
+ break
|
|
|
|
|
+ if failures and not args.get('keep_going'):
|
|
|
|
|
+ break
|
|
|
|
|
+ else:
|
|
|
|
|
+ for type in ['normal', 'reentrant', 'valgrind']:
|
|
|
|
|
+ expected_, passed_, failures_ = run_step(
|
|
|
|
|
+ '%s tests' % type,
|
|
|
|
|
+ runner_ + ['--%s' % type],
|
|
|
|
|
+ **args)
|
|
|
|
|
+ expected += expected_
|
|
|
|
|
+ passed += passed_
|
|
|
|
|
+ failures.extend(failures_)
|
|
|
|
|
+ if failures and not args.get('keep_going'):
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ # show summary
|
|
|
|
|
+ print()
|
|
|
|
|
+ print('\x1b[%dmdone\x1b[m: %d/%d passed, %d/%d failed, in %.2fs'
|
|
|
|
|
+ % (32 if not failures else 31,
|
|
|
|
|
+ passed, expected, len(failures), expected,
|
|
|
|
|
+ time.time()-start))
|
|
|
|
|
+ print()
|
|
|
|
|
+
|
|
|
|
|
+ # print each failure
|
|
|
|
|
+ # TODO get line, defines, path
|
|
|
|
|
+ for failure in failures:
|
|
|
|
|
+# print('\x1b[01m%s:%d:\x1b[01;31mfailure:\x1b[m %s failed'
|
|
|
|
|
+# # TODO this should be the suite path and lineno
|
|
|
|
|
+# % (failure.assert
|
|
|
|
|
+
|
|
|
|
|
+ if failure.assert_ is not None:
|
|
|
|
|
+ path, lineno, message = failure.assert_
|
|
|
|
|
+ print('\x1b[01m%s:%d:\x1b[01;31massert:\x1b[m %s'
|
|
|
|
|
+ % (path, lineno, message))
|
|
|
|
|
+ with open(path) as f:
|
|
|
|
|
+ line = next(it.islice(f, lineno-1, None)).strip('\n')
|
|
|
|
|
+ print(line)
|
|
|
|
|
+ print()
|
|
|
|
|
+
|
|
|
|
|
+ return 1 if failures else 0
|
|
|
|
|
+
|
|
|
|
|
|
|
|
def main(**args):
|
|
def main(**args):
|
|
|
if args.get('compile'):
|
|
if args.get('compile'):
|
|
|
compile(**args)
|
|
compile(**args)
|
|
|
|
|
+ elif (args.get('summary')
|
|
|
|
|
+ or args.get('list_suites')
|
|
|
|
|
+ or args.get('list_cases')
|
|
|
|
|
+ or args.get('list_paths')
|
|
|
|
|
+ or args.get('list_defines')
|
|
|
|
|
+ or args.get('list_geometries')):
|
|
|
|
|
+ list_(**args)
|
|
|
else:
|
|
else:
|
|
|
run(**args)
|
|
run(**args)
|
|
|
|
|
|
|
|
|
|
+
|
|
|
if __name__ == "__main__":
|
|
if __name__ == "__main__":
|
|
|
import argparse
|
|
import argparse
|
|
|
import sys
|
|
import sys
|
|
|
parser = argparse.ArgumentParser(
|
|
parser = argparse.ArgumentParser(
|
|
|
description="Build and run tests.")
|
|
description="Build and run tests.")
|
|
|
# TODO document test case/perm specifier
|
|
# TODO document test case/perm specifier
|
|
|
- parser.add_argument('test_paths', nargs='*', default=TEST_PATHS,
|
|
|
|
|
- help="Description of test(s) to run. May be a directory, a path, or \
|
|
|
|
|
- test identifier. Defaults to all tests in %r." % TEST_PATHS)
|
|
|
|
|
|
|
+ parser.add_argument('test_paths', nargs='*',
|
|
|
|
|
+ help="Description of testis to run. May be a directory, path, or \
|
|
|
|
|
+ test identifier. Defaults to %r." % TEST_PATHS)
|
|
|
|
|
+ parser.add_argument('-v', '--verbose', action='store_true',
|
|
|
|
|
+ help="Output commands that run behind the scenes.")
|
|
|
# test flags
|
|
# test flags
|
|
|
test_parser = parser.add_argument_group('test options')
|
|
test_parser = parser.add_argument_group('test options')
|
|
|
|
|
+ test_parser.add_argument('-Y', '--summary', action='store_true',
|
|
|
|
|
+ help="Show quick summary.")
|
|
|
|
|
+ test_parser.add_argument('-l', '--list-suites', action='store_true',
|
|
|
|
|
+ help="List test suites.")
|
|
|
|
|
+ test_parser.add_argument('-L', '--list-cases', action='store_true',
|
|
|
|
|
+ help="List test cases.")
|
|
|
|
|
+ test_parser.add_argument('--list-paths', action='store_true',
|
|
|
|
|
+ help="List the path for each test case.")
|
|
|
|
|
+ test_parser.add_argument('--list-defines', action='store_true',
|
|
|
|
|
+ help="List the defines for each test permutation.")
|
|
|
|
|
+ test_parser.add_argument('--list-geometries', action='store_true',
|
|
|
|
|
+ help="List the disk geometries used for testing.")
|
|
|
|
|
+ test_parser.add_argument('-D', '--define', action='append',
|
|
|
|
|
+ help="Override a test define.")
|
|
|
|
|
+ test_parser.add_argument('-G', '--geometry',
|
|
|
|
|
+ help="Filter by geometry.")
|
|
|
|
|
+ test_parser.add_argument('-n', '--normal', action='store_true',
|
|
|
|
|
+ help="Filter for normal tests. Can be combined.")
|
|
|
|
|
+ test_parser.add_argument('-r', '--reentrant', action='store_true',
|
|
|
|
|
+ help="Filter for reentrant tests. Can be combined.")
|
|
|
|
|
+ test_parser.add_argument('-V', '--valgrind', action='store_true',
|
|
|
|
|
+ help="Filter for Valgrind tests. Can be combined.")
|
|
|
|
|
+ test_parser.add_argument('-p', '--persist',
|
|
|
|
|
+ help="Persist the disk to this file.")
|
|
|
|
|
+ test_parser.add_argument('-t', '--trace',
|
|
|
|
|
+ help="Redirect trace output to this file.")
|
|
|
|
|
+ test_parser.add_argument('--runner', default=[RUNNER_PATH],
|
|
|
|
|
+ type=lambda x: x.split(),
|
|
|
|
|
+ help="Path to runner, defaults to %r" % RUNNER_PATH)
|
|
|
|
|
+ test_parser.add_argument('-j', '--jobs', nargs='?', type=int,
|
|
|
|
|
+ const=len(os.sched_getaffinity(0)),
|
|
|
|
|
+ help="Number of parallel runners to run.")
|
|
|
|
|
+ test_parser.add_argument('-k', '--keep-going', action='store_true',
|
|
|
|
|
+ help="Don't stop on first error.")
|
|
|
|
|
+ test_parser.add_argument('-b', '--by-suites', action='store_true',
|
|
|
|
|
+ help="Step through tests by suite.")
|
|
|
|
|
+ test_parser.add_argument('-B', '--by-cases', action='store_true',
|
|
|
|
|
+ help="Step through tests by case.")
|
|
|
# compilation flags
|
|
# compilation flags
|
|
|
comp_parser = parser.add_argument_group('compilation options')
|
|
comp_parser = parser.add_argument_group('compilation options')
|
|
|
comp_parser.add_argument('-c', '--compile', action='store_true',
|
|
comp_parser.add_argument('-c', '--compile', action='store_true',
|