Enabled internal test code at the suite-level

Test suites already had the ability to provide suite-level code via the
"code" attribute, but this was placed in the suite's generated source
file, making it inaccessbile to internal tests.

This change allows suite code to be placed in the same place as internal
tests, via the "in" attribute, though this has some caveats:

1. Suite-level code generally declares helper functions in global scope.
   We don't parse this code or anything, so name collisions between
   helper functions across different test suites is up to the developer
   to resolve.

2. Internal suite-level code has access to internal functions/variables/
   etc, this means we can't place a copy in our suite's generate source
   and expect it to compile. For this reason, internal suite-level code
   is unavailable for non-internal tests in the suite.

   This also means you only get to place internal suite-level code in a
   single source file. Though this is not really an issue since littlefs
   is basically a single file...
This commit is contained in:
Christopher Haster
2023-08-15 02:08:46 -05:00
parent fb2fdb536c
commit 528f104cb4
2 changed files with 138 additions and 86 deletions
+69 -43
View File
@@ -150,6 +150,11 @@ class BenchCase:
return ((self.suite, self.lineno, self.name)
< (other.suite, other.lineno, other.name))
def isin(self, path):
return (self.in_ is not None
and os.path.normpath(self.in_)
== os.path.normpath(path))
class BenchSuite:
# create a BenchSuite object from a toml file
@@ -200,6 +205,7 @@ class BenchSuite:
(l for l in code_linenos
if not case_linenos or l < case_linenos[0][0]),
default=None)
self.in_ = config.pop('in', None)
self.after = config.pop('after', [])
if not isinstance(self.after, list):
@@ -207,7 +213,6 @@ class BenchSuite:
# a couple of these we just forward to all cases
defines = config.pop('defines', {})
in_ = config.pop('in', None)
self.cases = []
for name, case in cases.items():
@@ -217,7 +222,7 @@ class BenchSuite:
if 'lineno' in case else ''),
'suite': self.name,
'suite_defines': defines,
'suite_in': in_,
'suite_in': self.in_,
**case},
args=args))
@@ -248,6 +253,11 @@ class BenchSuite:
# note we override this with a topological sort during compilation
return self.name < other.name
def isin(self, path):
return (self.in_ is not None
and os.path.normpath(self.in_)
== os.path.normpath(path))
def compile(bench_paths, **args):
# find .toml files
@@ -419,16 +429,7 @@ def compile(bench_paths, **args):
f.writeln()
if not args.get('source'):
if suite.code is not None:
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (suite.code_lineno, suite.path))
f.write(suite.code)
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (f.lineno+1, args['output']))
f.writeln()
# write any suite defines
if suite.defines:
for i, define in enumerate(sorted(suite.defines)):
f.writeln('#ifndef %s' % define)
@@ -439,6 +440,17 @@ def compile(bench_paths, **args):
f.writeln('#endif')
f.writeln()
# write any suite code
if suite.code is not None and suite.in_ is None:
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (suite.code_lineno, suite.path))
f.write(suite.code)
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (f.lineno+1, args['output']))
f.writeln()
# create case functions
for case in suite.cases:
if case.in_ is None:
@@ -529,40 +541,54 @@ def compile(bench_paths, **args):
# write any internal benches
for suite in suites:
for case in suite.cases:
if (case.in_ is not None
and os.path.normpath(case.in_)
== os.path.normpath(args['source'])):
# write defines, but note we need to undef any
# new defines since we're in someone else's file
if suite.defines:
for i, define in enumerate(
sorted(suite.defines)):
f.writeln('#ifndef %s' % define)
f.writeln('#define %-24s '
'BENCH_IMPLICIT_DEFINE_COUNT+%d' % (
define+'_i', i))
f.writeln('#define %-24s '
'BENCH_DEFINE(%s)' % (
define, define+'_i'))
f.writeln('#define '
'__BENCH__%s__NEEDS_UNDEF' % (
define))
f.writeln('#endif')
f.writeln()
if (suite.isin(args['source'])
or any(case.isin(args['source'])
for case in suite.cases)):
# write defines, but note we need to undef any
# new defines since we're in someone else's file
if suite.defines:
for i, define in enumerate(
sorted(suite.defines)):
f.writeln('#ifndef %s' % define)
f.writeln('#define %-24s '
'BENCH_IMPLICIT_DEFINE_COUNT+%d' % (
define+'_i', i))
f.writeln('#define %-24s '
'BENCH_DEFINE(%s)' % (
define, define+'_i'))
f.writeln('#define '
'__BENCH__%s__NEEDS_UNDEF' % (
define))
f.writeln('#endif')
f.writeln()
# write any internal suite code
if suite.isin(args['source']):
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (suite.code_lineno, suite.path))
f.write(suite.code)
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (f.lineno+1, args['output']))
f.writeln()
for case in suite.cases:
if case.isin(args['source']):
write_case_functions(f, suite, case)
if suite.defines:
for define in sorted(suite.defines):
f.writeln('#ifdef __BENCH__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef __BENCH__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef %s' % define)
f.writeln('#undef %s' % (define+'_i'))
f.writeln('#endif')
f.writeln()
if (suite.isin(args['source'])
or any(case.isin(args['source'])
for case in suite.cases)):
for define in sorted(suite.defines):
f.writeln('#ifdef __BENCH__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef __BENCH__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef %s' % define)
f.writeln('#undef %s' % (define+'_i'))
f.writeln('#endif')
f.writeln()
# declare our bench suites
#
+69 -43
View File
@@ -152,6 +152,11 @@ class TestCase:
return ((self.suite, self.lineno, self.name)
< (other.suite, other.lineno, other.name))
def isin(self, path):
return (self.in_ is not None
and os.path.normpath(self.in_)
== os.path.normpath(path))
class TestSuite:
# create a TestSuite object from a toml file
@@ -202,6 +207,7 @@ class TestSuite:
(l for l in code_linenos
if not case_linenos or l < case_linenos[0][0]),
default=None)
self.in_ = config.pop('in', None)
self.after = config.pop('after', [])
if not isinstance(self.after, list):
@@ -209,7 +215,6 @@ class TestSuite:
# a couple of these we just forward to all cases
defines = config.pop('defines', {})
in_ = config.pop('in', None)
reentrant = config.pop('reentrant', False)
self.cases = []
@@ -220,7 +225,7 @@ class TestSuite:
if 'lineno' in case else ''),
'suite': self.name,
'suite_defines': defines,
'suite_in': in_,
'suite_in': self.in_,
'suite_reentrant': reentrant,
**case},
args=args))
@@ -253,6 +258,11 @@ class TestSuite:
# note we override this with a topological sort during compilation
return self.name < other.name
def isin(self, path):
return (self.in_ is not None
and os.path.normpath(self.in_)
== os.path.normpath(path))
def compile(test_paths, **args):
# find .toml files
@@ -424,16 +434,7 @@ def compile(test_paths, **args):
f.writeln()
if not args.get('source'):
if suite.code is not None:
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (suite.code_lineno, suite.path))
f.write(suite.code)
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (f.lineno+1, args['output']))
f.writeln()
# write any suite defines
if suite.defines:
for i, define in enumerate(sorted(suite.defines)):
f.writeln('#ifndef %s' % define)
@@ -444,6 +445,17 @@ def compile(test_paths, **args):
f.writeln('#endif')
f.writeln()
# write any suite code
if suite.code is not None and suite.in_ is None:
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (suite.code_lineno, suite.path))
f.write(suite.code)
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (f.lineno+1, args['output']))
f.writeln()
# create case functions
for case in suite.cases:
if case.in_ is None:
@@ -536,40 +548,54 @@ def compile(test_paths, **args):
# write any internal tests
for suite in suites:
for case in suite.cases:
if (case.in_ is not None
and os.path.normpath(case.in_)
== os.path.normpath(args['source'])):
# write defines, but note we need to undef any
# new defines since we're in someone else's file
if suite.defines:
for i, define in enumerate(
sorted(suite.defines)):
f.writeln('#ifndef %s' % define)
f.writeln('#define %-24s '
'TEST_IMPLICIT_DEFINE_COUNT+%d' % (
define+'_i', i))
f.writeln('#define %-24s '
'TEST_DEFINE(%s)' % (
define, define+'_i'))
f.writeln('#define '
'__TEST__%s__NEEDS_UNDEF' % (
define))
f.writeln('#endif')
f.writeln()
if (suite.isin(args['source'])
or any(case.isin(args['source'])
for case in suite.cases)):
# write defines, but note we need to undef any
# new defines since we're in someone else's file
if suite.defines:
for i, define in enumerate(
sorted(suite.defines)):
f.writeln('#ifndef %s' % define)
f.writeln('#define %-24s '
'TEST_IMPLICIT_DEFINE_COUNT+%d' % (
define+'_i', i))
f.writeln('#define %-24s '
'TEST_DEFINE(%s)' % (
define, define+'_i'))
f.writeln('#define '
'__TEST__%s__NEEDS_UNDEF' % (
define))
f.writeln('#endif')
f.writeln()
# write any internal suite code
if suite.isin(args['source']):
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (suite.code_lineno, suite.path))
f.write(suite.code)
if suite.code_lineno is not None:
f.writeln('#line %d "%s"'
% (f.lineno+1, args['output']))
f.writeln()
for case in suite.cases:
if case.isin(args['source']):
write_case_functions(f, suite, case)
if suite.defines:
for define in sorted(suite.defines):
f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef %s' % define)
f.writeln('#undef %s' % (define+'_i'))
f.writeln('#endif')
f.writeln()
if (suite.isin(args['source'])
or any(case.isin(args['source'])
for case in suite.cases)):
for define in sorted(suite.defines):
f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
% define)
f.writeln('#undef %s' % define)
f.writeln('#undef %s' % (define+'_i'))
f.writeln('#endif')
f.writeln()
# declare our test suites
#