forked from Imagelibrary/littlefs
Generated v2 prefixes
This commit is contained in:
191
scripts/test.py
191
scripts/test.py
@@ -20,19 +20,50 @@ import pty
|
||||
import errno
|
||||
import signal
|
||||
|
||||
TESTDIR = 'tests'
|
||||
TEST_PATHS = 'tests'
|
||||
RULES = """
|
||||
# add block devices to sources
|
||||
TESTSRC ?= $(SRC) $(wildcard bd/*.c)
|
||||
|
||||
define FLATTEN
|
||||
tests/%$(subst /,.,$(target)): $(target)
|
||||
%(path)s%%$(subst /,.,$(target)): $(target)
|
||||
./scripts/explode_asserts.py $$< -o $$@
|
||||
endef
|
||||
$(foreach target,$(SRC),$(eval $(FLATTEN)))
|
||||
|
||||
-include tests/*.d
|
||||
$(foreach target,$(TESTSRC),$(eval $(FLATTEN)))
|
||||
|
||||
-include %(path)s*.d
|
||||
.SECONDARY:
|
||||
%.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f)
|
||||
|
||||
%(path)s.test: %(path)s.test.o \\
|
||||
$(foreach t,$(subst /,.,$(TESTSRC:.c=.o)),%(path)s.$t)
|
||||
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
|
||||
|
||||
# needed in case builddir is different
|
||||
%(path)s%%.o: %(path)s%%.c
|
||||
$(CC) -c -MMD $(CFLAGS) $< -o $@
|
||||
"""
|
||||
COVERAGE_RULES = """
|
||||
%(path)s.test: override CFLAGS += -fprofile-arcs -ftest-coverage
|
||||
|
||||
# delete lingering coverage
|
||||
%(path)s.test: | %(path)s.info.clean
|
||||
.PHONY: %(path)s.info.clean
|
||||
%(path)s.info.clean:
|
||||
rm -f %(path)s*.gcda
|
||||
|
||||
# accumulate coverage info
|
||||
.PHONY: %(path)s.info
|
||||
%(path)s.info:
|
||||
$(strip $(LCOV) -c \\
|
||||
$(addprefix -d ,$(wildcard %(path)s*.gcda)) \\
|
||||
--rc 'geninfo_adjust_src_path=$(shell pwd)' \\
|
||||
-o $@)
|
||||
$(LCOV) -e $@ $(addprefix /,$(SRC)) -o $@
|
||||
ifdef COVERAGETARGET
|
||||
$(strip $(LCOV) -a $@ \\
|
||||
$(addprefix -a ,$(wildcard $(COVERAGETARGET))) \\
|
||||
-o $(COVERAGETARGET))
|
||||
endif
|
||||
"""
|
||||
GLOBALS = """
|
||||
//////////////// AUTOGENERATED TEST ////////////////
|
||||
@@ -119,6 +150,8 @@ class TestCase:
|
||||
self.if_ = config.get('if', None)
|
||||
self.in_ = config.get('in', None)
|
||||
|
||||
self.result = None
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'permno'):
|
||||
if any(k not in self.case.defines for k in self.defines):
|
||||
@@ -179,7 +212,7 @@ class TestCase:
|
||||
len(self.filter) >= 2 and
|
||||
self.filter[1] != self.permno):
|
||||
return False
|
||||
elif args.get('no_internal', False) and self.in_ is not None:
|
||||
elif args.get('no_internal') and self.in_ is not None:
|
||||
return False
|
||||
elif self.if_ is not None:
|
||||
if_ = self.if_
|
||||
@@ -213,7 +246,7 @@ class TestCase:
|
||||
try:
|
||||
with open(disk, 'w') as f:
|
||||
f.truncate(0)
|
||||
if args.get('verbose', False):
|
||||
if args.get('verbose'):
|
||||
print('truncate --size=0', disk)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
@@ -237,14 +270,14 @@ class TestCase:
|
||||
'-ex', 'r'])
|
||||
ncmd.extend(['--args'] + cmd)
|
||||
|
||||
if args.get('verbose', False):
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in ncmd))
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
sys.exit(sp.call(ncmd))
|
||||
|
||||
# run test case!
|
||||
mpty, spty = pty.openpty()
|
||||
if args.get('verbose', False):
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
||||
os.close(spty)
|
||||
@@ -260,7 +293,7 @@ class TestCase:
|
||||
break
|
||||
raise
|
||||
stdout.append(line)
|
||||
if args.get('verbose', False):
|
||||
if args.get('verbose'):
|
||||
sys.stdout.write(line)
|
||||
# intercept asserts
|
||||
m = re.match(
|
||||
@@ -299,7 +332,7 @@ class ValgrindTestCase(TestCase):
|
||||
return not self.leaky and super().shouldtest(**args)
|
||||
|
||||
def test(self, exec=[], **args):
|
||||
verbose = args.get('verbose', False)
|
||||
verbose = args.get('verbose')
|
||||
uninit = (self.defines.get('LFS2_ERASE_VALUE', None) == -1)
|
||||
exec = [
|
||||
'valgrind',
|
||||
@@ -351,12 +384,17 @@ class TestSuite:
|
||||
self.name = os.path.basename(path)
|
||||
if self.name.endswith('.toml'):
|
||||
self.name = self.name[:-len('.toml')]
|
||||
self.path = path
|
||||
if args.get('build_dir'):
|
||||
self.toml = path
|
||||
self.path = args['build_dir'] + '/' + path
|
||||
else:
|
||||
self.toml = path
|
||||
self.path = path
|
||||
self.classes = classes
|
||||
self.defines = defines.copy()
|
||||
self.filter = filter
|
||||
|
||||
with open(path) as f:
|
||||
with open(self.toml) as f:
|
||||
# load tests
|
||||
config = toml.load(f)
|
||||
|
||||
@@ -467,7 +505,7 @@ class TestSuite:
|
||||
|
||||
def build(self, **args):
|
||||
# build test files
|
||||
tf = open(self.path + '.test.c.t', 'w')
|
||||
tf = open(self.path + '.test.tc', 'w')
|
||||
tf.write(GLOBALS)
|
||||
if self.code is not None:
|
||||
tf.write('#line %d "%s"\n' % (self.code_lineno, self.path))
|
||||
@@ -477,7 +515,7 @@ class TestSuite:
|
||||
for case in self.cases:
|
||||
if case.in_ not in tfs:
|
||||
tfs[case.in_] = open(self.path+'.'+
|
||||
case.in_.replace('/', '.')+'.t', 'w')
|
||||
re.sub('(\.c)?$', '.tc', case.in_.replace('/', '.')), 'w')
|
||||
tfs[case.in_].write('#line 1 "%s"\n' % case.in_)
|
||||
with open(case.in_) as f:
|
||||
for line in f:
|
||||
@@ -516,25 +554,33 @@ class TestSuite:
|
||||
|
||||
# write makefiles
|
||||
with open(self.path + '.mk', 'w') as mk:
|
||||
mk.write(RULES.replace(4*' ', '\t'))
|
||||
mk.write(RULES.replace(4*' ', '\t') % dict(path=self.path))
|
||||
mk.write('\n')
|
||||
|
||||
# add coverage hooks?
|
||||
if args.get('coverage'):
|
||||
mk.write(COVERAGE_RULES.replace(4*' ', '\t') % dict(
|
||||
path=self.path))
|
||||
mk.write('\n')
|
||||
|
||||
# add truely global defines globally
|
||||
for k, v in sorted(self.defines.items()):
|
||||
mk.write('%s: override CFLAGS += -D%s=%r\n' % (
|
||||
self.path+'.test', k, v))
|
||||
mk.write('%s.test: override CFLAGS += -D%s=%r\n'
|
||||
% (self.path, k, v))
|
||||
|
||||
for path in tfs:
|
||||
if path is None:
|
||||
mk.write('%s: %s | %s\n' % (
|
||||
self.path+'.test.c',
|
||||
self.path,
|
||||
self.path+'.test.c.t'))
|
||||
self.toml,
|
||||
self.path+'.test.tc'))
|
||||
else:
|
||||
mk.write('%s: %s %s | %s\n' % (
|
||||
self.path+'.'+path.replace('/', '.'),
|
||||
self.path, path,
|
||||
self.path+'.'+path.replace('/', '.')+'.t'))
|
||||
self.toml,
|
||||
path,
|
||||
self.path+'.'+re.sub('(\.c)?$', '.tc',
|
||||
path.replace('/', '.'))))
|
||||
mk.write('\t./scripts/explode_asserts.py $| -o $@\n')
|
||||
|
||||
self.makefile = self.path + '.mk'
|
||||
@@ -557,7 +603,7 @@ class TestSuite:
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write(FAIL)
|
||||
sys.stdout.flush()
|
||||
if not args.get('keep_going', False):
|
||||
if not args.get('keep_going'):
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write('\n')
|
||||
raise
|
||||
@@ -579,30 +625,30 @@ def main(**args):
|
||||
|
||||
# and what class of TestCase to run
|
||||
classes = []
|
||||
if args.get('normal', False):
|
||||
if args.get('normal'):
|
||||
classes.append(TestCase)
|
||||
if args.get('reentrant', False):
|
||||
if args.get('reentrant'):
|
||||
classes.append(ReentrantTestCase)
|
||||
if args.get('valgrind', False):
|
||||
if args.get('valgrind'):
|
||||
classes.append(ValgrindTestCase)
|
||||
if not classes:
|
||||
classes = [TestCase]
|
||||
|
||||
suites = []
|
||||
for testpath in args['testpaths']:
|
||||
for testpath in args['test_paths']:
|
||||
# optionally specified test case/perm
|
||||
testpath, *filter = testpath.split('#')
|
||||
filter = [int(f) for f in filter]
|
||||
|
||||
# figure out the suite's toml file
|
||||
if os.path.isdir(testpath):
|
||||
testpath = testpath + '/test_*.toml'
|
||||
testpath = testpath + '/*.toml'
|
||||
elif os.path.isfile(testpath):
|
||||
testpath = testpath
|
||||
elif testpath.endswith('.toml'):
|
||||
testpath = TESTDIR + '/' + testpath
|
||||
testpath = TEST_PATHS + '/' + testpath
|
||||
else:
|
||||
testpath = TESTDIR + '/' + testpath + '.toml'
|
||||
testpath = TEST_PATHS + '/' + testpath + '.toml'
|
||||
|
||||
# find tests
|
||||
for path in glob.glob(testpath):
|
||||
@@ -628,7 +674,7 @@ def main(**args):
|
||||
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
|
||||
[target for target in targets])
|
||||
mpty, spty = pty.openpty()
|
||||
if args.get('verbose', False):
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
||||
os.close(spty)
|
||||
@@ -642,14 +688,14 @@ def main(**args):
|
||||
break
|
||||
raise
|
||||
stdout.append(line)
|
||||
if args.get('verbose', False):
|
||||
if args.get('verbose'):
|
||||
sys.stdout.write(line)
|
||||
# intercept warnings
|
||||
m = re.match(
|
||||
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
|
||||
.format('(?:\033\[[\d;]*.| )*', 'warning'),
|
||||
line)
|
||||
if m and not args.get('verbose', False):
|
||||
if m and not args.get('verbose'):
|
||||
try:
|
||||
with open(m.group(1)) as f:
|
||||
lineno = int(m.group(2))
|
||||
@@ -662,27 +708,26 @@ def main(**args):
|
||||
except:
|
||||
pass
|
||||
proc.wait()
|
||||
|
||||
if proc.returncode != 0:
|
||||
if not args.get('verbose', False):
|
||||
if not args.get('verbose'):
|
||||
for line in stdout:
|
||||
sys.stdout.write(line)
|
||||
sys.exit(-3)
|
||||
sys.exit(-1)
|
||||
|
||||
print('built %d test suites, %d test cases, %d permutations' % (
|
||||
len(suites),
|
||||
sum(len(suite.cases) for suite in suites),
|
||||
sum(len(suite.perms) for suite in suites)))
|
||||
|
||||
filtered = 0
|
||||
total = 0
|
||||
for suite in suites:
|
||||
for perm in suite.perms:
|
||||
filtered += perm.shouldtest(**args)
|
||||
if filtered != sum(len(suite.perms) for suite in suites):
|
||||
print('filtered down to %d permutations' % filtered)
|
||||
total += perm.shouldtest(**args)
|
||||
if total != sum(len(suite.perms) for suite in suites):
|
||||
print('filtered down to %d permutations' % total)
|
||||
|
||||
# only requested to build?
|
||||
if args.get('build', False):
|
||||
if args.get('build'):
|
||||
return 0
|
||||
|
||||
print('====== testing ======')
|
||||
@@ -697,15 +742,12 @@ def main(**args):
|
||||
failed = 0
|
||||
for suite in suites:
|
||||
for perm in suite.perms:
|
||||
if not hasattr(perm, 'result'):
|
||||
continue
|
||||
|
||||
if perm.result == PASS:
|
||||
passed += 1
|
||||
else:
|
||||
elif isinstance(perm.result, TestFailure):
|
||||
sys.stdout.write(
|
||||
"\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
|
||||
"{perm} failed with {returncode}\n".format(
|
||||
"{perm} failed\n".format(
|
||||
perm=perm, path=perm.suite.path, lineno=perm.lineno,
|
||||
returncode=perm.result.returncode or 0))
|
||||
if perm.result.stdout:
|
||||
@@ -723,11 +765,33 @@ def main(**args):
|
||||
sys.stdout.write('\n')
|
||||
failed += 1
|
||||
|
||||
if args.get('gdb', False):
|
||||
if args.get('coverage'):
|
||||
# collect coverage info
|
||||
# why -j1? lcov doesn't work in parallel because of gcov limitations
|
||||
cmd = (['make', '-j1', '-f', 'Makefile'] +
|
||||
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
|
||||
(['COVERAGETARGET=%s' % args['coverage']]
|
||||
if isinstance(args['coverage'], str) else []) +
|
||||
[suite.path + '.info' for suite in suites
|
||||
if any(perm.result == PASS for perm in suite.perms)])
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE if not args.get('verbose') else None,
|
||||
stderr=sp.STDOUT if not args.get('verbose') else None,
|
||||
universal_newlines=True)
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
if not args.get('verbose'):
|
||||
for line in proc.stdout:
|
||||
sys.stdout.write(line)
|
||||
sys.exit(-1)
|
||||
|
||||
if args.get('gdb'):
|
||||
failure = None
|
||||
for suite in suites:
|
||||
for perm in suite.perms:
|
||||
if getattr(perm, 'result', PASS) != PASS:
|
||||
if isinstance(perm.result, TestFailure):
|
||||
failure = perm.result
|
||||
if failure is not None:
|
||||
print('======= gdb ======')
|
||||
@@ -735,20 +799,22 @@ def main(**args):
|
||||
failure.case.test(failure=failure, **args)
|
||||
sys.exit(0)
|
||||
|
||||
print('tests passed: %d' % passed)
|
||||
print('tests failed: %d' % failed)
|
||||
print('tests passed %d/%d (%.2f%%)' % (passed, total,
|
||||
100*(passed/total if total else 1.0)))
|
||||
print('tests failed %d/%d (%.2f%%)' % (failed, total,
|
||||
100*(failed/total if total else 1.0)))
|
||||
return 1 if failed > 0 else 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run parameterized tests in various configurations.")
|
||||
parser.add_argument('testpaths', nargs='*', default=[TESTDIR],
|
||||
parser.add_argument('test_paths', nargs='*', default=[TEST_PATHS],
|
||||
help="Description of test(s) to run. By default, this is all tests \
|
||||
found in the \"{0}\" directory. Here, you can specify a different \
|
||||
directory of tests, a specific file, a suite by name, and even a \
|
||||
specific test case by adding brackets. For example \
|
||||
\"test_dirs[0]\" or \"{0}/test_dirs.toml[0]\".".format(TESTDIR))
|
||||
directory of tests, a specific file, a suite by name, and even \
|
||||
specific test cases and permutations. For example \
|
||||
\"test_dirs#1\" or \"{0}/test_dirs.toml#1#1\".".format(TEST_PATHS))
|
||||
parser.add_argument('-D', action='append', default=[],
|
||||
help="Overriding parameter definitions.")
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
@@ -769,10 +835,19 @@ if __name__ == "__main__":
|
||||
help="Run tests normally.")
|
||||
parser.add_argument('-r', '--reentrant', action='store_true',
|
||||
help="Run reentrant tests with simulated power-loss.")
|
||||
parser.add_argument('-V', '--valgrind', action='store_true',
|
||||
parser.add_argument('--valgrind', action='store_true',
|
||||
help="Run non-leaky tests under valgrind to check for memory leaks.")
|
||||
parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '),
|
||||
parser.add_argument('--exec', default=[], type=lambda e: e.split(),
|
||||
help="Run tests with another executable prefixed on the command line.")
|
||||
parser.add_argument('-d', '--disk',
|
||||
parser.add_argument('--disk',
|
||||
help="Specify a file to use for persistent/reentrant tests.")
|
||||
parser.add_argument('--coverage', type=lambda x: x if x else True,
|
||||
nargs='?', const='',
|
||||
help="Collect coverage information during testing. This uses lcov/gcov \
|
||||
to accumulate coverage information into *.info files. May also \
|
||||
a path to a *.info file to accumulate coverage info into.")
|
||||
parser.add_argument('--build-dir',
|
||||
help="Build relative to the specified directory instead of the \
|
||||
current directory.")
|
||||
|
||||
sys.exit(main(**vars(parser.parse_args())))
|
||||
|
||||
Reference in New Issue
Block a user