diff --git a/scripts/pylib/twister/twisterlib/environment.py b/scripts/pylib/twister/twisterlib/environment.py index bdb9765363a..5bf5fbd1d6d 100644 --- a/scripts/pylib/twister/twisterlib/environment.py +++ b/scripts/pylib/twister/twisterlib/environment.py @@ -253,6 +253,12 @@ Artificially long but functional example: timeout would be multiplication of test timeout value, board-level timeout multiplier and global timeout multiplier (this parameter)""") + parser.add_argument( + "--test-pattern", action="append", + help="""Run only the tests matching the specified pattern. The pattern + can include regular expressions. + """) + test_xor_subtest.add_argument( "-s", "--test", "--scenario", action="append", type = norm_path, help="""Run only the specified test suite scenario. These are named by diff --git a/scripts/pylib/twister/twisterlib/testplan.py b/scripts/pylib/twister/twisterlib/testplan.py index 6845ca123ad..31e833fc4b4 100755 --- a/scripts/pylib/twister/twisterlib/testplan.py +++ b/scripts/pylib/twister/twisterlib/testplan.py @@ -202,13 +202,12 @@ class TestPlan: def discover(self): self.handle_modules() - if self.options.test: - self.run_individual_testsuite = self.options.test - self.test_config = TestConfiguration(self.env.test_config) self.add_configurations() - num = self.add_testsuites(testsuite_filter=self.run_individual_testsuite) + num = self.add_testsuites(testsuite_filter=self.options.test, + testsuite_pattern=self.options.test_pattern) + if num == 0: raise TwisterRuntimeError("No testsuites found at the specified location...") if self.load_errors: @@ -507,9 +506,35 @@ class TestPlan: testcases.remove(case.detailed_name) return testcases - def add_testsuites(self, testsuite_filter=None): + def _is_testsuite_selected(self, suite: TestSuite, testsuite_filter, testsuite_patterns_r): + """Check if the testsuite is selected by the user.""" + if not testsuite_filter and not testsuite_patterns_r: + # no matching requested, include all testsuites + return True + if testsuite_filter: + scenario = os.path.basename(suite.name) + if ( + suite.name + and (suite.name in testsuite_filter or scenario in testsuite_filter) + ): + return True + if testsuite_patterns_r: + for r in testsuite_patterns_r: + if r.search(suite.id): + return True + return False + + def add_testsuites(self, testsuite_filter=None, testsuite_pattern=None): if testsuite_filter is None: testsuite_filter = [] + + testsuite_patterns_r = [] + if testsuite_pattern is None: + testsuite_pattern = [] + else: + for pattern in testsuite_pattern: + testsuite_patterns_r.append(re.compile(pattern)) + for root in self.env.test_roots: root = os.path.abspath(root) @@ -574,14 +599,11 @@ class TestPlan: else: suite.add_subcases(suite_dict) - if testsuite_filter: - scenario = os.path.basename(suite.name) - if ( - suite.name - and (suite.name in testsuite_filter or scenario in testsuite_filter) - ): - self.testsuites[suite.name] = suite - elif suite.name in self.testsuites: + if not self._is_testsuite_selected(suite, testsuite_filter, + testsuite_patterns_r): + # skip testsuite if they were not selected directly by the user + continue + if suite.name in self.testsuites: msg = ( f"test suite '{suite.name}' in '{suite.yamlfile}' is already added" ) diff --git a/scripts/tests/twister/test_testplan.py b/scripts/tests/twister/test_testplan.py index 60f1ddd9437..55a5561c5f7 100644 --- a/scripts/tests/twister/test_testplan.py +++ b/scripts/tests/twister/test_testplan.py @@ -570,6 +570,7 @@ def test_testplan_discover( env.test_config = tmp_tc testplan = TestPlan(env=env) testplan.options = mock.Mock( + test_pattern=[], test='ts1', quarantine_list=[tmp_path / qf for qf in ql], quarantine_verify=qv @@ -589,7 +590,7 @@ def test_testplan_discover( with pytest.raises(exception) if exception else nullcontext(): testplan.discover() - testplan.add_testsuites.assert_called_once_with(testsuite_filter='ts1') + testplan.add_testsuites.assert_called_once_with(testsuite_filter='ts1', testsuite_pattern=[]) assert all([log in caplog.text for log in expected_logs]) @@ -1186,7 +1187,7 @@ TESTDATA_9 = [ (['good_test/dummy.common.1', 'good_test/dummy.common.2', 'good_test/dummy.common.3'], False, True, 3, 1), (['good_test/dummy.common.1', 'good_test/dummy.common.2', 'duplicate_test/dummy.common.1', 'duplicate_test/dummy.common.2'], False, True, 4, 1), - (['dummy.common.1', 'dummy.common.2'], False, False, 2, 1), + (['dummy.common.1', 'dummy.common.2'], False, False, 2, 2), (['good_test/dummy.common.1', 'good_test/dummy.common.2', 'good_test/dummy.common.3'], True, True, 0, 1), ] @@ -1314,7 +1315,7 @@ tests: testplan = TestPlan(env=env) - res = testplan.add_testsuites(testsuite_filter) + res = testplan.add_testsuites(testsuite_filter, testsuite_pattern=[]) assert res == expected_suite_count assert testplan.load_errors == expected_errors