Added permutations and ranges to test defines

This is really more work for the bench runner. With this change defines
can be manipulated at a rather high level at runtime. Which should be
useful for generating benchmarks across various dimensions.

The define grammar in the test_runner is now a bit more powerful,
accepting:

1. A single value: -DN=42
2. A list of values, which get permuted: -DN=1,2,3
3. A range: -DN=range(10)
4. Some combo: -DN=1,2,range(3,0,-1)

This is more complex in the test .toml defines, which can also be C
expressions:

1. A single value: define=42
2. A single expression: define='42*42'
3. A list: define=[1,2,3]
4. A comma separated string: define='1,2,3'
5. A range: define='42*range(10)'
6. This mess: define=[1,2,'3,4,range(2)*range(2)+3']
This commit is contained in:
Christopher Haster
2022-09-11 03:10:08 -05:00
parent bfbe44e70d
commit 03c1a4ee2e
6 changed files with 324 additions and 127 deletions
+65 -15
View File
@@ -62,6 +62,7 @@ class TestCase:
self.defines = set()
self.permutations = []
# defines can be a dict or a list or dicts
suite_defines = config.pop('suite_defines', {})
if not isinstance(suite_defines, list):
suite_defines = [suite_defines]
@@ -69,15 +70,63 @@ class TestCase:
if not isinstance(defines, list):
defines = [defines]
def csplit(v):
# split commas but only outside of parens
parens = 0
i_ = 0
for i in range(len(v)):
if v[i] == ',' and parens == 0:
yield v[i_:i]
i_ = i+1
elif v[i] in '([{':
parens += 1
elif v[i] in '}])':
parens -= 1
if v[i_:].strip():
yield v[i_:]
def parse_define(v):
# a define entry can be a list
if isinstance(v, list):
for v_ in v:
yield from parse_define(v_)
# or a string
elif isinstance(v, str):
# which can be comma-separated values, with optional
# range statements. This matches the runtime define parser in
# the runner itself.
for v_ in csplit(v):
m = re.search(r'\brange\b\s*\('
'(?P<start>[^,\s]*)'
'\s*(?:,\s*(?P<stop>[^,\s]*)'
'\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
v_)
if m:
start = (int(m.group('start'), 0)
if m.group('start') else 0)
stop = (int(m.group('stop'), 0)
if m.group('stop') else None)
step = (int(m.group('step'), 0)
if m.group('step') else 1)
if m.lastindex <= 1:
start, stop = 0, start
for x in range(start, stop, step):
yield from parse_define('%s(%d)%s' % (
v_[:m.start()], x, v_[m.end():]))
else:
yield v_
# or a literal value
else:
yield v
# build possible permutations
for suite_defines_ in suite_defines:
self.defines |= suite_defines_.keys()
for defines_ in defines:
self.defines |= defines_.keys()
self.permutations.extend(map(dict, it.product(*(
[(k, v) for v in (vs if isinstance(vs, list) else [vs])]
for k, vs in sorted(
(suite_defines_ | defines_).items())))))
self.permutations.extend(dict(perm) for perm in it.product(*(
[(k, v) for v in parse_define(vs)]
for k, vs in sorted((suite_defines_ | defines_).items()))))
for k in config.keys():
print('%swarning:%s in %s, found unused key %r' % (
@@ -254,13 +303,12 @@ def compile(test_paths, **args):
f.writeln(4*' '+'return %s;' % v)
f.writeln('}')
f.writeln()
f.writeln('const test_define_t *const '
'__test__%s__%s__defines[] = {'
% (suite.name, case.name))
f.writeln('const test_define_t '
'__test__%s__%s__defines[]['
'TEST_IMPLICIT_DEFINE_COUNT+%d] = {'
% (suite.name, case.name, len(suite.defines)))
for defines in case.permutations:
f.writeln(4*' '+'(const test_define_t['
'TEST_IMPLICIT_DEFINE_COUNT+%d]){' % (
len(suite.defines)))
f.writeln(4*' '+'{')
for k, v in sorted(defines.items()):
f.writeln(8*' '+'[%-24s] = {%s, NULL},' % (
k+'_i', define_cbs[v]))
@@ -321,9 +369,10 @@ def compile(test_paths, **args):
write_case_functions(f, suite, case)
else:
if case.defines:
f.writeln('extern const test_define_t *const '
'__test__%s__%s__defines[];'
% (suite.name, case.name))
f.writeln('extern const test_define_t '
'__test__%s__%s__defines[]['
'TEST_IMPLICIT_DEFINE_COUNT+%d];'
% (suite.name, case.name, len(suite.defines)))
if suite.if_ is not None or case.if_ is not None:
f.writeln('extern bool __test__%s__%s__filter('
'void);'
@@ -368,7 +417,8 @@ def compile(test_paths, **args):
f.writeln(12*' '+'.permutations = %d,'
% len(case.permutations))
if case.defines:
f.writeln(12*' '+'.defines = __test__%s__%s__defines,'
f.writeln(12*' '+'.defines '
'= (const test_define_t*)__test__%s__%s__defines,'
% (suite.name, case.name))
if suite.if_ is not None or case.if_ is not None:
f.writeln(12*' '+'.filter = __test__%s__%s__filter,'
@@ -1088,7 +1138,7 @@ if __name__ == "__main__":
help="Output file.")
# runner + test_ids overlaps test_paths, so we need to do some munging here
args = parser.parse_args()
args = parser.parse_intermixed_args()
args.test_paths = [' '.join(args.runner or [])] + args.test_ids
args.runner = args.runner or [RUNNER_PATH]