twister: optimize file scanning
We have been scanning code for tests for every scenario defined in the yaml file. This needs to only be parsed once and not more. We are dealing with the same files for each scenario. Signed-off-by: Anas Nashif <anas.nashif@intel.com>
This commit is contained in:
parent
77ef1b715a
commit
acda94be2d
|
@ -1466,7 +1466,7 @@ class SizeCalculator:
|
|||
|
||||
|
||||
class TwisterConfigParser:
|
||||
"""Class to read test case files with semantic checking
|
||||
"""Class to read testsuite yaml files with semantic checking
|
||||
"""
|
||||
|
||||
def __init__(self, filename, schema):
|
||||
|
@ -1477,14 +1477,14 @@ class TwisterConfigParser:
|
|||
self.data = {}
|
||||
self.schema = schema
|
||||
self.filename = filename
|
||||
self.tests = {}
|
||||
self.scenarios = {}
|
||||
self.common = {}
|
||||
|
||||
def load(self):
|
||||
self.data = scl.yaml_load_verify(self.filename, self.schema)
|
||||
|
||||
if 'tests' in self.data:
|
||||
self.tests = self.data['tests']
|
||||
self.scenarios = self.data['tests']
|
||||
if 'common' in self.data:
|
||||
self.common = self.data['common']
|
||||
|
||||
|
@ -1525,12 +1525,12 @@ class TwisterConfigParser:
|
|||
raise ConfigurationError(
|
||||
self.filename, "unknown type '%s'" % value)
|
||||
|
||||
def get_test(self, name, valid_keys):
|
||||
"""Get a dictionary representing the keys/values within a test
|
||||
def get_scenario(self, name, valid_keys):
|
||||
"""Get a dictionary representing the keys/values within a scenario
|
||||
|
||||
@param name The test in the .yaml file to retrieve data from
|
||||
@param name The scenario in the .yaml file to retrieve data from
|
||||
@param valid_keys A dictionary representing the intended semantics
|
||||
for this test. Each key in this dictionary is a key that could
|
||||
for this scenario. Each key in this dictionary is a key that could
|
||||
be specified, if a key is given in the .yaml file which isn't in
|
||||
here, it will generate an error. Each value in this dictionary
|
||||
is another dictionary containing metadata:
|
||||
|
@ -1546,7 +1546,7 @@ class TwisterConfigParser:
|
|||
"required" - If true, raise an error if not defined. If false
|
||||
and "default" isn't specified, a type conversion will be
|
||||
done on an empty string
|
||||
@return A dictionary containing the test key-value pairs with
|
||||
@return A dictionary containing the scenario key-value pairs with
|
||||
type conversion and default values filled in per valid_keys
|
||||
"""
|
||||
|
||||
|
@ -1554,7 +1554,7 @@ class TwisterConfigParser:
|
|||
for k, v in self.common.items():
|
||||
d[k] = v
|
||||
|
||||
for k, v in self.tests[name].items():
|
||||
for k, v in self.scenarios[name].items():
|
||||
if k in d:
|
||||
if isinstance(d[k], str):
|
||||
# By default, we just concatenate string values of keys
|
||||
|
@ -1820,279 +1820,6 @@ Tests should reference the category and subsystem with a dot as a separator.
|
|||
)
|
||||
return unique
|
||||
|
||||
def scan_file(self, inf_name):
|
||||
regular_suite_regex = re.compile(
|
||||
# do not match until end-of-line, otherwise we won't allow
|
||||
# stc_regex below to catch the ones that are declared in the same
|
||||
# line--as we only search starting the end of this match
|
||||
br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
|
||||
re.MULTILINE)
|
||||
registered_suite_regex = re.compile(
|
||||
br"^\s*ztest_register_test_suite"
|
||||
br"\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
|
||||
re.MULTILINE)
|
||||
new_suite_regex = re.compile(
|
||||
br"^\s*ZTEST_SUITE\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
|
||||
re.MULTILINE)
|
||||
# Checks if the file contains a definition of "void test_main(void)"
|
||||
# Since ztest provides a plain test_main implementation it is OK to:
|
||||
# 1. register test suites and not call the run function iff the test
|
||||
# doesn't have a custom test_main.
|
||||
# 2. register test suites and a custom test_main definition iff the test
|
||||
# also calls ztest_run_registered_test_suites.
|
||||
test_main_regex = re.compile(
|
||||
br"^\s*void\s+test_main\(void\)",
|
||||
re.MULTILINE)
|
||||
registered_suite_run_regex = re.compile(
|
||||
br"^\s*ztest_run_registered_test_suites\("
|
||||
br"(\*+|&)?(?P<state_identifier>[a-zA-Z0-9_]+)\)",
|
||||
re.MULTILINE)
|
||||
|
||||
warnings = None
|
||||
has_registered_test_suites = False
|
||||
has_run_registered_test_suites = False
|
||||
has_test_main = False
|
||||
|
||||
with open(inf_name) as inf:
|
||||
if os.name == 'nt':
|
||||
mmap_args = {'fileno': inf.fileno(), 'length': 0, 'access': mmap.ACCESS_READ}
|
||||
else:
|
||||
mmap_args = {'fileno': inf.fileno(), 'length': 0, 'flags': mmap.MAP_PRIVATE, 'prot': mmap.PROT_READ,
|
||||
'offset': 0}
|
||||
|
||||
with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
|
||||
regular_suite_regex_matches = \
|
||||
[m for m in regular_suite_regex.finditer(main_c)]
|
||||
registered_suite_regex_matches = \
|
||||
[m for m in registered_suite_regex.finditer(main_c)]
|
||||
new_suite_regex_matches = \
|
||||
[m for m in new_suite_regex.finditer(main_c)]
|
||||
|
||||
if registered_suite_regex_matches:
|
||||
has_registered_test_suites = True
|
||||
if registered_suite_run_regex.search(main_c):
|
||||
has_run_registered_test_suites = True
|
||||
if test_main_regex.search(main_c):
|
||||
has_test_main = True
|
||||
|
||||
if regular_suite_regex_matches:
|
||||
ztest_suite_names = \
|
||||
self._extract_ztest_suite_names(regular_suite_regex_matches)
|
||||
testcase_names, warnings = \
|
||||
self._find_regular_ztest_testcases(main_c, regular_suite_regex_matches, has_registered_test_suites)
|
||||
elif registered_suite_regex_matches:
|
||||
ztest_suite_names = \
|
||||
self._extract_ztest_suite_names(registered_suite_regex_matches)
|
||||
testcase_names, warnings = \
|
||||
self._find_regular_ztest_testcases(main_c, registered_suite_regex_matches, has_registered_test_suites)
|
||||
elif new_suite_regex_matches:
|
||||
ztest_suite_names = \
|
||||
self._extract_ztest_suite_names(new_suite_regex_matches)
|
||||
testcase_names, warnings = \
|
||||
self._find_new_ztest_testcases(main_c)
|
||||
else:
|
||||
# can't find ztest_test_suite, maybe a client, because
|
||||
# it includes ztest.h
|
||||
ztest_suite_names = []
|
||||
testcase_names, warnings = None, None
|
||||
|
||||
return ScanPathResult(
|
||||
matches=testcase_names,
|
||||
warnings=warnings,
|
||||
has_registered_test_suites=has_registered_test_suites,
|
||||
has_run_registered_test_suites=has_run_registered_test_suites,
|
||||
has_test_main=has_test_main,
|
||||
ztest_suite_names=ztest_suite_names)
|
||||
|
||||
@staticmethod
|
||||
def _extract_ztest_suite_names(suite_regex_matches):
|
||||
ztest_suite_names = \
|
||||
[m.group("suite_name") for m in suite_regex_matches]
|
||||
ztest_suite_names = \
|
||||
[name.decode("UTF-8") for name in ztest_suite_names]
|
||||
return ztest_suite_names
|
||||
|
||||
def _find_regular_ztest_testcases(self, search_area, suite_regex_matches, is_registered_test_suite):
|
||||
"""
|
||||
Find regular ztest testcases like "ztest_unit_test" or similar. Return
|
||||
testcases' names and eventually found warnings.
|
||||
"""
|
||||
testcase_regex = re.compile(
|
||||
br"""^\s* # empty space at the beginning is ok
|
||||
# catch the case where it is declared in the same sentence, e.g:
|
||||
#
|
||||
# ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
|
||||
# ztest_register_test_suite(n, p, ztest_user_unit_test(TESTNAME),
|
||||
(?:ztest_
|
||||
(?:test_suite\(|register_test_suite\([a-zA-Z0-9_]+\s*,\s*)
|
||||
[a-zA-Z0-9_]+\s*,\s*
|
||||
)?
|
||||
# Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
|
||||
ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?
|
||||
# Consume the argument that becomes the extra testcase
|
||||
\(\s*(?P<testcase_name>[a-zA-Z0-9_]+)
|
||||
# _setup_teardown() variant has two extra arguments that we ignore
|
||||
(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?
|
||||
\s*\)""",
|
||||
# We don't check how it finishes; we don't care
|
||||
re.MULTILINE | re.VERBOSE)
|
||||
achtung_regex = re.compile(
|
||||
br"(#ifdef|#endif)",
|
||||
re.MULTILINE)
|
||||
|
||||
search_start, search_end = \
|
||||
self._get_search_area_boundary(search_area, suite_regex_matches, is_registered_test_suite)
|
||||
limited_search_area = search_area[search_start:search_end]
|
||||
testcase_names, warnings = \
|
||||
self._find_ztest_testcases(limited_search_area, testcase_regex)
|
||||
|
||||
achtung_matches = re.findall(achtung_regex, limited_search_area)
|
||||
if achtung_matches and warnings is None:
|
||||
achtung = ", ".join(sorted({match.decode() for match in achtung_matches},reverse = True))
|
||||
warnings = f"found invalid {achtung} in ztest_test_suite()"
|
||||
|
||||
return testcase_names, warnings
|
||||
|
||||
@staticmethod
|
||||
def _get_search_area_boundary(search_area, suite_regex_matches, is_registered_test_suite):
|
||||
"""
|
||||
Get search area boundary based on "ztest_test_suite(...)",
|
||||
"ztest_register_test_suite(...)" or "ztest_run_test_suite(...)"
|
||||
functions occurrence.
|
||||
"""
|
||||
suite_run_regex = re.compile(
|
||||
br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
|
||||
re.MULTILINE)
|
||||
|
||||
search_start = suite_regex_matches[0].end()
|
||||
|
||||
suite_run_match = suite_run_regex.search(search_area)
|
||||
if suite_run_match:
|
||||
search_end = suite_run_match.start()
|
||||
elif not suite_run_match and not is_registered_test_suite:
|
||||
raise ValueError("can't find ztest_run_test_suite")
|
||||
else:
|
||||
search_end = re.compile(br"\);", re.MULTILINE) \
|
||||
.search(search_area, search_start) \
|
||||
.end()
|
||||
|
||||
return search_start, search_end
|
||||
|
||||
def _find_new_ztest_testcases(self, search_area):
|
||||
"""
|
||||
Find regular ztest testcases like "ZTEST", "ZTEST_F", ... Return
|
||||
testcases' names and eventually found warnings.
|
||||
"""
|
||||
testcase_regex = re.compile(
|
||||
br"^\s*(?:ZTEST|ZTEST_F|ZTEST_USER|ZTEST_USER_F)\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,"
|
||||
br"\s*(?P<testcase_name>[a-zA-Z0-9_]+)\s*",
|
||||
re.MULTILINE)
|
||||
|
||||
return self._find_ztest_testcases(search_area, testcase_regex)
|
||||
|
||||
@staticmethod
|
||||
def _find_ztest_testcases(search_area, testcase_regex):
|
||||
"""
|
||||
Parse search area and try to find testcases defined in testcase_regex
|
||||
argument. Return testcase names and eventually found warnings.
|
||||
"""
|
||||
testcase_regex_matches = \
|
||||
[m for m in testcase_regex.finditer(search_area)]
|
||||
testcase_names = \
|
||||
[m.group("testcase_name") for m in testcase_regex_matches]
|
||||
testcase_names = [name.decode("UTF-8") for name in testcase_names]
|
||||
warnings = None
|
||||
for testcase_name in testcase_names:
|
||||
if not testcase_name.startswith("test_"):
|
||||
warnings = "Found a test that does not start with test_"
|
||||
testcase_names = \
|
||||
[tc_name.replace("test_", "", 1) for tc_name in testcase_names]
|
||||
|
||||
return testcase_names, warnings
|
||||
|
||||
def scan_path(self, path):
|
||||
subcases = []
|
||||
has_registered_test_suites = False
|
||||
has_run_registered_test_suites = False
|
||||
has_test_main = False
|
||||
ztest_suite_names = []
|
||||
|
||||
src_dir_path = self._find_src_dir_path(path)
|
||||
for filename in glob.glob(os.path.join(src_dir_path, "*.c*")):
|
||||
try:
|
||||
result: ScanPathResult = self.scan_file(filename)
|
||||
if result.warnings:
|
||||
logger.error("%s: %s" % (filename, result.warnings))
|
||||
raise TwisterRuntimeError(
|
||||
"%s: %s" % (filename, result.warnings))
|
||||
if result.matches:
|
||||
subcases += result.matches
|
||||
if result.has_registered_test_suites:
|
||||
has_registered_test_suites = True
|
||||
if result.has_run_registered_test_suites:
|
||||
has_run_registered_test_suites = True
|
||||
if result.has_test_main:
|
||||
has_test_main = True
|
||||
if result.ztest_suite_names:
|
||||
ztest_suite_names += result.ztest_suite_names
|
||||
|
||||
except ValueError as e:
|
||||
logger.error("%s: can't find: %s" % (filename, e))
|
||||
|
||||
for filename in glob.glob(os.path.join(path, "*.c")):
|
||||
try:
|
||||
result: ScanPathResult = self.scan_file(filename)
|
||||
if result.warnings:
|
||||
logger.error("%s: %s" % (filename, result.warnings))
|
||||
if result.matches:
|
||||
subcases += result.matches
|
||||
if result.ztest_suite_names:
|
||||
ztest_suite_names += result.ztest_suite_names
|
||||
except ValueError as e:
|
||||
logger.error("%s: can't find: %s" % (filename, e))
|
||||
|
||||
if (has_registered_test_suites and has_test_main and
|
||||
not has_run_registered_test_suites):
|
||||
warning = \
|
||||
"Found call to 'ztest_register_test_suite()' but no "\
|
||||
"call to 'ztest_run_registered_test_suites()'"
|
||||
logger.error(warning)
|
||||
raise TwisterRuntimeError(warning)
|
||||
|
||||
return subcases, ztest_suite_names
|
||||
|
||||
def parse_subcases(self, test_path):
|
||||
subcases, ztest_suite_names = self.scan_path(test_path)
|
||||
# if testcases are provided as part of the yaml, skip this step.
|
||||
if not self.testcases:
|
||||
# only add each testcase once
|
||||
for sub in set(subcases):
|
||||
name = "{}.{}".format(self.id, sub)
|
||||
self.add_testcase(name)
|
||||
|
||||
if not subcases:
|
||||
self.add_testcase(self.id, freeform=True)
|
||||
|
||||
self.ztest_suite_names = ztest_suite_names
|
||||
|
||||
@staticmethod
|
||||
def _find_src_dir_path(test_dir_path):
|
||||
"""
|
||||
Try to find src directory with test source code. Sometimes due to the
|
||||
optimization reasons it is placed in upper directory.
|
||||
"""
|
||||
src_dir_name = "src"
|
||||
src_dir_path = os.path.join(test_dir_path, src_dir_name)
|
||||
if os.path.isdir(src_dir_path):
|
||||
return src_dir_path
|
||||
src_dir_path = os.path.join(test_dir_path, "..", src_dir_name)
|
||||
if os.path.isdir(src_dir_path):
|
||||
return src_dir_path
|
||||
return ""
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class TestInstance(DisablePyTestCollectionMixin):
|
||||
"""Class representing the execution of a particular TestSuite on a platform
|
||||
|
@ -3409,10 +3136,12 @@ class TestPlan(DisablePyTestCollectionMixin):
|
|||
ts_path = os.path.dirname(ts_path)
|
||||
workdir = os.path.relpath(ts_path, root)
|
||||
|
||||
for name in parsed_data.tests.keys():
|
||||
subcases, ztest_suite_names = self.scan_path(ts_path)
|
||||
|
||||
for name in parsed_data.scenarios.keys():
|
||||
ts = TestSuite(root, workdir, name)
|
||||
|
||||
ts_dict = parsed_data.get_test(name, self.testsuite_valid_keys)
|
||||
ts_dict = parsed_data.get_scenario(name, self.testsuite_valid_keys)
|
||||
|
||||
ts.source_dir = ts_path
|
||||
ts.yamlfile = ts_path
|
||||
|
@ -3451,7 +3180,15 @@ class TestPlan(DisablePyTestCollectionMixin):
|
|||
for tc in testcases:
|
||||
ts.add_testcase(name=f"{name}.{tc}")
|
||||
else:
|
||||
ts.parse_subcases(ts_path)
|
||||
# only add each testcase once
|
||||
for sub in set(subcases):
|
||||
name = "{}.{}".format(ts.id, sub)
|
||||
ts.add_testcase(name)
|
||||
|
||||
if not subcases:
|
||||
ts.add_testcase(ts.id, freeform=True)
|
||||
|
||||
ts.ztest_suite_names = ztest_suite_names
|
||||
|
||||
if testsuite_filter:
|
||||
if ts.name and ts.name in testsuite_filter:
|
||||
|
@ -3464,6 +3201,267 @@ class TestPlan(DisablePyTestCollectionMixin):
|
|||
self.load_errors += 1
|
||||
return len(self.testsuites)
|
||||
|
||||
|
||||
|
||||
def scan_file(self, inf_name):
|
||||
regular_suite_regex = re.compile(
|
||||
# do not match until end-of-line, otherwise we won't allow
|
||||
# stc_regex below to catch the ones that are declared in the same
|
||||
# line--as we only search starting the end of this match
|
||||
br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
|
||||
re.MULTILINE)
|
||||
registered_suite_regex = re.compile(
|
||||
br"^\s*ztest_register_test_suite"
|
||||
br"\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
|
||||
re.MULTILINE)
|
||||
new_suite_regex = re.compile(
|
||||
br"^\s*ZTEST_SUITE\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
|
||||
re.MULTILINE)
|
||||
# Checks if the file contains a definition of "void test_main(void)"
|
||||
# Since ztest provides a plain test_main implementation it is OK to:
|
||||
# 1. register test suites and not call the run function iff the test
|
||||
# doesn't have a custom test_main.
|
||||
# 2. register test suites and a custom test_main definition iff the test
|
||||
# also calls ztest_run_registered_test_suites.
|
||||
test_main_regex = re.compile(
|
||||
br"^\s*void\s+test_main\(void\)",
|
||||
re.MULTILINE)
|
||||
registered_suite_run_regex = re.compile(
|
||||
br"^\s*ztest_run_registered_test_suites\("
|
||||
br"(\*+|&)?(?P<state_identifier>[a-zA-Z0-9_]+)\)",
|
||||
re.MULTILINE)
|
||||
|
||||
warnings = None
|
||||
has_registered_test_suites = False
|
||||
has_run_registered_test_suites = False
|
||||
has_test_main = False
|
||||
|
||||
with open(inf_name) as inf:
|
||||
if os.name == 'nt':
|
||||
mmap_args = {'fileno': inf.fileno(), 'length': 0, 'access': mmap.ACCESS_READ}
|
||||
else:
|
||||
mmap_args = {'fileno': inf.fileno(), 'length': 0, 'flags': mmap.MAP_PRIVATE, 'prot': mmap.PROT_READ,
|
||||
'offset': 0}
|
||||
|
||||
with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
|
||||
regular_suite_regex_matches = \
|
||||
[m for m in regular_suite_regex.finditer(main_c)]
|
||||
registered_suite_regex_matches = \
|
||||
[m for m in registered_suite_regex.finditer(main_c)]
|
||||
new_suite_regex_matches = \
|
||||
[m for m in new_suite_regex.finditer(main_c)]
|
||||
|
||||
if registered_suite_regex_matches:
|
||||
has_registered_test_suites = True
|
||||
if registered_suite_run_regex.search(main_c):
|
||||
has_run_registered_test_suites = True
|
||||
if test_main_regex.search(main_c):
|
||||
has_test_main = True
|
||||
|
||||
if regular_suite_regex_matches:
|
||||
ztest_suite_names = \
|
||||
self._extract_ztest_suite_names(regular_suite_regex_matches)
|
||||
testcase_names, warnings = \
|
||||
self._find_regular_ztest_testcases(main_c, regular_suite_regex_matches, has_registered_test_suites)
|
||||
elif registered_suite_regex_matches:
|
||||
ztest_suite_names = \
|
||||
self._extract_ztest_suite_names(registered_suite_regex_matches)
|
||||
testcase_names, warnings = \
|
||||
self._find_regular_ztest_testcases(main_c, registered_suite_regex_matches, has_registered_test_suites)
|
||||
elif new_suite_regex_matches:
|
||||
ztest_suite_names = \
|
||||
self._extract_ztest_suite_names(new_suite_regex_matches)
|
||||
testcase_names, warnings = \
|
||||
self._find_new_ztest_testcases(main_c)
|
||||
else:
|
||||
# can't find ztest_test_suite, maybe a client, because
|
||||
# it includes ztest.h
|
||||
ztest_suite_names = []
|
||||
testcase_names, warnings = None, None
|
||||
|
||||
return ScanPathResult(
|
||||
matches=testcase_names,
|
||||
warnings=warnings,
|
||||
has_registered_test_suites=has_registered_test_suites,
|
||||
has_run_registered_test_suites=has_run_registered_test_suites,
|
||||
has_test_main=has_test_main,
|
||||
ztest_suite_names=ztest_suite_names)
|
||||
|
||||
@staticmethod
|
||||
def _extract_ztest_suite_names(suite_regex_matches):
|
||||
ztest_suite_names = \
|
||||
[m.group("suite_name") for m in suite_regex_matches]
|
||||
ztest_suite_names = \
|
||||
[name.decode("UTF-8") for name in ztest_suite_names]
|
||||
return ztest_suite_names
|
||||
|
||||
def _find_regular_ztest_testcases(self, search_area, suite_regex_matches, is_registered_test_suite):
|
||||
"""
|
||||
Find regular ztest testcases like "ztest_unit_test" or similar. Return
|
||||
testcases' names and eventually found warnings.
|
||||
"""
|
||||
testcase_regex = re.compile(
|
||||
br"""^\s* # empty space at the beginning is ok
|
||||
# catch the case where it is declared in the same sentence, e.g:
|
||||
#
|
||||
# ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
|
||||
# ztest_register_test_suite(n, p, ztest_user_unit_test(TESTNAME),
|
||||
(?:ztest_
|
||||
(?:test_suite\(|register_test_suite\([a-zA-Z0-9_]+\s*,\s*)
|
||||
[a-zA-Z0-9_]+\s*,\s*
|
||||
)?
|
||||
# Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
|
||||
ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?
|
||||
# Consume the argument that becomes the extra testcase
|
||||
\(\s*(?P<testcase_name>[a-zA-Z0-9_]+)
|
||||
# _setup_teardown() variant has two extra arguments that we ignore
|
||||
(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?
|
||||
\s*\)""",
|
||||
# We don't check how it finishes; we don't care
|
||||
re.MULTILINE | re.VERBOSE)
|
||||
achtung_regex = re.compile(
|
||||
br"(#ifdef|#endif)",
|
||||
re.MULTILINE)
|
||||
|
||||
search_start, search_end = \
|
||||
self._get_search_area_boundary(search_area, suite_regex_matches, is_registered_test_suite)
|
||||
limited_search_area = search_area[search_start:search_end]
|
||||
testcase_names, warnings = \
|
||||
self._find_ztest_testcases(limited_search_area, testcase_regex)
|
||||
|
||||
achtung_matches = re.findall(achtung_regex, limited_search_area)
|
||||
if achtung_matches and warnings is None:
|
||||
achtung = ", ".join(sorted({match.decode() for match in achtung_matches},reverse = True))
|
||||
warnings = f"found invalid {achtung} in ztest_test_suite()"
|
||||
|
||||
return testcase_names, warnings
|
||||
|
||||
@staticmethod
|
||||
def _get_search_area_boundary(search_area, suite_regex_matches, is_registered_test_suite):
|
||||
"""
|
||||
Get search area boundary based on "ztest_test_suite(...)",
|
||||
"ztest_register_test_suite(...)" or "ztest_run_test_suite(...)"
|
||||
functions occurrence.
|
||||
"""
|
||||
suite_run_regex = re.compile(
|
||||
br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
|
||||
re.MULTILINE)
|
||||
|
||||
search_start = suite_regex_matches[0].end()
|
||||
|
||||
suite_run_match = suite_run_regex.search(search_area)
|
||||
if suite_run_match:
|
||||
search_end = suite_run_match.start()
|
||||
elif not suite_run_match and not is_registered_test_suite:
|
||||
raise ValueError("can't find ztest_run_test_suite")
|
||||
else:
|
||||
search_end = re.compile(br"\);", re.MULTILINE) \
|
||||
.search(search_area, search_start) \
|
||||
.end()
|
||||
|
||||
return search_start, search_end
|
||||
|
||||
def _find_new_ztest_testcases(self, search_area):
|
||||
"""
|
||||
Find regular ztest testcases like "ZTEST" or "ZTEST_F". Return
|
||||
testcases' names and eventually found warnings.
|
||||
"""
|
||||
testcase_regex = re.compile(
|
||||
br"^\s*(?:ZTEST|ZTEST_F|ZTEST_USER|ZTEST_USER_F)\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,"
|
||||
br"\s*(?P<testcase_name>[a-zA-Z0-9_]+)\s*",
|
||||
re.MULTILINE)
|
||||
|
||||
return self._find_ztest_testcases(search_area, testcase_regex)
|
||||
|
||||
@staticmethod
|
||||
def _find_ztest_testcases(search_area, testcase_regex):
|
||||
"""
|
||||
Parse search area and try to find testcases defined in testcase_regex
|
||||
argument. Return testcase names and eventually found warnings.
|
||||
"""
|
||||
testcase_regex_matches = \
|
||||
[m for m in testcase_regex.finditer(search_area)]
|
||||
testcase_names = \
|
||||
[m.group("testcase_name") for m in testcase_regex_matches]
|
||||
testcase_names = [name.decode("UTF-8") for name in testcase_names]
|
||||
warnings = None
|
||||
for testcase_name in testcase_names:
|
||||
if not testcase_name.startswith("test_"):
|
||||
warnings = "Found a test that does not start with test_"
|
||||
testcase_names = \
|
||||
[tc_name.replace("test_", "", 1) for tc_name in testcase_names]
|
||||
|
||||
return testcase_names, warnings
|
||||
|
||||
def scan_path(self, path):
|
||||
subcases = []
|
||||
has_registered_test_suites = False
|
||||
has_run_registered_test_suites = False
|
||||
has_test_main = False
|
||||
ztest_suite_names = []
|
||||
|
||||
src_dir_path = self._find_src_dir_path(path)
|
||||
for filename in glob.glob(os.path.join(src_dir_path, "*.c*")):
|
||||
try:
|
||||
result: ScanPathResult = self.scan_file(filename)
|
||||
if result.warnings:
|
||||
logger.error("%s: %s" % (filename, result.warnings))
|
||||
raise TwisterRuntimeError(
|
||||
"%s: %s" % (filename, result.warnings))
|
||||
if result.matches:
|
||||
subcases += result.matches
|
||||
if result.has_registered_test_suites:
|
||||
has_registered_test_suites = True
|
||||
if result.has_run_registered_test_suites:
|
||||
has_run_registered_test_suites = True
|
||||
if result.has_test_main:
|
||||
has_test_main = True
|
||||
if result.ztest_suite_names:
|
||||
ztest_suite_names += result.ztest_suite_names
|
||||
|
||||
except ValueError as e:
|
||||
logger.error("%s: can't find: %s" % (filename, e))
|
||||
|
||||
for filename in glob.glob(os.path.join(path, "*.c")):
|
||||
try:
|
||||
result: ScanPathResult = self.scan_file(filename)
|
||||
if result.warnings:
|
||||
logger.error("%s: %s" % (filename, result.warnings))
|
||||
if result.matches:
|
||||
subcases += result.matches
|
||||
if result.ztest_suite_names:
|
||||
ztest_suite_names += result.ztest_suite_names
|
||||
except ValueError as e:
|
||||
logger.error("%s: can't find: %s" % (filename, e))
|
||||
|
||||
if (has_registered_test_suites and has_test_main and
|
||||
not has_run_registered_test_suites):
|
||||
warning = \
|
||||
"Found call to 'ztest_register_test_suite()' but no "\
|
||||
"call to 'ztest_run_registered_test_suites()'"
|
||||
logger.error(warning)
|
||||
raise TwisterRuntimeError(warning)
|
||||
|
||||
return subcases, ztest_suite_names
|
||||
|
||||
@staticmethod
|
||||
def _find_src_dir_path(test_dir_path):
|
||||
"""
|
||||
Try to find src directory with test source code. Sometimes due to the
|
||||
optimization reasons it is placed in upper directory.
|
||||
"""
|
||||
src_dir_name = "src"
|
||||
src_dir_path = os.path.join(test_dir_path, src_dir_name)
|
||||
if os.path.isdir(src_dir_path):
|
||||
return src_dir_path
|
||||
src_dir_path = os.path.join(test_dir_path, "..", src_dir_name)
|
||||
if os.path.isdir(src_dir_path):
|
||||
return src_dir_path
|
||||
return ""
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_platform(self, name):
|
||||
selected_platform = None
|
||||
for platform in self.platforms:
|
||||
|
|
Loading…
Reference in a new issue