12f28a4fcSJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause 22f28a4fcSJuraj Linkeš# Copyright(c) 2010-2019 Intel Corporation 32f28a4fcSJuraj Linkeš# Copyright(c) 2022-2023 PANTHEON.tech s.r.o. 42f28a4fcSJuraj Linkeš# Copyright(c) 2022-2023 University of New Hampshire 52f28a4fcSJuraj Linkeš 62f28a4fcSJuraj Linkeš"""Test suite runner module. 72f28a4fcSJuraj Linkeš 82f28a4fcSJuraj LinkešThe module is responsible for running DTS in a series of stages: 92f28a4fcSJuraj Linkeš 102f28a4fcSJuraj Linkeš #. Execution stage, 112f28a4fcSJuraj Linkeš #. Build target stage, 122f28a4fcSJuraj Linkeš #. Test suite stage, 132f28a4fcSJuraj Linkeš #. Test case stage. 142f28a4fcSJuraj Linkeš 152f28a4fcSJuraj LinkešThe execution and build target stages set up the environment before running test suites. 162f28a4fcSJuraj LinkešThe test suite stage sets up steps common to all test cases 172f28a4fcSJuraj Linkešand the test case stage runs test cases individually. 182f28a4fcSJuraj Linkeš""" 192f28a4fcSJuraj Linkeš 205d094f9fSJuraj Linkešimport importlib 215d094f9fSJuraj Linkešimport inspect 2204f5a5a6SJuraj Linkešimport os 235d094f9fSJuraj Linkešimport re 242f28a4fcSJuraj Linkešimport sys 2504f5a5a6SJuraj Linkešfrom pathlib import Path 265c7b6207SJuraj Linkešfrom types import MethodType 27*4a4678c7SJuraj Linkešfrom typing import Iterable, Sequence 282f28a4fcSJuraj Linkeš 292f28a4fcSJuraj Linkešfrom .config import ( 302f28a4fcSJuraj Linkeš BuildTargetConfiguration, 315d094f9fSJuraj Linkeš Configuration, 322f28a4fcSJuraj Linkeš ExecutionConfiguration, 332f28a4fcSJuraj Linkeš TestSuiteConfig, 342f28a4fcSJuraj Linkeš load_config, 352f28a4fcSJuraj Linkeš) 365d094f9fSJuraj Linkešfrom .exception import ( 375d094f9fSJuraj Linkeš BlockingTestSuiteError, 385d094f9fSJuraj Linkeš ConfigurationError, 395d094f9fSJuraj Linkeš SSHTimeoutError, 405d094f9fSJuraj Linkeš TestCaseVerifyError, 415d094f9fSJuraj Linkeš) 4204f5a5a6SJuraj Linkešfrom .logger import DTSLogger, DtsStage, get_dts_logger 435c7b6207SJuraj Linkešfrom .settings import SETTINGS 445c7b6207SJuraj Linkešfrom .test_result import ( 455c7b6207SJuraj Linkeš BuildTargetResult, 465c7b6207SJuraj Linkeš DTSResult, 475c7b6207SJuraj Linkeš ExecutionResult, 485c7b6207SJuraj Linkeš Result, 495c7b6207SJuraj Linkeš TestCaseResult, 505c7b6207SJuraj Linkeš TestSuiteResult, 515d094f9fSJuraj Linkeš TestSuiteWithCases, 525c7b6207SJuraj Linkeš) 535d094f9fSJuraj Linkešfrom .test_suite import TestSuite 542f28a4fcSJuraj Linkešfrom .testbed_model import SutNode, TGNode 552f28a4fcSJuraj Linkeš 562f28a4fcSJuraj Linkeš 572f28a4fcSJuraj Linkešclass DTSRunner: 582f28a4fcSJuraj Linkeš r"""Test suite runner class. 592f28a4fcSJuraj Linkeš 602f28a4fcSJuraj Linkeš The class is responsible for running tests on testbeds defined in the test run configuration. 612f28a4fcSJuraj Linkeš Each setup or teardown of each stage is recorded in a :class:`~framework.test_result.DTSResult` 622f28a4fcSJuraj Linkeš or one of its subclasses. The test case results are also recorded. 632f28a4fcSJuraj Linkeš 64caae1889SJuraj Linkeš If an error occurs, the current stage is aborted, the error is recorded, everything in 65caae1889SJuraj Linkeš the inner stages is marked as blocked and the run continues in the next iteration 66caae1889SJuraj Linkeš of the same stage. The return code is the highest `severity` of all 672f28a4fcSJuraj Linkeš :class:`~.framework.exception.DTSError`\s. 682f28a4fcSJuraj Linkeš 692f28a4fcSJuraj Linkeš Example: 70caae1889SJuraj Linkeš An error occurs in a build target setup. The current build target is aborted, 71caae1889SJuraj Linkeš all test suites and their test cases are marked as blocked and the run continues 72caae1889SJuraj Linkeš with the next build target. If the errored build target was the last one in the 732f28a4fcSJuraj Linkeš given execution, the next execution begins. 742f28a4fcSJuraj Linkeš """ 752f28a4fcSJuraj Linkeš 765d094f9fSJuraj Linkeš _configuration: Configuration 7704f5a5a6SJuraj Linkeš _logger: DTSLogger 782f28a4fcSJuraj Linkeš _result: DTSResult 795d094f9fSJuraj Linkeš _test_suite_class_prefix: str 805d094f9fSJuraj Linkeš _test_suite_module_prefix: str 815d094f9fSJuraj Linkeš _func_test_case_regex: str 825d094f9fSJuraj Linkeš _perf_test_case_regex: str 832f28a4fcSJuraj Linkeš 842f28a4fcSJuraj Linkeš def __init__(self): 855d094f9fSJuraj Linkeš """Initialize the instance with configuration, logger, result and string constants.""" 86*4a4678c7SJuraj Linkeš self._configuration = load_config(SETTINGS.config_file_path) 8704f5a5a6SJuraj Linkeš self._logger = get_dts_logger() 8804f5a5a6SJuraj Linkeš if not os.path.exists(SETTINGS.output_dir): 8904f5a5a6SJuraj Linkeš os.makedirs(SETTINGS.output_dir) 9004f5a5a6SJuraj Linkeš self._logger.add_dts_root_logger_handlers(SETTINGS.verbose, SETTINGS.output_dir) 912f28a4fcSJuraj Linkeš self._result = DTSResult(self._logger) 925d094f9fSJuraj Linkeš self._test_suite_class_prefix = "Test" 935d094f9fSJuraj Linkeš self._test_suite_module_prefix = "tests.TestSuite_" 945d094f9fSJuraj Linkeš self._func_test_case_regex = r"test_(?!perf_)" 955d094f9fSJuraj Linkeš self._perf_test_case_regex = r"test_perf_" 962f28a4fcSJuraj Linkeš 972f28a4fcSJuraj Linkeš def run(self): 982f28a4fcSJuraj Linkeš """Run all build targets in all executions from the test run configuration. 992f28a4fcSJuraj Linkeš 1002f28a4fcSJuraj Linkeš Before running test suites, executions and build targets are first set up. 1012f28a4fcSJuraj Linkeš The executions and build targets defined in the test run configuration are iterated over. 1022f28a4fcSJuraj Linkeš The executions define which tests to run and where to run them and build targets define 1032f28a4fcSJuraj Linkeš the DPDK build setup. 1042f28a4fcSJuraj Linkeš 1052f28a4fcSJuraj Linkeš The tests suites are set up for each execution/build target tuple and each discovered 1062f28a4fcSJuraj Linkeš test case within the test suite is set up, executed and torn down. After all test cases 1072f28a4fcSJuraj Linkeš have been executed, the test suite is torn down and the next build target will be tested. 1082f28a4fcSJuraj Linkeš 109caae1889SJuraj Linkeš In order to properly mark test suites and test cases as blocked in case of a failure, 110caae1889SJuraj Linkeš we need to have discovered which test suites and test cases to run before any failures 111caae1889SJuraj Linkeš happen. The discovery happens at the earliest point at the start of each execution. 112caae1889SJuraj Linkeš 1132f28a4fcSJuraj Linkeš All the nested steps look like this: 1142f28a4fcSJuraj Linkeš 1152f28a4fcSJuraj Linkeš #. Execution setup 1162f28a4fcSJuraj Linkeš 1172f28a4fcSJuraj Linkeš #. Build target setup 1182f28a4fcSJuraj Linkeš 1192f28a4fcSJuraj Linkeš #. Test suite setup 1202f28a4fcSJuraj Linkeš 1212f28a4fcSJuraj Linkeš #. Test case setup 1222f28a4fcSJuraj Linkeš #. Test case logic 1232f28a4fcSJuraj Linkeš #. Test case teardown 1242f28a4fcSJuraj Linkeš 1252f28a4fcSJuraj Linkeš #. Test suite teardown 1262f28a4fcSJuraj Linkeš 1272f28a4fcSJuraj Linkeš #. Build target teardown 1282f28a4fcSJuraj Linkeš 1292f28a4fcSJuraj Linkeš #. Execution teardown 1302f28a4fcSJuraj Linkeš 1312f28a4fcSJuraj Linkeš The test cases are filtered according to the specification in the test run configuration and 132*4a4678c7SJuraj Linkeš the :option:`--test-suite` command line argument or 1332f28a4fcSJuraj Linkeš the :envvar:`DTS_TESTCASES` environment variable. 1342f28a4fcSJuraj Linkeš """ 1352f28a4fcSJuraj Linkeš sut_nodes: dict[str, SutNode] = {} 1362f28a4fcSJuraj Linkeš tg_nodes: dict[str, TGNode] = {} 1372f28a4fcSJuraj Linkeš try: 1382f28a4fcSJuraj Linkeš # check the python version of the server that runs dts 1392f28a4fcSJuraj Linkeš self._check_dts_python_version() 1405d094f9fSJuraj Linkeš self._result.update_setup(Result.PASS) 1412f28a4fcSJuraj Linkeš 1422f28a4fcSJuraj Linkeš # for all Execution sections 1435d094f9fSJuraj Linkeš for execution in self._configuration.executions: 14404f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.execution_setup) 1455d094f9fSJuraj Linkeš self._logger.info( 1465d094f9fSJuraj Linkeš f"Running execution with SUT '{execution.system_under_test_node.name}'." 1475d094f9fSJuraj Linkeš ) 148caae1889SJuraj Linkeš execution_result = self._result.add_execution(execution) 1495d094f9fSJuraj Linkeš # we don't want to modify the original config, so create a copy 150*4a4678c7SJuraj Linkeš execution_test_suites = list( 151*4a4678c7SJuraj Linkeš SETTINGS.test_suites if SETTINGS.test_suites else execution.test_suites 152*4a4678c7SJuraj Linkeš ) 1535d094f9fSJuraj Linkeš if not execution.skip_smoke_tests: 1545d094f9fSJuraj Linkeš execution_test_suites[:0] = [TestSuiteConfig.from_dict("smoke_tests")] 1552f28a4fcSJuraj Linkeš try: 1565d094f9fSJuraj Linkeš test_suites_with_cases = self._get_test_suites_with_cases( 1575d094f9fSJuraj Linkeš execution_test_suites, execution.func, execution.perf 1585d094f9fSJuraj Linkeš ) 159caae1889SJuraj Linkeš execution_result.test_suites_with_cases = test_suites_with_cases 1602f28a4fcSJuraj Linkeš except Exception as e: 1615d094f9fSJuraj Linkeš self._logger.exception( 1625d094f9fSJuraj Linkeš f"Invalid test suite configuration found: " f"{execution_test_suites}." 1635d094f9fSJuraj Linkeš ) 1645d094f9fSJuraj Linkeš execution_result.update_setup(Result.FAIL, e) 1652f28a4fcSJuraj Linkeš 1662f28a4fcSJuraj Linkeš else: 1675d094f9fSJuraj Linkeš self._connect_nodes_and_run_execution( 1685d094f9fSJuraj Linkeš sut_nodes, tg_nodes, execution, execution_result, test_suites_with_cases 1695d094f9fSJuraj Linkeš ) 1702f28a4fcSJuraj Linkeš 1712f28a4fcSJuraj Linkeš except Exception as e: 1722f28a4fcSJuraj Linkeš self._logger.exception("An unexpected error has occurred.") 1732f28a4fcSJuraj Linkeš self._result.add_error(e) 1742f28a4fcSJuraj Linkeš raise 1752f28a4fcSJuraj Linkeš 1762f28a4fcSJuraj Linkeš finally: 1772f28a4fcSJuraj Linkeš try: 17804f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.post_execution) 1792f28a4fcSJuraj Linkeš for node in (sut_nodes | tg_nodes).values(): 1802f28a4fcSJuraj Linkeš node.close() 1812f28a4fcSJuraj Linkeš self._result.update_teardown(Result.PASS) 1822f28a4fcSJuraj Linkeš except Exception as e: 1832f28a4fcSJuraj Linkeš self._logger.exception("The final cleanup of nodes failed.") 1842f28a4fcSJuraj Linkeš self._result.update_teardown(Result.ERROR, e) 1852f28a4fcSJuraj Linkeš 1862f28a4fcSJuraj Linkeš # we need to put the sys.exit call outside the finally clause to make sure 1872f28a4fcSJuraj Linkeš # that unexpected exceptions will propagate 1882f28a4fcSJuraj Linkeš # in that case, the error that should be reported is the uncaught exception as 1892f28a4fcSJuraj Linkeš # that is a severe error originating from the framework 1902f28a4fcSJuraj Linkeš # at that point, we'll only have partial results which could be impacted by the 1912f28a4fcSJuraj Linkeš # error causing the uncaught exception, making them uninterpretable 1922f28a4fcSJuraj Linkeš self._exit_dts() 1932f28a4fcSJuraj Linkeš 1942f28a4fcSJuraj Linkeš def _check_dts_python_version(self) -> None: 1952f28a4fcSJuraj Linkeš """Check the required Python version - v3.10.""" 1962f28a4fcSJuraj Linkeš if sys.version_info.major < 3 or ( 1972f28a4fcSJuraj Linkeš sys.version_info.major == 3 and sys.version_info.minor < 10 1982f28a4fcSJuraj Linkeš ): 1992f28a4fcSJuraj Linkeš self._logger.warning( 2002f28a4fcSJuraj Linkeš "DTS execution node's python version is lower than Python 3.10, " 2012f28a4fcSJuraj Linkeš "is deprecated and will not work in future releases." 2022f28a4fcSJuraj Linkeš ) 2032f28a4fcSJuraj Linkeš self._logger.warning("Please use Python >= 3.10 instead.") 2042f28a4fcSJuraj Linkeš 2055d094f9fSJuraj Linkeš def _get_test_suites_with_cases( 2065d094f9fSJuraj Linkeš self, 2075d094f9fSJuraj Linkeš test_suite_configs: list[TestSuiteConfig], 2085d094f9fSJuraj Linkeš func: bool, 2095d094f9fSJuraj Linkeš perf: bool, 2105d094f9fSJuraj Linkeš ) -> list[TestSuiteWithCases]: 2115d094f9fSJuraj Linkeš """Test suites with test cases discovery. 2125d094f9fSJuraj Linkeš 2135d094f9fSJuraj Linkeš The test suites with test cases defined in the user configuration are discovered 2145d094f9fSJuraj Linkeš and stored for future use so that we don't import the modules twice and so that 2155d094f9fSJuraj Linkeš the list of test suites with test cases is available for recording right away. 2165d094f9fSJuraj Linkeš 2175d094f9fSJuraj Linkeš Args: 2185d094f9fSJuraj Linkeš test_suite_configs: Test suite configurations. 2195d094f9fSJuraj Linkeš func: Whether to include functional test cases in the final list. 2205d094f9fSJuraj Linkeš perf: Whether to include performance test cases in the final list. 2215d094f9fSJuraj Linkeš 2225d094f9fSJuraj Linkeš Returns: 2235d094f9fSJuraj Linkeš The discovered test suites, each with test cases. 2245d094f9fSJuraj Linkeš """ 2255d094f9fSJuraj Linkeš test_suites_with_cases = [] 2265d094f9fSJuraj Linkeš 2275d094f9fSJuraj Linkeš for test_suite_config in test_suite_configs: 2285d094f9fSJuraj Linkeš test_suite_class = self._get_test_suite_class(test_suite_config.test_suite) 2295d094f9fSJuraj Linkeš test_cases = [] 2305d094f9fSJuraj Linkeš func_test_cases, perf_test_cases = self._filter_test_cases( 231*4a4678c7SJuraj Linkeš test_suite_class, test_suite_config.test_cases 2325d094f9fSJuraj Linkeš ) 2335d094f9fSJuraj Linkeš if func: 2345d094f9fSJuraj Linkeš test_cases.extend(func_test_cases) 2355d094f9fSJuraj Linkeš if perf: 2365d094f9fSJuraj Linkeš test_cases.extend(perf_test_cases) 2375d094f9fSJuraj Linkeš 2385d094f9fSJuraj Linkeš test_suites_with_cases.append( 2395d094f9fSJuraj Linkeš TestSuiteWithCases(test_suite_class=test_suite_class, test_cases=test_cases) 2405d094f9fSJuraj Linkeš ) 2415d094f9fSJuraj Linkeš 2425d094f9fSJuraj Linkeš return test_suites_with_cases 2435d094f9fSJuraj Linkeš 2445d094f9fSJuraj Linkeš def _get_test_suite_class(self, module_name: str) -> type[TestSuite]: 2455d094f9fSJuraj Linkeš """Find the :class:`TestSuite` class in `module_name`. 2465d094f9fSJuraj Linkeš 2475d094f9fSJuraj Linkeš The full module name is `module_name` prefixed with `self._test_suite_module_prefix`. 2485d094f9fSJuraj Linkeš The module name is a standard filename with words separated with underscores. 2495d094f9fSJuraj Linkeš Search the `module_name` for a :class:`TestSuite` class which starts 2505d094f9fSJuraj Linkeš with `self._test_suite_class_prefix`, continuing with CamelCase `module_name`. 2515d094f9fSJuraj Linkeš The first matching class is returned. 2525d094f9fSJuraj Linkeš 2535d094f9fSJuraj Linkeš The CamelCase convention applies to abbreviations, acronyms, initialisms and so on:: 2545d094f9fSJuraj Linkeš 2555d094f9fSJuraj Linkeš OS -> Os 2565d094f9fSJuraj Linkeš TCP -> Tcp 2575d094f9fSJuraj Linkeš 2585d094f9fSJuraj Linkeš Args: 2595d094f9fSJuraj Linkeš module_name: The module name without prefix where to search for the test suite. 2605d094f9fSJuraj Linkeš 2615d094f9fSJuraj Linkeš Returns: 2625d094f9fSJuraj Linkeš The found test suite class. 2635d094f9fSJuraj Linkeš 2645d094f9fSJuraj Linkeš Raises: 2655d094f9fSJuraj Linkeš ConfigurationError: If the corresponding module is not found or 2665d094f9fSJuraj Linkeš a valid :class:`TestSuite` is not found in the module. 2675d094f9fSJuraj Linkeš """ 2685d094f9fSJuraj Linkeš 2695d094f9fSJuraj Linkeš def is_test_suite(object) -> bool: 2705d094f9fSJuraj Linkeš """Check whether `object` is a :class:`TestSuite`. 2715d094f9fSJuraj Linkeš 2725d094f9fSJuraj Linkeš The `object` is a subclass of :class:`TestSuite`, but not :class:`TestSuite` itself. 2735d094f9fSJuraj Linkeš 2745d094f9fSJuraj Linkeš Args: 2755d094f9fSJuraj Linkeš object: The object to be checked. 2765d094f9fSJuraj Linkeš 2775d094f9fSJuraj Linkeš Returns: 2785d094f9fSJuraj Linkeš :data:`True` if `object` is a subclass of `TestSuite`. 2795d094f9fSJuraj Linkeš """ 2805d094f9fSJuraj Linkeš try: 2815d094f9fSJuraj Linkeš if issubclass(object, TestSuite) and object is not TestSuite: 2825d094f9fSJuraj Linkeš return True 2835d094f9fSJuraj Linkeš except TypeError: 2845d094f9fSJuraj Linkeš return False 2855d094f9fSJuraj Linkeš return False 2865d094f9fSJuraj Linkeš 2875d094f9fSJuraj Linkeš testsuite_module_path = f"{self._test_suite_module_prefix}{module_name}" 2885d094f9fSJuraj Linkeš try: 2895d094f9fSJuraj Linkeš test_suite_module = importlib.import_module(testsuite_module_path) 2905d094f9fSJuraj Linkeš except ModuleNotFoundError as e: 2915d094f9fSJuraj Linkeš raise ConfigurationError( 2925d094f9fSJuraj Linkeš f"Test suite module '{testsuite_module_path}' not found." 2935d094f9fSJuraj Linkeš ) from e 2945d094f9fSJuraj Linkeš 2955d094f9fSJuraj Linkeš camel_case_suite_name = "".join( 2965d094f9fSJuraj Linkeš [suite_word.capitalize() for suite_word in module_name.split("_")] 2975d094f9fSJuraj Linkeš ) 2985d094f9fSJuraj Linkeš full_suite_name_to_find = f"{self._test_suite_class_prefix}{camel_case_suite_name}" 2995d094f9fSJuraj Linkeš for class_name, class_obj in inspect.getmembers(test_suite_module, is_test_suite): 3005d094f9fSJuraj Linkeš if class_name == full_suite_name_to_find: 3015d094f9fSJuraj Linkeš return class_obj 3025d094f9fSJuraj Linkeš raise ConfigurationError( 3035d094f9fSJuraj Linkeš f"Couldn't find any valid test suites in {test_suite_module.__name__}." 3045d094f9fSJuraj Linkeš ) 3055d094f9fSJuraj Linkeš 3065d094f9fSJuraj Linkeš def _filter_test_cases( 307*4a4678c7SJuraj Linkeš self, test_suite_class: type[TestSuite], test_cases_to_run: Sequence[str] 3085d094f9fSJuraj Linkeš ) -> tuple[list[MethodType], list[MethodType]]: 3095d094f9fSJuraj Linkeš """Filter `test_cases_to_run` from `test_suite_class`. 3105d094f9fSJuraj Linkeš 3115d094f9fSJuraj Linkeš There are two rounds of filtering if `test_cases_to_run` is not empty. 3125d094f9fSJuraj Linkeš The first filters `test_cases_to_run` from all methods of `test_suite_class`. 3135d094f9fSJuraj Linkeš Then the methods are separated into functional and performance test cases. 3145d094f9fSJuraj Linkeš If a method matches neither the functional nor performance name prefix, it's an error. 3155d094f9fSJuraj Linkeš 3165d094f9fSJuraj Linkeš Args: 3175d094f9fSJuraj Linkeš test_suite_class: The class of the test suite. 3185d094f9fSJuraj Linkeš test_cases_to_run: Test case names to filter from `test_suite_class`. 3195d094f9fSJuraj Linkeš If empty, return all matching test cases. 3205d094f9fSJuraj Linkeš 3215d094f9fSJuraj Linkeš Returns: 3225d094f9fSJuraj Linkeš A list of test case methods that should be executed. 3235d094f9fSJuraj Linkeš 3245d094f9fSJuraj Linkeš Raises: 3255d094f9fSJuraj Linkeš ConfigurationError: If a test case from `test_cases_to_run` is not found 3265d094f9fSJuraj Linkeš or it doesn't match either the functional nor performance name prefix. 3275d094f9fSJuraj Linkeš """ 3285d094f9fSJuraj Linkeš func_test_cases = [] 3295d094f9fSJuraj Linkeš perf_test_cases = [] 3305d094f9fSJuraj Linkeš name_method_tuples = inspect.getmembers(test_suite_class, inspect.isfunction) 3315d094f9fSJuraj Linkeš if test_cases_to_run: 3325d094f9fSJuraj Linkeš name_method_tuples = [ 3335d094f9fSJuraj Linkeš (name, method) for name, method in name_method_tuples if name in test_cases_to_run 3345d094f9fSJuraj Linkeš ] 3355d094f9fSJuraj Linkeš if len(name_method_tuples) < len(test_cases_to_run): 336*4a4678c7SJuraj Linkeš missing_test_cases = set(test_cases_to_run) - { 337*4a4678c7SJuraj Linkeš name for name, _ in name_method_tuples 338*4a4678c7SJuraj Linkeš } 3395d094f9fSJuraj Linkeš raise ConfigurationError( 3405d094f9fSJuraj Linkeš f"Test cases {missing_test_cases} not found among methods " 3415d094f9fSJuraj Linkeš f"of {test_suite_class.__name__}." 3425d094f9fSJuraj Linkeš ) 3435d094f9fSJuraj Linkeš 3445d094f9fSJuraj Linkeš for test_case_name, test_case_method in name_method_tuples: 3455d094f9fSJuraj Linkeš if re.match(self._func_test_case_regex, test_case_name): 3465d094f9fSJuraj Linkeš func_test_cases.append(test_case_method) 3475d094f9fSJuraj Linkeš elif re.match(self._perf_test_case_regex, test_case_name): 3485d094f9fSJuraj Linkeš perf_test_cases.append(test_case_method) 3495d094f9fSJuraj Linkeš elif test_cases_to_run: 3505d094f9fSJuraj Linkeš raise ConfigurationError( 3515d094f9fSJuraj Linkeš f"Method '{test_case_name}' matches neither " 3525d094f9fSJuraj Linkeš f"a functional nor a performance test case name." 3535d094f9fSJuraj Linkeš ) 3545d094f9fSJuraj Linkeš 3555d094f9fSJuraj Linkeš return func_test_cases, perf_test_cases 3565d094f9fSJuraj Linkeš 3575d094f9fSJuraj Linkeš def _connect_nodes_and_run_execution( 3585d094f9fSJuraj Linkeš self, 3595d094f9fSJuraj Linkeš sut_nodes: dict[str, SutNode], 3605d094f9fSJuraj Linkeš tg_nodes: dict[str, TGNode], 3615d094f9fSJuraj Linkeš execution: ExecutionConfiguration, 3625d094f9fSJuraj Linkeš execution_result: ExecutionResult, 3635d094f9fSJuraj Linkeš test_suites_with_cases: Iterable[TestSuiteWithCases], 3645d094f9fSJuraj Linkeš ) -> None: 3655d094f9fSJuraj Linkeš """Connect nodes, then continue to run the given execution. 3665d094f9fSJuraj Linkeš 3675d094f9fSJuraj Linkeš Connect the :class:`SutNode` and the :class:`TGNode` of this `execution`. 3685d094f9fSJuraj Linkeš If either has already been connected, it's going to be in either `sut_nodes` or `tg_nodes`, 3695d094f9fSJuraj Linkeš respectively. 3705d094f9fSJuraj Linkeš If not, connect and add the node to the respective `sut_nodes` or `tg_nodes` :class:`dict`. 3715d094f9fSJuraj Linkeš 3725d094f9fSJuraj Linkeš Args: 3735d094f9fSJuraj Linkeš sut_nodes: A dictionary storing connected/to be connected SUT nodes. 3745d094f9fSJuraj Linkeš tg_nodes: A dictionary storing connected/to be connected TG nodes. 3755d094f9fSJuraj Linkeš execution: An execution's test run configuration. 3765d094f9fSJuraj Linkeš execution_result: The execution's result. 3775d094f9fSJuraj Linkeš test_suites_with_cases: The test suites with test cases to run. 3785d094f9fSJuraj Linkeš """ 3795d094f9fSJuraj Linkeš sut_node = sut_nodes.get(execution.system_under_test_node.name) 3805d094f9fSJuraj Linkeš tg_node = tg_nodes.get(execution.traffic_generator_node.name) 3815d094f9fSJuraj Linkeš 3825d094f9fSJuraj Linkeš try: 3835d094f9fSJuraj Linkeš if not sut_node: 3845d094f9fSJuraj Linkeš sut_node = SutNode(execution.system_under_test_node) 3855d094f9fSJuraj Linkeš sut_nodes[sut_node.name] = sut_node 3865d094f9fSJuraj Linkeš if not tg_node: 3875d094f9fSJuraj Linkeš tg_node = TGNode(execution.traffic_generator_node) 3885d094f9fSJuraj Linkeš tg_nodes[tg_node.name] = tg_node 3895d094f9fSJuraj Linkeš except Exception as e: 3905d094f9fSJuraj Linkeš failed_node = execution.system_under_test_node.name 3915d094f9fSJuraj Linkeš if sut_node: 3925d094f9fSJuraj Linkeš failed_node = execution.traffic_generator_node.name 3935d094f9fSJuraj Linkeš self._logger.exception(f"The Creation of node {failed_node} failed.") 3945d094f9fSJuraj Linkeš execution_result.update_setup(Result.FAIL, e) 3955d094f9fSJuraj Linkeš 3965d094f9fSJuraj Linkeš else: 3975d094f9fSJuraj Linkeš self._run_execution( 3985d094f9fSJuraj Linkeš sut_node, tg_node, execution, execution_result, test_suites_with_cases 3995d094f9fSJuraj Linkeš ) 4005d094f9fSJuraj Linkeš 4012f28a4fcSJuraj Linkeš def _run_execution( 4022f28a4fcSJuraj Linkeš self, 4032f28a4fcSJuraj Linkeš sut_node: SutNode, 4042f28a4fcSJuraj Linkeš tg_node: TGNode, 4052f28a4fcSJuraj Linkeš execution: ExecutionConfiguration, 4065d094f9fSJuraj Linkeš execution_result: ExecutionResult, 4075d094f9fSJuraj Linkeš test_suites_with_cases: Iterable[TestSuiteWithCases], 4082f28a4fcSJuraj Linkeš ) -> None: 4092f28a4fcSJuraj Linkeš """Run the given execution. 4102f28a4fcSJuraj Linkeš 4112f28a4fcSJuraj Linkeš This involves running the execution setup as well as running all build targets 4122f28a4fcSJuraj Linkeš in the given execution. After that, execution teardown is run. 4132f28a4fcSJuraj Linkeš 4142f28a4fcSJuraj Linkeš Args: 4152f28a4fcSJuraj Linkeš sut_node: The execution's SUT node. 4162f28a4fcSJuraj Linkeš tg_node: The execution's TG node. 4172f28a4fcSJuraj Linkeš execution: An execution's test run configuration. 4185d094f9fSJuraj Linkeš execution_result: The execution's result. 4195d094f9fSJuraj Linkeš test_suites_with_cases: The test suites with test cases to run. 4202f28a4fcSJuraj Linkeš """ 4212f28a4fcSJuraj Linkeš self._logger.info(f"Running execution with SUT '{execution.system_under_test_node.name}'.") 4222f28a4fcSJuraj Linkeš execution_result.add_sut_info(sut_node.node_info) 4232f28a4fcSJuraj Linkeš try: 4242f28a4fcSJuraj Linkeš sut_node.set_up_execution(execution) 4252f28a4fcSJuraj Linkeš execution_result.update_setup(Result.PASS) 4262f28a4fcSJuraj Linkeš except Exception as e: 4272f28a4fcSJuraj Linkeš self._logger.exception("Execution setup failed.") 4282f28a4fcSJuraj Linkeš execution_result.update_setup(Result.FAIL, e) 4292f28a4fcSJuraj Linkeš 4302f28a4fcSJuraj Linkeš else: 4312f28a4fcSJuraj Linkeš for build_target in execution.build_targets: 4325d094f9fSJuraj Linkeš build_target_result = execution_result.add_build_target(build_target) 4335d094f9fSJuraj Linkeš self._run_build_target( 4345d094f9fSJuraj Linkeš sut_node, tg_node, build_target, build_target_result, test_suites_with_cases 4355d094f9fSJuraj Linkeš ) 4362f28a4fcSJuraj Linkeš 4372f28a4fcSJuraj Linkeš finally: 4382f28a4fcSJuraj Linkeš try: 43904f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.execution_teardown) 4402f28a4fcSJuraj Linkeš sut_node.tear_down_execution() 4412f28a4fcSJuraj Linkeš execution_result.update_teardown(Result.PASS) 4422f28a4fcSJuraj Linkeš except Exception as e: 4432f28a4fcSJuraj Linkeš self._logger.exception("Execution teardown failed.") 4442f28a4fcSJuraj Linkeš execution_result.update_teardown(Result.FAIL, e) 4452f28a4fcSJuraj Linkeš 4462f28a4fcSJuraj Linkeš def _run_build_target( 4472f28a4fcSJuraj Linkeš self, 4482f28a4fcSJuraj Linkeš sut_node: SutNode, 4492f28a4fcSJuraj Linkeš tg_node: TGNode, 4502f28a4fcSJuraj Linkeš build_target: BuildTargetConfiguration, 4515d094f9fSJuraj Linkeš build_target_result: BuildTargetResult, 4525d094f9fSJuraj Linkeš test_suites_with_cases: Iterable[TestSuiteWithCases], 4532f28a4fcSJuraj Linkeš ) -> None: 4542f28a4fcSJuraj Linkeš """Run the given build target. 4552f28a4fcSJuraj Linkeš 4562f28a4fcSJuraj Linkeš This involves running the build target setup as well as running all test suites 4572f28a4fcSJuraj Linkeš of the build target's execution. 4582f28a4fcSJuraj Linkeš After that, build target teardown is run. 4592f28a4fcSJuraj Linkeš 4602f28a4fcSJuraj Linkeš Args: 4612f28a4fcSJuraj Linkeš sut_node: The execution's sut node. 4622f28a4fcSJuraj Linkeš tg_node: The execution's tg node. 4632f28a4fcSJuraj Linkeš build_target: A build target's test run configuration. 4645d094f9fSJuraj Linkeš build_target_result: The build target level result object associated 4655d094f9fSJuraj Linkeš with the current build target. 4665d094f9fSJuraj Linkeš test_suites_with_cases: The test suites with test cases to run. 4672f28a4fcSJuraj Linkeš """ 46804f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.build_target_setup) 4692f28a4fcSJuraj Linkeš self._logger.info(f"Running build target '{build_target.name}'.") 4702f28a4fcSJuraj Linkeš 4712f28a4fcSJuraj Linkeš try: 4722f28a4fcSJuraj Linkeš sut_node.set_up_build_target(build_target) 4732f28a4fcSJuraj Linkeš self._result.dpdk_version = sut_node.dpdk_version 4742f28a4fcSJuraj Linkeš build_target_result.add_build_target_info(sut_node.get_build_target_info()) 4752f28a4fcSJuraj Linkeš build_target_result.update_setup(Result.PASS) 4762f28a4fcSJuraj Linkeš except Exception as e: 4772f28a4fcSJuraj Linkeš self._logger.exception("Build target setup failed.") 4782f28a4fcSJuraj Linkeš build_target_result.update_setup(Result.FAIL, e) 4792f28a4fcSJuraj Linkeš 4802f28a4fcSJuraj Linkeš else: 4815d094f9fSJuraj Linkeš self._run_test_suites(sut_node, tg_node, build_target_result, test_suites_with_cases) 4822f28a4fcSJuraj Linkeš 4832f28a4fcSJuraj Linkeš finally: 4842f28a4fcSJuraj Linkeš try: 48504f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.build_target_teardown) 4862f28a4fcSJuraj Linkeš sut_node.tear_down_build_target() 4872f28a4fcSJuraj Linkeš build_target_result.update_teardown(Result.PASS) 4882f28a4fcSJuraj Linkeš except Exception as e: 4892f28a4fcSJuraj Linkeš self._logger.exception("Build target teardown failed.") 4902f28a4fcSJuraj Linkeš build_target_result.update_teardown(Result.FAIL, e) 4912f28a4fcSJuraj Linkeš 4925c7b6207SJuraj Linkeš def _run_test_suites( 4932f28a4fcSJuraj Linkeš self, 4942f28a4fcSJuraj Linkeš sut_node: SutNode, 4952f28a4fcSJuraj Linkeš tg_node: TGNode, 4962f28a4fcSJuraj Linkeš build_target_result: BuildTargetResult, 4975d094f9fSJuraj Linkeš test_suites_with_cases: Iterable[TestSuiteWithCases], 4982f28a4fcSJuraj Linkeš ) -> None: 4995d094f9fSJuraj Linkeš """Run `test_suites_with_cases` with the current build target. 5002f28a4fcSJuraj Linkeš 5012f28a4fcSJuraj Linkeš The method assumes the build target we're testing has already been built on the SUT node. 5022f28a4fcSJuraj Linkeš The current build target thus corresponds to the current DPDK build present on the SUT node. 5032f28a4fcSJuraj Linkeš 5045c7b6207SJuraj Linkeš If a blocking test suite (such as the smoke test suite) fails, the rest of the test suites 5055c7b6207SJuraj Linkeš in the current build target won't be executed. 5065c7b6207SJuraj Linkeš 5072f28a4fcSJuraj Linkeš Args: 5082f28a4fcSJuraj Linkeš sut_node: The execution's SUT node. 5092f28a4fcSJuraj Linkeš tg_node: The execution's TG node. 5102f28a4fcSJuraj Linkeš build_target_result: The build target level result object associated 5112f28a4fcSJuraj Linkeš with the current build target. 5125d094f9fSJuraj Linkeš test_suites_with_cases: The test suites with test cases to run. 5132f28a4fcSJuraj Linkeš """ 5142f28a4fcSJuraj Linkeš end_build_target = False 5155d094f9fSJuraj Linkeš for test_suite_with_cases in test_suites_with_cases: 516caae1889SJuraj Linkeš test_suite_result = build_target_result.add_test_suite(test_suite_with_cases) 5175d094f9fSJuraj Linkeš try: 5185d094f9fSJuraj Linkeš self._run_test_suite(sut_node, tg_node, test_suite_result, test_suite_with_cases) 5192f28a4fcSJuraj Linkeš except BlockingTestSuiteError as e: 5202f28a4fcSJuraj Linkeš self._logger.exception( 5215d094f9fSJuraj Linkeš f"An error occurred within {test_suite_with_cases.test_suite_class.__name__}. " 5222f28a4fcSJuraj Linkeš "Skipping build target..." 5232f28a4fcSJuraj Linkeš ) 5242f28a4fcSJuraj Linkeš self._result.add_error(e) 5252f28a4fcSJuraj Linkeš end_build_target = True 5262f28a4fcSJuraj Linkeš # if a blocking test failed and we need to bail out of suite executions 5272f28a4fcSJuraj Linkeš if end_build_target: 5282f28a4fcSJuraj Linkeš break 5292f28a4fcSJuraj Linkeš 5305d094f9fSJuraj Linkeš def _run_test_suite( 5312f28a4fcSJuraj Linkeš self, 5322f28a4fcSJuraj Linkeš sut_node: SutNode, 5332f28a4fcSJuraj Linkeš tg_node: TGNode, 5345d094f9fSJuraj Linkeš test_suite_result: TestSuiteResult, 5355d094f9fSJuraj Linkeš test_suite_with_cases: TestSuiteWithCases, 5362f28a4fcSJuraj Linkeš ) -> None: 5375d094f9fSJuraj Linkeš """Set up, execute and tear down `test_suite_with_cases`. 5382f28a4fcSJuraj Linkeš 5392f28a4fcSJuraj Linkeš The method assumes the build target we're testing has already been built on the SUT node. 5402f28a4fcSJuraj Linkeš The current build target thus corresponds to the current DPDK build present on the SUT node. 5412f28a4fcSJuraj Linkeš 5425c7b6207SJuraj Linkeš Test suite execution consists of running the discovered test cases. 5435c7b6207SJuraj Linkeš A test case run consists of setup, execution and teardown of said test case. 5445c7b6207SJuraj Linkeš 5455c7b6207SJuraj Linkeš Record the setup and the teardown and handle failures. 5465c7b6207SJuraj Linkeš 5472f28a4fcSJuraj Linkeš Args: 5482f28a4fcSJuraj Linkeš sut_node: The execution's SUT node. 5492f28a4fcSJuraj Linkeš tg_node: The execution's TG node. 5505d094f9fSJuraj Linkeš test_suite_result: The test suite level result object associated 5515d094f9fSJuraj Linkeš with the current test suite. 5525d094f9fSJuraj Linkeš test_suite_with_cases: The test suite with test cases to run. 5532f28a4fcSJuraj Linkeš 5542f28a4fcSJuraj Linkeš Raises: 5552f28a4fcSJuraj Linkeš BlockingTestSuiteError: If a blocking test suite fails. 5562f28a4fcSJuraj Linkeš """ 5575d094f9fSJuraj Linkeš test_suite_name = test_suite_with_cases.test_suite_class.__name__ 55804f5a5a6SJuraj Linkeš self._logger.set_stage( 55904f5a5a6SJuraj Linkeš DtsStage.test_suite_setup, Path(SETTINGS.output_dir, test_suite_name) 56004f5a5a6SJuraj Linkeš ) 5615d094f9fSJuraj Linkeš test_suite = test_suite_with_cases.test_suite_class(sut_node, tg_node) 5625c7b6207SJuraj Linkeš try: 5635c7b6207SJuraj Linkeš self._logger.info(f"Starting test suite setup: {test_suite_name}") 5645c7b6207SJuraj Linkeš test_suite.set_up_suite() 5655c7b6207SJuraj Linkeš test_suite_result.update_setup(Result.PASS) 5665c7b6207SJuraj Linkeš self._logger.info(f"Test suite setup successful: {test_suite_name}") 5675c7b6207SJuraj Linkeš except Exception as e: 5685c7b6207SJuraj Linkeš self._logger.exception(f"Test suite setup ERROR: {test_suite_name}") 5695c7b6207SJuraj Linkeš test_suite_result.update_setup(Result.ERROR, e) 5705c7b6207SJuraj Linkeš 5715c7b6207SJuraj Linkeš else: 5725d094f9fSJuraj Linkeš self._execute_test_suite( 5735d094f9fSJuraj Linkeš test_suite, 5745d094f9fSJuraj Linkeš test_suite_with_cases.test_cases, 5755d094f9fSJuraj Linkeš test_suite_result, 5765d094f9fSJuraj Linkeš ) 5775c7b6207SJuraj Linkeš finally: 5785c7b6207SJuraj Linkeš try: 57904f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.test_suite_teardown) 5805c7b6207SJuraj Linkeš test_suite.tear_down_suite() 5815c7b6207SJuraj Linkeš sut_node.kill_cleanup_dpdk_apps() 5825c7b6207SJuraj Linkeš test_suite_result.update_teardown(Result.PASS) 5835c7b6207SJuraj Linkeš except Exception as e: 5845c7b6207SJuraj Linkeš self._logger.exception(f"Test suite teardown ERROR: {test_suite_name}") 5855c7b6207SJuraj Linkeš self._logger.warning( 5865c7b6207SJuraj Linkeš f"Test suite '{test_suite_name}' teardown failed, " 5875d094f9fSJuraj Linkeš "the next test suite may be affected." 5882f28a4fcSJuraj Linkeš ) 5895c7b6207SJuraj Linkeš test_suite_result.update_setup(Result.ERROR, e) 5905c7b6207SJuraj Linkeš if len(test_suite_result.get_errors()) > 0 and test_suite.is_blocking: 5915c7b6207SJuraj Linkeš raise BlockingTestSuiteError(test_suite_name) 5925c7b6207SJuraj Linkeš 5935c7b6207SJuraj Linkeš def _execute_test_suite( 5945d094f9fSJuraj Linkeš self, 5955d094f9fSJuraj Linkeš test_suite: TestSuite, 5965d094f9fSJuraj Linkeš test_cases: Iterable[MethodType], 5975d094f9fSJuraj Linkeš test_suite_result: TestSuiteResult, 5985c7b6207SJuraj Linkeš ) -> None: 5995d094f9fSJuraj Linkeš """Execute all `test_cases` in `test_suite`. 6005c7b6207SJuraj Linkeš 6015c7b6207SJuraj Linkeš If the :option:`--re-run` command line argument or the :envvar:`DTS_RERUN` environment 6025c7b6207SJuraj Linkeš variable is set, in case of a test case failure, the test case will be executed again 6035c7b6207SJuraj Linkeš until it passes or it fails that many times in addition of the first failure. 6045c7b6207SJuraj Linkeš 6055c7b6207SJuraj Linkeš Args: 6065c7b6207SJuraj Linkeš test_suite: The test suite object. 6075d094f9fSJuraj Linkeš test_cases: The list of test case methods. 6085c7b6207SJuraj Linkeš test_suite_result: The test suite level result object associated 6095c7b6207SJuraj Linkeš with the current test suite. 6105c7b6207SJuraj Linkeš """ 61104f5a5a6SJuraj Linkeš self._logger.set_stage(DtsStage.test_suite) 6125d094f9fSJuraj Linkeš for test_case_method in test_cases: 6135c7b6207SJuraj Linkeš test_case_name = test_case_method.__name__ 6145c7b6207SJuraj Linkeš test_case_result = test_suite_result.add_test_case(test_case_name) 6155c7b6207SJuraj Linkeš all_attempts = SETTINGS.re_run + 1 6165c7b6207SJuraj Linkeš attempt_nr = 1 6175c7b6207SJuraj Linkeš self._run_test_case(test_suite, test_case_method, test_case_result) 6185c7b6207SJuraj Linkeš while not test_case_result and attempt_nr < all_attempts: 6195c7b6207SJuraj Linkeš attempt_nr += 1 6205c7b6207SJuraj Linkeš self._logger.info( 6215c7b6207SJuraj Linkeš f"Re-running FAILED test case '{test_case_name}'. " 6225c7b6207SJuraj Linkeš f"Attempt number {attempt_nr} out of {all_attempts}." 6235c7b6207SJuraj Linkeš ) 6245c7b6207SJuraj Linkeš self._run_test_case(test_suite, test_case_method, test_case_result) 6255c7b6207SJuraj Linkeš 6265c7b6207SJuraj Linkeš def _run_test_case( 6275c7b6207SJuraj Linkeš self, 6285c7b6207SJuraj Linkeš test_suite: TestSuite, 6295c7b6207SJuraj Linkeš test_case_method: MethodType, 6305c7b6207SJuraj Linkeš test_case_result: TestCaseResult, 6315c7b6207SJuraj Linkeš ) -> None: 6325d094f9fSJuraj Linkeš """Setup, execute and teardown `test_case_method` from `test_suite`. 6335c7b6207SJuraj Linkeš 6345c7b6207SJuraj Linkeš Record the result of the setup and the teardown and handle failures. 6355c7b6207SJuraj Linkeš 6365c7b6207SJuraj Linkeš Args: 6375c7b6207SJuraj Linkeš test_suite: The test suite object. 6385c7b6207SJuraj Linkeš test_case_method: The test case method. 6395c7b6207SJuraj Linkeš test_case_result: The test case level result object associated 6405c7b6207SJuraj Linkeš with the current test case. 6415c7b6207SJuraj Linkeš """ 6425c7b6207SJuraj Linkeš test_case_name = test_case_method.__name__ 6435c7b6207SJuraj Linkeš 6445c7b6207SJuraj Linkeš try: 6455c7b6207SJuraj Linkeš # run set_up function for each case 6465c7b6207SJuraj Linkeš test_suite.set_up_test_case() 6475c7b6207SJuraj Linkeš test_case_result.update_setup(Result.PASS) 6485c7b6207SJuraj Linkeš except SSHTimeoutError as e: 6495c7b6207SJuraj Linkeš self._logger.exception(f"Test case setup FAILED: {test_case_name}") 6505c7b6207SJuraj Linkeš test_case_result.update_setup(Result.FAIL, e) 6515c7b6207SJuraj Linkeš except Exception as e: 6525c7b6207SJuraj Linkeš self._logger.exception(f"Test case setup ERROR: {test_case_name}") 6535c7b6207SJuraj Linkeš test_case_result.update_setup(Result.ERROR, e) 6545c7b6207SJuraj Linkeš 6555c7b6207SJuraj Linkeš else: 6565c7b6207SJuraj Linkeš # run test case if setup was successful 6575d094f9fSJuraj Linkeš self._execute_test_case(test_suite, test_case_method, test_case_result) 6585c7b6207SJuraj Linkeš 6595c7b6207SJuraj Linkeš finally: 6605c7b6207SJuraj Linkeš try: 6615c7b6207SJuraj Linkeš test_suite.tear_down_test_case() 6625c7b6207SJuraj Linkeš test_case_result.update_teardown(Result.PASS) 6635c7b6207SJuraj Linkeš except Exception as e: 6645c7b6207SJuraj Linkeš self._logger.exception(f"Test case teardown ERROR: {test_case_name}") 6655c7b6207SJuraj Linkeš self._logger.warning( 6665c7b6207SJuraj Linkeš f"Test case '{test_case_name}' teardown failed, " 6675c7b6207SJuraj Linkeš f"the next test case may be affected." 6685c7b6207SJuraj Linkeš ) 6695c7b6207SJuraj Linkeš test_case_result.update_teardown(Result.ERROR, e) 6705c7b6207SJuraj Linkeš test_case_result.update(Result.ERROR) 6715c7b6207SJuraj Linkeš 6725c7b6207SJuraj Linkeš def _execute_test_case( 6735d094f9fSJuraj Linkeš self, 6745d094f9fSJuraj Linkeš test_suite: TestSuite, 6755d094f9fSJuraj Linkeš test_case_method: MethodType, 6765d094f9fSJuraj Linkeš test_case_result: TestCaseResult, 6775c7b6207SJuraj Linkeš ) -> None: 6785d094f9fSJuraj Linkeš """Execute `test_case_method` from `test_suite`, record the result and handle failures. 6795c7b6207SJuraj Linkeš 6805c7b6207SJuraj Linkeš Args: 6815d094f9fSJuraj Linkeš test_suite: The test suite object. 6825c7b6207SJuraj Linkeš test_case_method: The test case method. 6835c7b6207SJuraj Linkeš test_case_result: The test case level result object associated 6845c7b6207SJuraj Linkeš with the current test case. 6855c7b6207SJuraj Linkeš """ 6865c7b6207SJuraj Linkeš test_case_name = test_case_method.__name__ 6875c7b6207SJuraj Linkeš try: 6885c7b6207SJuraj Linkeš self._logger.info(f"Starting test case execution: {test_case_name}") 6895d094f9fSJuraj Linkeš test_case_method(test_suite) 6905c7b6207SJuraj Linkeš test_case_result.update(Result.PASS) 6915c7b6207SJuraj Linkeš self._logger.info(f"Test case execution PASSED: {test_case_name}") 6925c7b6207SJuraj Linkeš 6935c7b6207SJuraj Linkeš except TestCaseVerifyError as e: 6945c7b6207SJuraj Linkeš self._logger.exception(f"Test case execution FAILED: {test_case_name}") 6955c7b6207SJuraj Linkeš test_case_result.update(Result.FAIL, e) 6965c7b6207SJuraj Linkeš except Exception as e: 6975c7b6207SJuraj Linkeš self._logger.exception(f"Test case execution ERROR: {test_case_name}") 6985c7b6207SJuraj Linkeš test_case_result.update(Result.ERROR, e) 6995c7b6207SJuraj Linkeš except KeyboardInterrupt: 7005c7b6207SJuraj Linkeš self._logger.error(f"Test case execution INTERRUPTED by user: {test_case_name}") 7015c7b6207SJuraj Linkeš test_case_result.update(Result.SKIP) 7025c7b6207SJuraj Linkeš raise KeyboardInterrupt("Stop DTS") 7032f28a4fcSJuraj Linkeš 7042f28a4fcSJuraj Linkeš def _exit_dts(self) -> None: 7052f28a4fcSJuraj Linkeš """Process all errors and exit with the proper exit code.""" 7062f28a4fcSJuraj Linkeš self._result.process() 7072f28a4fcSJuraj Linkeš 7082f28a4fcSJuraj Linkeš if self._logger: 7092f28a4fcSJuraj Linkeš self._logger.info("DTS execution has ended.") 7102f28a4fcSJuraj Linkeš 7112f28a4fcSJuraj Linkeš sys.exit(self._result.get_return_code()) 712