diff --git a/scripts/pylib/twister/twisterlib/config_parser.py b/scripts/pylib/twister/twisterlib/config_parser.py index c822e9dfc77a272..f431e2d57c2f6b1 100644 --- a/scripts/pylib/twister/twisterlib/config_parser.py +++ b/scripts/pylib/twister/twisterlib/config_parser.py @@ -48,6 +48,7 @@ class TwisterConfigParser: "extra_conf_files": {"type": "list", "default": []}, "extra_overlay_confs" : {"type": "list", "default": []}, "extra_dtc_overlay_files": {"type": "list", "default": []}, + "required_snippets": {"type": "list"}, "build_only": {"type": "bool", "default": False}, "build_on_all": {"type": "bool", "default": False}, "skip": {"type": "bool", "default": False}, diff --git a/scripts/pylib/twister/twisterlib/runner.py b/scripts/pylib/twister/twisterlib/runner.py index 2d8805903343c50..ab4fe4a50b02cf9 100644 --- a/scripts/pylib/twister/twisterlib/runner.py +++ b/scripts/pylib/twister/twisterlib/runner.py @@ -359,6 +359,10 @@ def run_cmake(self, args="", filter_stages=[]): cmake_opts = ['-DBOARD={}'.format(self.platform.name)] cmake_args.extend(cmake_opts) + for this_snippet in self.instance.testsuite.required_snippets: + cmake_opts = ['-DSNIPPET={}'.format(this_snippet)] + cmake_args.extend(cmake_opts) + cmake = shutil.which('cmake') cmd = [cmake] + cmake_args diff --git a/scripts/pylib/twister/twisterlib/testplan.py b/scripts/pylib/twister/twisterlib/testplan.py index 85340885dbb6d12..98b05419a263046 100755 --- a/scripts/pylib/twister/twisterlib/testplan.py +++ b/scripts/pylib/twister/twisterlib/testplan.py @@ -16,6 +16,8 @@ import copy import shutil import random +import snippets +from pathlib import Path logger = logging.getLogger('twister') logger.setLevel(logging.DEBUG) @@ -819,6 +821,36 @@ def apply_filters(self, **kwargs): if plat.only_tags and not set(plat.only_tags) & ts.tags: instance.add_filter("Excluded tags per platform (only_tags)", Filters.PLATFORM) + snippet_args = {"snippets": ts.required_snippets} + found_snippets = snippets.process_snippets_inter(snippet_args, [Path(ZEPHYR_BASE), Path(ts.source_dir)]) + + # Search and check that all required snippet files are found + for this_snippet in snippet_args['snippets']: + if (this_snippet not in found_snippets): + instance.add_filter("Snippet not found", Filters.PLATFORM) + logger.error(f"Can't find snippet '%s' for test '%s'", this_snippet, ts.name) + + # Look for required snippets and check that they are applicable for these platforms/boards + for this_snippet in found_snippets: + matched_snippet_board = False + + if len(found_snippets[this_snippet].appends) > 0: + continue + + for this_board in found_snippets[this_snippet].board2appends: + if this_board.startswith('/'): + match = re.search(this_board[1:-1], plat.name) + if match is not None: + matched_snippet_board = True + break + elif this_board == plat.name: + matched_snippet_board = True + break + + if matched_snippet_board is False: + instance.add_filter("Snippet not supported", Filters.PLATFORM) + break + # platform_key is a list of unique platform attributes that form a unique key a test # will match against to determine if it should be scheduled to run. A key containing a # field name that the platform does not have will filter the platform. diff --git a/scripts/schemas/twister/testsuite-schema.yaml b/scripts/schemas/twister/testsuite-schema.yaml index 4d67fe717064e3e..3f7e325bed3841e 100644 --- a/scripts/schemas/twister/testsuite-schema.yaml +++ b/scripts/schemas/twister/testsuite-schema.yaml @@ -243,6 +243,11 @@ mapping: "extra_sections": type: any required: false + "required_snippets": + type: seq + required: false + sequence: + - type: str "filter": type: str required: false diff --git a/scripts/snippets.py b/scripts/snippets.py index 9662f3edcec22d1..548ca2824dcd788 100644 --- a/scripts/snippets.py +++ b/scripts/snippets.py @@ -238,6 +238,22 @@ def process_snippets(args: argparse.Namespace) -> Snippets: return snippets +def process_snippets_inter(requested_snippets, snippet_roots) -> Snippets: + '''Process snippet.yml files under each *snippet_root* + by recursive search. Return a Snippets object describing + the results of the search. + ''' + # This will contain information about all the snippets + # we discover in each snippet_root element. + snippets = Snippets(requested=requested_snippets) + + # Process each path in snippet_root in order, adjusting + # snippets as needed for each one. + for root in snippet_roots: + process_snippets_in(root, snippets) + + return snippets + def process_snippets_in(root_dir: Path, snippets: Snippets) -> None: '''Process snippet.yml files in *root_dir*, updating *snippets* as needed.''' diff --git a/scripts/twister b/scripts/twister index 0f5e622011f7c36..f41349871ed6c7b 100755 --- a/scripts/twister +++ b/scripts/twister @@ -44,6 +44,9 @@ pairs: Extra configuration options to be merged with a master prj.conf when building or running the test case. + required_snippets: + Snippets that must be applied for the test case to run. + sysbuild: (default False) If true, build the sample using the sysbuild infrastructure. Filtering will only be enabled for the main project, and is not supported for diff --git a/scripts/west_commands/build.py b/scripts/west_commands/build.py index dc56ae307d7bc94..0390c38170d9ac3 100644 --- a/scripts/west_commands/build.py +++ b/scripts/west_commands/build.py @@ -282,6 +282,7 @@ def _parse_test_item(self, test_item): extra_dtc_overlay_files = [] extra_overlay_confs = [] extra_conf_files = [] + required_snippets = [] for section in [common, item]: if not section: continue @@ -291,7 +292,8 @@ def _parse_test_item(self, test_item): 'extra_configs', 'extra_conf_files', 'extra_overlay_confs', - 'extra_dtc_overlay_files' + 'extra_dtc_overlay_files', + 'required_snippets' ]: extra = section.get(data) if not extra: @@ -314,6 +316,9 @@ def _parse_test_item(self, test_item): elif data == 'extra_dtc_overlay_files': extra_dtc_overlay_files.extend(arg_list) continue + elif data == 'required_snippets': + required_snippets.extend(arg_list) + continue if self.args.cmake_opts: self.args.cmake_opts.extend(args) @@ -331,6 +336,9 @@ def _parse_test_item(self, test_item): if extra_overlay_confs: args.append(f"OVERLAY_CONFIG=\"{';'.join(extra_overlay_confs)}\"") + + if required_snippets: + args.append(f"SNIPPET=\"{';'.join(required_snippets)}\"") # Build the final argument list args_expanded = ["-D{}".format(a.replace('"', '')) for a in args]