scripts: test.py/bench.py: Added explicit internal flag

This adds an explicit:

  internal = true

As an alternative to:

  in = 'lfs3.c'

For marking tests/benches as internal without actually placing them in a
specific source file.

The internal flag and --no-internal have proven suprisingly useful for
running a subset of tests when refactoring, as internal tests break much
more frequently than the high-level API. However, placing all the
internal tests in lfs3.c _has_ put a big strain on compilation/link
times.

`internal = true` now lets you mark tests/benches as internal, without
the extra compile/link overhead. You don't get access to any internal
things, but the flag can still be useful for filtering.

---

The original motivation for this was in the test_fwrite_clip_* tests,
but they ended up using lfs3_bptr_size to check leaf sizes, so oh well.
At least it's a good flag to have around. (In theory these could be made
"fake internal" with a manual bitmask, but it doesn't seem worth the
potential maintenance headache for saving a bit of link time. Though
maybe in the future priorities will change.)

Also cleaned up the handling of None in test/bench config a bit. Now
None should be equivalent to missing config fields, at the cost of more
noise in the Python code. None vs missing always feels unusually clunky
in Python.
This commit is contained in:
Christopher Haster
2026-02-05 00:12:10 -06:00
parent 26e8bc2e9e
commit 9974656c5c
2 changed files with 114 additions and 57 deletions
+52 -25
View File
@@ -82,28 +82,44 @@ class BenchCase:
self.path = config.pop('path')
self.suite = config.pop('suite')
self.lineno = config.pop('lineno', None)
self.if_ = config.pop('if', [])
if not isinstance(self.if_, list):
self.if_ = config.pop('if', None)
if self.if_ is None:
self.if_ = []
elif not isinstance(self.if_, list):
self.if_ = [self.if_]
self.ifdef = config.pop('ifdef', [])
if not isinstance(self.ifdef, list):
self.ifdef = config.pop('ifdef', None)
if self.ifdef is None:
self.ifdef = []
elif not isinstance(self.ifdef, list):
self.ifdef = [self.ifdef]
self.ifndef = config.pop('ifndef', [])
if not isinstance(self.ifndef, list):
self.ifndef = config.pop('ifndef', None)
if self.ifndef is None:
self.ifndef = []
elif not isinstance(self.ifndef, list):
self.ifndef = [self.ifndef]
self.code = config.pop('code')
self.code_lineno = config.pop('code_lineno', None)
self.in_ = config.pop('in',
config.pop('suite_in', None))
self.internal = bool(self.in_)
self.internal = config.pop('internal',
config.pop('suite_internal', None))
if self.internal is None:
self.internal = False
# in implies internal
self.internal |= bool(self.in_)
# defines can be a dict or a list or dicts
suite_defines = config.pop('suite_defines', {})
if not isinstance(suite_defines, list):
suite_defines = config.pop('suite_defines', None)
if suite_defines is None:
suite_defines = [{}]
elif not isinstance(suite_defines, list):
suite_defines = [suite_defines]
defines = config.pop('defines', {})
if not isinstance(defines, list):
defines = config.pop('defines', None)
if defines is None:
defines = [{}]
elif not isinstance(defines, list):
defines = [defines]
def csplit(v):
@@ -225,7 +241,9 @@ class BenchSuite:
# sort in case toml parsing did not retain order
case_linenos.sort()
cases = config.pop('cases', {})
cases = config.pop('cases', None)
if cases is None:
cases = {}
for (lineno, name), (nlineno, _) in it.zip_longest(
case_linenos, case_linenos[1:],
fillvalue=(float('inf'), None)):
@@ -236,15 +254,21 @@ class BenchSuite:
cases[name]['lineno'] = lineno
cases[name]['code_lineno'] = code_lineno
self.if_ = config.pop('if', [])
if not isinstance(self.if_, list):
self.if_ = config.pop('if', None)
if self.if_ is None:
self.if_ = []
elif not isinstance(self.if_, list):
self.if_ = [self.if_]
self.ifdef = config.pop('ifdef', [])
if not isinstance(self.ifdef, list):
self.ifdef = config.pop('ifdef', None)
if self.ifdef is None:
self.ifdef = []
elif not isinstance(self.ifdef, list):
self.ifdef = [self.ifdef]
self.ifndef = config.pop('ifndef', [])
if not isinstance(self.ifndef, list):
self.ifndef = config.pop('ifndef', None)
if self.ifndef is None:
self.ifndef = []
elif not isinstance(self.ifndef, list):
self.ifndef = [self.ifndef]
self.code = config.pop('code', None)
@@ -254,28 +278,31 @@ class BenchSuite:
default=None)
self.in_ = config.pop('in', None)
self.after = config.pop('after', [])
if not isinstance(self.after, list):
self.after = config.pop('after', None)
if self.after is None:
self.after = []
elif not isinstance(self.after, list):
self.after = [self.after]
# a couple of these we just forward to all cases
defines = config.pop('defines', {})
defines = config.pop('defines', None)
internal = config.pop('internal', None)
self.cases = []
for name, config_ in cases.items():
case = BenchCase(
config={
'name': name,
{ 'name': name,
'path': path + (':%d' % config_['lineno']
if 'lineno' in config_ else ''),
'suite': self.name,
'suite_defines': defines,
'suite_in': self.in_,
'suite_internal': internal,
**config_},
args=args)
args)
# skipping internal tests?
if args.get('no_internal') and case.in_ is not None:
if args.get('no_internal') and case.internal:
continue
self.cases.append(case)
+62 -32
View File
@@ -83,33 +83,52 @@ class TestCase:
self.path = config.pop('path')
self.suite = config.pop('suite')
self.lineno = config.pop('lineno', None)
self.if_ = config.pop('if', [])
if not isinstance(self.if_, list):
self.if_ = config.pop('if', None)
if self.if_ is None:
self.if_ = []
elif not isinstance(self.if_, list):
self.if_ = [self.if_]
self.ifdef = config.pop('ifdef', [])
if not isinstance(self.ifdef, list):
self.ifdef = config.pop('ifdef', None)
if self.ifdef is None:
self.ifdef = []
elif not isinstance(self.ifdef, list):
self.ifdef = [self.ifdef]
self.ifndef = config.pop('ifndef', [])
if not isinstance(self.ifndef, list):
self.ifndef = config.pop('ifndef', None)
if self.ifndef is None:
self.ifndef = []
elif not isinstance(self.ifndef, list):
self.ifndef = [self.ifndef]
self.code = config.pop('code')
self.code_lineno = config.pop('code_lineno', None)
self.in_ = config.pop('in',
config.pop('suite_in', None))
self.fuzz_ = config.pop('fuzz',
config.pop('suite_fuzz', None))
self.internal = bool(self.in_)
self.internal = config.pop('internal',
config.pop('suite_internal', None))
if self.internal is None:
self.internal = False
self.reentrant = config.pop('reentrant',
config.pop('suite_reentrant', False))
self.fuzz = bool(self.fuzz_)
config.pop('suite_reentrant', None))
if self.reentrant is None:
self.reentrant = False
self.fuzz = config.pop('fuzz',
config.pop('suite_fuzz', None))
if self.fuzz is None:
self.fuzz = False
# in implies internal
self.internal |= bool(self.in_)
# defines can be a dict or a list or dicts
suite_defines = config.pop('suite_defines', {})
if not isinstance(suite_defines, list):
suite_defines = config.pop('suite_defines', None)
if suite_defines is None:
suite_defines = [{}]
elif not isinstance(suite_defines, list):
suite_defines = [suite_defines]
defines = config.pop('defines', {})
if not isinstance(defines, list):
defines = config.pop('defines', None)
if defines is None:
defines = [{}]
elif not isinstance(defines, list):
defines = [defines]
def csplit(v):
@@ -231,7 +250,9 @@ class TestSuite:
# sort in case toml parsing did not retain order
case_linenos.sort()
cases = config.pop('cases', {})
cases = config.pop('cases', None)
if cases is None:
cases = {}
for (lineno, name), (nlineno, _) in it.zip_longest(
case_linenos, case_linenos[1:],
fillvalue=(float('inf'), None)):
@@ -242,15 +263,21 @@ class TestSuite:
cases[name]['lineno'] = lineno
cases[name]['code_lineno'] = code_lineno
self.if_ = config.pop('if', [])
if not isinstance(self.if_, list):
self.if_ = config.pop('if', None)
if self.if_ is None:
self.if_ = []
elif not isinstance(self.if_, list):
self.if_ = [self.if_]
self.ifdef = config.pop('ifdef', [])
if not isinstance(self.ifdef, list):
self.ifdef = config.pop('ifdef', None)
if self.ifdef is None:
self.ifdef = []
elif not isinstance(self.ifdef, list):
self.ifdef = [self.ifdef]
self.ifndef = config.pop('ifndef', [])
if not isinstance(self.ifndef, list):
self.ifndef = config.pop('ifndef', None)
if self.ifndef is None:
self.ifndef = []
elif not isinstance(self.ifndef, list):
self.ifndef = [self.ifndef]
self.code = config.pop('code', None)
@@ -259,33 +286,36 @@ class TestSuite:
if not case_linenos or l < case_linenos[0][0]),
default=None)
self.in_ = config.pop('in', None)
self.fuzz_ = config.pop('fuzz', None)
self.after = config.pop('after', [])
if not isinstance(self.after, list):
self.after = config.pop('after', None)
if self.after is None:
self.after = []
elif not isinstance(self.after, list):
self.after = [self.after]
# a couple of these we just forward to all cases
defines = config.pop('defines', {})
reentrant = config.pop('reentrant', False)
defines = config.pop('defines', None)
internal = config.pop('internal', None)
reentrant = config.pop('reentrant', None)
fuzz = config.pop('fuzz', None)
self.cases = []
for name, config_ in cases.items():
case = TestCase(
config={
'name': name,
{ 'name': name,
'path': path + (':%d' % config_['lineno']
if 'lineno' in config_ else ''),
'suite': self.name,
'suite_defines': defines,
'suite_in': self.in_,
'suite_internal': internal,
'suite_reentrant': reentrant,
'suite_fuzz': self.fuzz_,
'suite_fuzz': fuzz,
**config_},
args=args)
args)
# skipping internal tests?
if args.get('no_internal') and case.in_ is not None:
if args.get('no_internal') and case.internal:
continue
self.cases.append(case)