xref: /dpdk/dts/framework/runner.py (revision 4a4678c76def68a9e81a6acd340eb9a567de964c)
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