xref: /dpdk/dts/framework/runner.py (revision 9f8a257235ac6d7ed5b621428551c88b4408ac26)
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
55ea59af4SLuca Vizzarro# Copyright(c) 2024 Arm Limited
62f28a4fcSJuraj Linkeš
72f28a4fcSJuraj Linkeš"""Test suite runner module.
82f28a4fcSJuraj Linkeš
92f28a4fcSJuraj LinkešThe module is responsible for running DTS in a series of stages:
102f28a4fcSJuraj Linkeš
1185ceeeceSJuraj Linkeš    #. Test run stage,
12ecaff610STomáš Ďurovec    #. DPDK build stage,
132f28a4fcSJuraj Linkeš    #. Test suite stage,
142f28a4fcSJuraj Linkeš    #. Test case stage.
152f28a4fcSJuraj Linkeš
1611b2279aSTomáš ĎurovecThe test run stage sets up the environment before running test suites.
172f28a4fcSJuraj LinkešThe test suite stage sets up steps common to all test cases
182f28a4fcSJuraj Linkešand the test case stage runs test cases individually.
192f28a4fcSJuraj Linkeš"""
202f28a4fcSJuraj Linkeš
2104f5a5a6SJuraj Linkešimport os
22cfe40bacSLuca Vizzarroimport random
232f28a4fcSJuraj Linkešimport sys
2404f5a5a6SJuraj Linkešfrom pathlib import Path
25b6eb5004SJuraj Linkešfrom types import MethodType
26b6eb5004SJuraj Linkešfrom typing import Iterable
272f28a4fcSJuraj Linkeš
28eebfb5bbSJuraj Linkešfrom framework.testbed_model.capability import Capability, get_supported_capabilities
292b2f5a8aSLuca Vizzarrofrom framework.testbed_model.sut_node import SutNode
302b2f5a8aSLuca Vizzarrofrom framework.testbed_model.tg_node import TGNode
312b2f5a8aSLuca Vizzarro
32b935bdc3SLuca Vizzarrofrom .config import (
33b935bdc3SLuca Vizzarro    Configuration,
34b935bdc3SLuca Vizzarro    DPDKPrecompiledBuildConfiguration,
35b935bdc3SLuca Vizzarro    SutNodeConfiguration,
36b935bdc3SLuca Vizzarro    TestRunConfiguration,
37b935bdc3SLuca Vizzarro    TestSuiteConfig,
38b935bdc3SLuca Vizzarro    TGNodeConfiguration,
39b935bdc3SLuca Vizzarro    load_config,
40b935bdc3SLuca Vizzarro)
415ea59af4SLuca Vizzarrofrom .exception import BlockingTestSuiteError, SSHTimeoutError, TestCaseVerifyError
4204f5a5a6SJuraj Linkešfrom .logger import DTSLogger, DtsStage, get_dts_logger
435c7b6207SJuraj Linkešfrom .settings import SETTINGS
445c7b6207SJuraj Linkešfrom .test_result import (
455c7b6207SJuraj Linkeš    DTSResult,
465c7b6207SJuraj Linkeš    Result,
475c7b6207SJuraj Linkeš    TestCaseResult,
4885ceeeceSJuraj Linkeš    TestRunResult,
495c7b6207SJuraj Linkeš    TestSuiteResult,
505d094f9fSJuraj Linkeš    TestSuiteWithCases,
515c7b6207SJuraj Linkeš)
52b6eb5004SJuraj Linkešfrom .test_suite import TestCase, TestSuite
530b5ee16dSJuraj Linkešfrom .testbed_model.topology import Topology
542f28a4fcSJuraj Linkeš
552f28a4fcSJuraj Linkeš
562f28a4fcSJuraj Linkešclass DTSRunner:
572f28a4fcSJuraj Linkeš    r"""Test suite runner class.
582f28a4fcSJuraj Linkeš
592f28a4fcSJuraj Linkeš    The class is responsible for running tests on testbeds defined in the test run configuration.
602f28a4fcSJuraj Linkeš    Each setup or teardown of each stage is recorded in a :class:`~framework.test_result.DTSResult`
612f28a4fcSJuraj Linkeš    or one of its subclasses. The test case results are also recorded.
622f28a4fcSJuraj Linkeš
63caae1889SJuraj Linkeš    If an error occurs, the current stage is aborted, the error is recorded, everything in
64caae1889SJuraj Linkeš    the inner stages is marked as blocked and the run continues in the next iteration
65caae1889SJuraj Linkeš    of the same stage. The return code is the highest `severity` of all
662f28a4fcSJuraj Linkeš    :class:`~.framework.exception.DTSError`\s.
672f28a4fcSJuraj Linkeš
682f28a4fcSJuraj Linkeš    Example:
6911b2279aSTomáš Ďurovec        An error occurs in a test suite setup. The current test suite is aborted,
7011b2279aSTomáš Ďurovec        all its test cases are marked as blocked and the run continues
7111b2279aSTomáš Ďurovec        with the next test suite. If the errored test suite was the last one in the
7285ceeeceSJuraj Linkeš        given test run, the next test run begins.
732f28a4fcSJuraj Linkeš    """
742f28a4fcSJuraj Linkeš
755d094f9fSJuraj Linkeš    _configuration: Configuration
7604f5a5a6SJuraj Linkeš    _logger: DTSLogger
772f28a4fcSJuraj Linkeš    _result: DTSResult
785d094f9fSJuraj Linkeš    _test_suite_class_prefix: str
795d094f9fSJuraj Linkeš    _test_suite_module_prefix: str
805d094f9fSJuraj Linkeš    _func_test_case_regex: str
815d094f9fSJuraj Linkeš    _perf_test_case_regex: str
822f28a4fcSJuraj Linkeš
832f28a4fcSJuraj Linkeš    def __init__(self):
845d094f9fSJuraj Linkeš        """Initialize the instance with configuration, logger, result and string constants."""
854a4678c7SJuraj Linkeš        self._configuration = load_config(SETTINGS.config_file_path)
8604f5a5a6SJuraj Linkeš        self._logger = get_dts_logger()
8704f5a5a6SJuraj Linkeš        if not os.path.exists(SETTINGS.output_dir):
8804f5a5a6SJuraj Linkeš            os.makedirs(SETTINGS.output_dir)
8904f5a5a6SJuraj Linkeš        self._logger.add_dts_root_logger_handlers(SETTINGS.verbose, SETTINGS.output_dir)
90*9f8a2572STomáš Ďurovec        self._result = DTSResult(SETTINGS.output_dir, self._logger)
915d094f9fSJuraj Linkeš        self._test_suite_class_prefix = "Test"
925d094f9fSJuraj Linkeš        self._test_suite_module_prefix = "tests.TestSuite_"
935d094f9fSJuraj Linkeš        self._func_test_case_regex = r"test_(?!perf_)"
945d094f9fSJuraj Linkeš        self._perf_test_case_regex = r"test_perf_"
952f28a4fcSJuraj Linkeš
96282688eaSLuca Vizzarro    def run(self) -> None:
9711b2279aSTomáš Ďurovec        """Run all test runs from the test run configuration.
982f28a4fcSJuraj Linkeš
9911b2279aSTomáš Ďurovec        Before running test suites, test runs are first set up.
10011b2279aSTomáš Ďurovec        The test runs defined in the test run configuration are iterated over.
10111b2279aSTomáš Ďurovec        The test runs define which tests to run and where to run them.
1022f28a4fcSJuraj Linkeš
10311b2279aSTomáš Ďurovec        The test suites are set up for each test run and each discovered
1042f28a4fcSJuraj Linkeš        test case within the test suite is set up, executed and torn down. After all test cases
10511b2279aSTomáš Ďurovec        have been executed, the test suite is torn down and the next test suite will be run. Once
10611b2279aSTomáš Ďurovec        all test suites have been run, the next test run will be tested.
1072f28a4fcSJuraj Linkeš
108caae1889SJuraj Linkeš        In order to properly mark test suites and test cases as blocked in case of a failure,
109caae1889SJuraj Linkeš        we need to have discovered which test suites and test cases to run before any failures
11085ceeeceSJuraj Linkeš        happen. The discovery happens at the earliest point at the start of each test run.
111caae1889SJuraj Linkeš
1122f28a4fcSJuraj Linkeš        All the nested steps look like this:
1132f28a4fcSJuraj Linkeš
11485ceeeceSJuraj Linkeš            #. Test run setup
1152f28a4fcSJuraj Linkeš
1162f28a4fcSJuraj Linkeš                #. Test suite setup
1172f28a4fcSJuraj Linkeš
1182f28a4fcSJuraj Linkeš                    #. Test case setup
1192f28a4fcSJuraj Linkeš                    #. Test case logic
1202f28a4fcSJuraj Linkeš                    #. Test case teardown
1212f28a4fcSJuraj Linkeš
1222f28a4fcSJuraj Linkeš                #. Test suite teardown
1232f28a4fcSJuraj Linkeš
12485ceeeceSJuraj Linkeš            #. Test run teardown
1252f28a4fcSJuraj Linkeš
1262f28a4fcSJuraj Linkeš        The test cases are filtered according to the specification in the test run configuration and
1274a4678c7SJuraj Linkeš        the :option:`--test-suite` command line argument or
1282f28a4fcSJuraj Linkeš        the :envvar:`DTS_TESTCASES` environment variable.
1292f28a4fcSJuraj Linkeš        """
1302f28a4fcSJuraj Linkeš        sut_nodes: dict[str, SutNode] = {}
1312f28a4fcSJuraj Linkeš        tg_nodes: dict[str, TGNode] = {}
1322f28a4fcSJuraj Linkeš        try:
1332f28a4fcSJuraj Linkeš            # check the python version of the server that runs dts
1342f28a4fcSJuraj Linkeš            self._check_dts_python_version()
1355d094f9fSJuraj Linkeš            self._result.update_setup(Result.PASS)
1362f28a4fcSJuraj Linkeš
13785ceeeceSJuraj Linkeš            # for all test run sections
138b935bdc3SLuca Vizzarro            for test_run_with_nodes_config in self._configuration.test_runs_with_nodes:
139b935bdc3SLuca Vizzarro                test_run_config, sut_node_config, tg_node_config = test_run_with_nodes_config
14085ceeeceSJuraj Linkeš                self._logger.set_stage(DtsStage.test_run_setup)
141b935bdc3SLuca Vizzarro                self._logger.info(f"Running test run with SUT '{sut_node_config.name}'.")
142cfe40bacSLuca Vizzarro                self._init_random_seed(test_run_config)
14385ceeeceSJuraj Linkeš                test_run_result = self._result.add_test_run(test_run_config)
1445d094f9fSJuraj Linkeš                # we don't want to modify the original config, so create a copy
14585ceeeceSJuraj Linkeš                test_run_test_suites = list(
14685ceeeceSJuraj Linkeš                    SETTINGS.test_suites if SETTINGS.test_suites else test_run_config.test_suites
1474a4678c7SJuraj Linkeš                )
14885ceeeceSJuraj Linkeš                if not test_run_config.skip_smoke_tests:
149b935bdc3SLuca Vizzarro                    test_run_test_suites[:0] = [TestSuiteConfig(test_suite="smoke_tests")]
1502f28a4fcSJuraj Linkeš                try:
1515d094f9fSJuraj Linkeš                    test_suites_with_cases = self._get_test_suites_with_cases(
15285ceeeceSJuraj Linkeš                        test_run_test_suites, test_run_config.func, test_run_config.perf
1535d094f9fSJuraj Linkeš                    )
15485ceeeceSJuraj Linkeš                    test_run_result.test_suites_with_cases = test_suites_with_cases
1552f28a4fcSJuraj Linkeš                except Exception as e:
1565d094f9fSJuraj Linkeš                    self._logger.exception(
15785ceeeceSJuraj Linkeš                        f"Invalid test suite configuration found: " f"{test_run_test_suites}."
1585d094f9fSJuraj Linkeš                    )
15985ceeeceSJuraj Linkeš                    test_run_result.update_setup(Result.FAIL, e)
1602f28a4fcSJuraj Linkeš
1612f28a4fcSJuraj Linkeš                else:
16285ceeeceSJuraj Linkeš                    self._connect_nodes_and_run_test_run(
16385ceeeceSJuraj Linkeš                        sut_nodes,
16485ceeeceSJuraj Linkeš                        tg_nodes,
165b935bdc3SLuca Vizzarro                        sut_node_config,
166b935bdc3SLuca Vizzarro                        tg_node_config,
16785ceeeceSJuraj Linkeš                        test_run_config,
16885ceeeceSJuraj Linkeš                        test_run_result,
16985ceeeceSJuraj Linkeš                        test_suites_with_cases,
1705d094f9fSJuraj Linkeš                    )
1712f28a4fcSJuraj Linkeš
1722f28a4fcSJuraj Linkeš        except Exception as e:
1732f28a4fcSJuraj Linkeš            self._logger.exception("An unexpected error has occurred.")
1742f28a4fcSJuraj Linkeš            self._result.add_error(e)
1752f28a4fcSJuraj Linkeš            raise
1762f28a4fcSJuraj Linkeš
1772f28a4fcSJuraj Linkeš        finally:
1782f28a4fcSJuraj Linkeš            try:
17985ceeeceSJuraj Linkeš                self._logger.set_stage(DtsStage.post_run)
1802f28a4fcSJuraj Linkeš                for node in (sut_nodes | tg_nodes).values():
1812f28a4fcSJuraj Linkeš                    node.close()
1822f28a4fcSJuraj Linkeš                self._result.update_teardown(Result.PASS)
1832f28a4fcSJuraj Linkeš            except Exception as e:
1842f28a4fcSJuraj Linkeš                self._logger.exception("The final cleanup of nodes failed.")
1852f28a4fcSJuraj Linkeš                self._result.update_teardown(Result.ERROR, e)
1862f28a4fcSJuraj Linkeš
1872f28a4fcSJuraj Linkeš        # we need to put the sys.exit call outside the finally clause to make sure
1882f28a4fcSJuraj Linkeš        # that unexpected exceptions will propagate
1892f28a4fcSJuraj Linkeš        # in that case, the error that should be reported is the uncaught exception as
1902f28a4fcSJuraj Linkeš        # that is a severe error originating from the framework
1912f28a4fcSJuraj Linkeš        # at that point, we'll only have partial results which could be impacted by the
1922f28a4fcSJuraj Linkeš        # error causing the uncaught exception, making them uninterpretable
1932f28a4fcSJuraj Linkeš        self._exit_dts()
1942f28a4fcSJuraj Linkeš
1952f28a4fcSJuraj Linkeš    def _check_dts_python_version(self) -> None:
1962f28a4fcSJuraj Linkeš        """Check the required Python version - v3.10."""
1972f28a4fcSJuraj Linkeš        if sys.version_info.major < 3 or (
1982f28a4fcSJuraj Linkeš            sys.version_info.major == 3 and sys.version_info.minor < 10
1992f28a4fcSJuraj Linkeš        ):
2002f28a4fcSJuraj Linkeš            self._logger.warning(
2012f28a4fcSJuraj Linkeš                "DTS execution node's python version is lower than Python 3.10, "
2022f28a4fcSJuraj Linkeš                "is deprecated and will not work in future releases."
2032f28a4fcSJuraj Linkeš            )
2042f28a4fcSJuraj Linkeš            self._logger.warning("Please use Python >= 3.10 instead.")
2052f28a4fcSJuraj Linkeš
2065d094f9fSJuraj Linkeš    def _get_test_suites_with_cases(
2075d094f9fSJuraj Linkeš        self,
2085d094f9fSJuraj Linkeš        test_suite_configs: list[TestSuiteConfig],
2095d094f9fSJuraj Linkeš        func: bool,
2105d094f9fSJuraj Linkeš        perf: bool,
2115d094f9fSJuraj Linkeš    ) -> list[TestSuiteWithCases]:
2125ea59af4SLuca Vizzarro        """Get test suites with selected cases.
2135d094f9fSJuraj Linkeš
2145ea59af4SLuca Vizzarro        The test suites with test cases defined in the user configuration are selected
2155ea59af4SLuca Vizzarro        and the corresponding functions and classes are gathered.
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:
2235ea59af4SLuca Vizzarro            The 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:
2285ea59af4SLuca Vizzarro            test_suite_class = test_suite_config.test_suite_spec.class_obj
229b6eb5004SJuraj Linkeš            test_cases: list[type[TestCase]] = []
230c64af3c7SLuca Vizzarro            func_test_cases, perf_test_cases = test_suite_class.filter_test_cases(
231b935bdc3SLuca Vizzarro                test_suite_config.test_cases_names
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š        return test_suites_with_cases
2425d094f9fSJuraj Linkeš
24385ceeeceSJuraj Linkeš    def _connect_nodes_and_run_test_run(
2445d094f9fSJuraj Linkeš        self,
2455d094f9fSJuraj Linkeš        sut_nodes: dict[str, SutNode],
2465d094f9fSJuraj Linkeš        tg_nodes: dict[str, TGNode],
247b935bdc3SLuca Vizzarro        sut_node_config: SutNodeConfiguration,
248b935bdc3SLuca Vizzarro        tg_node_config: TGNodeConfiguration,
24985ceeeceSJuraj Linkeš        test_run_config: TestRunConfiguration,
25085ceeeceSJuraj Linkeš        test_run_result: TestRunResult,
2515d094f9fSJuraj Linkeš        test_suites_with_cases: Iterable[TestSuiteWithCases],
2525d094f9fSJuraj Linkeš    ) -> None:
25385ceeeceSJuraj Linkeš        """Connect nodes, then continue to run the given test run.
2545d094f9fSJuraj Linkeš
25585ceeeceSJuraj Linkeš        Connect the :class:`SutNode` and the :class:`TGNode` of this `test_run_config`.
2565d094f9fSJuraj Linkeš        If either has already been connected, it's going to be in either `sut_nodes` or `tg_nodes`,
2575d094f9fSJuraj Linkeš        respectively.
2585d094f9fSJuraj Linkeš        If not, connect and add the node to the respective `sut_nodes` or `tg_nodes` :class:`dict`.
2595d094f9fSJuraj Linkeš
2605d094f9fSJuraj Linkeš        Args:
2615d094f9fSJuraj Linkeš            sut_nodes: A dictionary storing connected/to be connected SUT nodes.
2625d094f9fSJuraj Linkeš            tg_nodes: A dictionary storing connected/to be connected TG nodes.
263b935bdc3SLuca Vizzarro            sut_node_config: The test run's SUT node configuration.
264b935bdc3SLuca Vizzarro            tg_node_config: The test run's TG node configuration.
26585ceeeceSJuraj Linkeš            test_run_config: A test run configuration.
26685ceeeceSJuraj Linkeš            test_run_result: The test run's result.
2675d094f9fSJuraj Linkeš            test_suites_with_cases: The test suites with test cases to run.
2685d094f9fSJuraj Linkeš        """
269b935bdc3SLuca Vizzarro        sut_node = sut_nodes.get(sut_node_config.name)
270b935bdc3SLuca Vizzarro        tg_node = tg_nodes.get(tg_node_config.name)
2715d094f9fSJuraj Linkeš
2725d094f9fSJuraj Linkeš        try:
2735d094f9fSJuraj Linkeš            if not sut_node:
274b935bdc3SLuca Vizzarro                sut_node = SutNode(sut_node_config)
2755d094f9fSJuraj Linkeš                sut_nodes[sut_node.name] = sut_node
2765d094f9fSJuraj Linkeš            if not tg_node:
277b935bdc3SLuca Vizzarro                tg_node = TGNode(tg_node_config)
2785d094f9fSJuraj Linkeš                tg_nodes[tg_node.name] = tg_node
2795d094f9fSJuraj Linkeš        except Exception as e:
280b935bdc3SLuca Vizzarro            failed_node = test_run_config.system_under_test_node.node_name
2815d094f9fSJuraj Linkeš            if sut_node:
282b935bdc3SLuca Vizzarro                failed_node = test_run_config.traffic_generator_node
2835d094f9fSJuraj Linkeš            self._logger.exception(f"The Creation of node {failed_node} failed.")
28485ceeeceSJuraj Linkeš            test_run_result.update_setup(Result.FAIL, e)
2855d094f9fSJuraj Linkeš
2865d094f9fSJuraj Linkeš        else:
28785ceeeceSJuraj Linkeš            self._run_test_run(
28885ceeeceSJuraj Linkeš                sut_node, tg_node, test_run_config, test_run_result, test_suites_with_cases
2895d094f9fSJuraj Linkeš            )
2905d094f9fSJuraj Linkeš
29185ceeeceSJuraj Linkeš    def _run_test_run(
2922f28a4fcSJuraj Linkeš        self,
2932f28a4fcSJuraj Linkeš        sut_node: SutNode,
2942f28a4fcSJuraj Linkeš        tg_node: TGNode,
29585ceeeceSJuraj Linkeš        test_run_config: TestRunConfiguration,
29685ceeeceSJuraj Linkeš        test_run_result: TestRunResult,
2975d094f9fSJuraj Linkeš        test_suites_with_cases: Iterable[TestSuiteWithCases],
2982f28a4fcSJuraj Linkeš    ) -> None:
29985ceeeceSJuraj Linkeš        """Run the given test run.
3002f28a4fcSJuraj Linkeš
30111b2279aSTomáš Ďurovec        This involves running the test run setup as well as running all test suites
30285ceeeceSJuraj Linkeš        in the given test run. After that, the test run teardown is run.
3032f28a4fcSJuraj Linkeš
3042f28a4fcSJuraj Linkeš        Args:
30585ceeeceSJuraj Linkeš            sut_node: The test run's SUT node.
30685ceeeceSJuraj Linkeš            tg_node: The test run's TG node.
30785ceeeceSJuraj Linkeš            test_run_config: A test run configuration.
30885ceeeceSJuraj Linkeš            test_run_result: The test run's result.
3095d094f9fSJuraj Linkeš            test_suites_with_cases: The test suites with test cases to run.
310f9957667STomáš Ďurovec
311f9957667STomáš Ďurovec        Raises:
312f9957667STomáš Ďurovec            ConfigurationError: If the DPDK sources or build is not set up from config or settings.
3132f28a4fcSJuraj Linkeš        """
31485ceeeceSJuraj Linkeš        self._logger.info(
315b935bdc3SLuca Vizzarro            f"Running test run with SUT '{test_run_config.system_under_test_node.node_name}'."
31685ceeeceSJuraj Linkeš        )
317*9f8a2572STomáš Ďurovec        test_run_result.ports = sut_node.ports
318*9f8a2572STomáš Ďurovec        test_run_result.sut_info = sut_node.node_info
3192f28a4fcSJuraj Linkeš        try:
320b935bdc3SLuca Vizzarro            dpdk_build_config = test_run_config.dpdk_config
321b935bdc3SLuca Vizzarro            if new_location := SETTINGS.dpdk_location:
322b935bdc3SLuca Vizzarro                dpdk_build_config = dpdk_build_config.model_copy(
323b935bdc3SLuca Vizzarro                    update={"dpdk_location": new_location}
324b935bdc3SLuca Vizzarro                )
325b935bdc3SLuca Vizzarro            if dir := SETTINGS.precompiled_build_dir:
326b935bdc3SLuca Vizzarro                dpdk_build_config = DPDKPrecompiledBuildConfiguration(
327b935bdc3SLuca Vizzarro                    dpdk_location=dpdk_build_config.dpdk_location, precompiled_build_dir=dir
328b935bdc3SLuca Vizzarro                )
329b935bdc3SLuca Vizzarro            sut_node.set_up_test_run(test_run_config, dpdk_build_config)
330*9f8a2572STomáš Ďurovec            test_run_result.dpdk_build_info = sut_node.get_dpdk_build_info()
331b935bdc3SLuca Vizzarro            tg_node.set_up_test_run(test_run_config, dpdk_build_config)
33285ceeeceSJuraj Linkeš            test_run_result.update_setup(Result.PASS)
3332f28a4fcSJuraj Linkeš        except Exception as e:
33485ceeeceSJuraj Linkeš            self._logger.exception("Test run setup failed.")
33585ceeeceSJuraj Linkeš            test_run_result.update_setup(Result.FAIL, e)
3362f28a4fcSJuraj Linkeš
3372f28a4fcSJuraj Linkeš        else:
33811b2279aSTomáš Ďurovec            self._run_test_suites(sut_node, tg_node, test_run_result, test_suites_with_cases)
3392f28a4fcSJuraj Linkeš
3402f28a4fcSJuraj Linkeš        finally:
3412f28a4fcSJuraj Linkeš            try:
34285ceeeceSJuraj Linkeš                self._logger.set_stage(DtsStage.test_run_teardown)
34385ceeeceSJuraj Linkeš                sut_node.tear_down_test_run()
344dc0174d2SJuraj Linkeš                tg_node.tear_down_test_run()
34585ceeeceSJuraj Linkeš                test_run_result.update_teardown(Result.PASS)
3462f28a4fcSJuraj Linkeš            except Exception as e:
34785ceeeceSJuraj Linkeš                self._logger.exception("Test run teardown failed.")
34885ceeeceSJuraj Linkeš                test_run_result.update_teardown(Result.FAIL, e)
3492f28a4fcSJuraj Linkeš
350eebfb5bbSJuraj Linkeš    def _get_supported_capabilities(
351eebfb5bbSJuraj Linkeš        self,
352eebfb5bbSJuraj Linkeš        sut_node: SutNode,
353eebfb5bbSJuraj Linkeš        topology_config: Topology,
354eebfb5bbSJuraj Linkeš        test_suites_with_cases: Iterable[TestSuiteWithCases],
355eebfb5bbSJuraj Linkeš    ) -> set[Capability]:
356eebfb5bbSJuraj Linkeš
357eebfb5bbSJuraj Linkeš        capabilities_to_check = set()
358eebfb5bbSJuraj Linkeš        for test_suite_with_cases in test_suites_with_cases:
359eebfb5bbSJuraj Linkeš            capabilities_to_check.update(test_suite_with_cases.required_capabilities)
360eebfb5bbSJuraj Linkeš
361eebfb5bbSJuraj Linkeš        self._logger.debug(f"Found capabilities to check: {capabilities_to_check}")
362eebfb5bbSJuraj Linkeš
363eebfb5bbSJuraj Linkeš        return get_supported_capabilities(sut_node, topology_config, capabilities_to_check)
364eebfb5bbSJuraj Linkeš
3655c7b6207SJuraj Linkeš    def _run_test_suites(
3662f28a4fcSJuraj Linkeš        self,
3672f28a4fcSJuraj Linkeš        sut_node: SutNode,
3682f28a4fcSJuraj Linkeš        tg_node: TGNode,
36911b2279aSTomáš Ďurovec        test_run_result: TestRunResult,
3705d094f9fSJuraj Linkeš        test_suites_with_cases: Iterable[TestSuiteWithCases],
3712f28a4fcSJuraj Linkeš    ) -> None:
37211b2279aSTomáš Ďurovec        """Run `test_suites_with_cases` with the current test run.
3732f28a4fcSJuraj Linkeš
374ecaff610STomáš Ďurovec        The method assumes the DPDK we're testing has already been built on the SUT node.
3752f28a4fcSJuraj Linkeš
376eebfb5bbSJuraj Linkeš        Before running any suites, the method determines whether they should be skipped
377eebfb5bbSJuraj Linkeš        by inspecting any required capabilities the test suite needs and comparing those
378eebfb5bbSJuraj Linkeš        to capabilities supported by the tested environment. If all capabilities are supported,
379eebfb5bbSJuraj Linkeš        the suite is run. If all test cases in a test suite would be skipped, the whole test suite
380eebfb5bbSJuraj Linkeš        is skipped (the setup and teardown is not run).
381eebfb5bbSJuraj Linkeš
3825c7b6207SJuraj Linkeš        If a blocking test suite (such as the smoke test suite) fails, the rest of the test suites
38311b2279aSTomáš Ďurovec        in the current test run won't be executed.
3845c7b6207SJuraj Linkeš
3852f28a4fcSJuraj Linkeš        Args:
38685ceeeceSJuraj Linkeš            sut_node: The test run's SUT node.
38785ceeeceSJuraj Linkeš            tg_node: The test run's TG node.
38811b2279aSTomáš Ďurovec            test_run_result: The test run's result.
3895d094f9fSJuraj Linkeš            test_suites_with_cases: The test suites with test cases to run.
3902f28a4fcSJuraj Linkeš        """
391ecaff610STomáš Ďurovec        end_dpdk_build = False
3920b5ee16dSJuraj Linkeš        topology = Topology(sut_node.ports, tg_node.ports)
393eebfb5bbSJuraj Linkeš        supported_capabilities = self._get_supported_capabilities(
394eebfb5bbSJuraj Linkeš            sut_node, topology, test_suites_with_cases
395eebfb5bbSJuraj Linkeš        )
3965d094f9fSJuraj Linkeš        for test_suite_with_cases in test_suites_with_cases:
397eebfb5bbSJuraj Linkeš            test_suite_with_cases.mark_skip_unsupported(supported_capabilities)
39811b2279aSTomáš Ďurovec            test_suite_result = test_run_result.add_test_suite(test_suite_with_cases)
3995d094f9fSJuraj Linkeš            try:
400566201aeSJuraj Linkeš                if not test_suite_with_cases.skip:
401566201aeSJuraj Linkeš                    self._run_test_suite(
402566201aeSJuraj Linkeš                        sut_node,
403566201aeSJuraj Linkeš                        tg_node,
4040b5ee16dSJuraj Linkeš                        topology,
405566201aeSJuraj Linkeš                        test_suite_result,
406566201aeSJuraj Linkeš                        test_suite_with_cases,
407566201aeSJuraj Linkeš                    )
408566201aeSJuraj Linkeš                else:
409566201aeSJuraj Linkeš                    self._logger.info(
410566201aeSJuraj Linkeš                        f"Test suite execution SKIPPED: "
411566201aeSJuraj Linkeš                        f"'{test_suite_with_cases.test_suite_class.__name__}'. Reason: "
412566201aeSJuraj Linkeš                        f"{test_suite_with_cases.test_suite_class.skip_reason}"
413566201aeSJuraj Linkeš                    )
414566201aeSJuraj Linkeš                    test_suite_result.update_setup(Result.SKIP)
4152f28a4fcSJuraj Linkeš            except BlockingTestSuiteError as e:
4162f28a4fcSJuraj Linkeš                self._logger.exception(
4175d094f9fSJuraj Linkeš                    f"An error occurred within {test_suite_with_cases.test_suite_class.__name__}. "
41811b2279aSTomáš Ďurovec                    "Skipping the rest of the test suites in this test run."
4192f28a4fcSJuraj Linkeš                )
4202f28a4fcSJuraj Linkeš                self._result.add_error(e)
421ecaff610STomáš Ďurovec                end_dpdk_build = True
4222f28a4fcSJuraj Linkeš            # if a blocking test failed and we need to bail out of suite executions
423ecaff610STomáš Ďurovec            if end_dpdk_build:
4242f28a4fcSJuraj Linkeš                break
4252f28a4fcSJuraj Linkeš
4265d094f9fSJuraj Linkeš    def _run_test_suite(
4272f28a4fcSJuraj Linkeš        self,
4282f28a4fcSJuraj Linkeš        sut_node: SutNode,
4292f28a4fcSJuraj Linkeš        tg_node: TGNode,
4300b5ee16dSJuraj Linkeš        topology: Topology,
4315d094f9fSJuraj Linkeš        test_suite_result: TestSuiteResult,
4325d094f9fSJuraj Linkeš        test_suite_with_cases: TestSuiteWithCases,
4332f28a4fcSJuraj Linkeš    ) -> None:
4345d094f9fSJuraj Linkeš        """Set up, execute and tear down `test_suite_with_cases`.
4352f28a4fcSJuraj Linkeš
436ecaff610STomáš Ďurovec        The method assumes the DPDK we're testing has already been built on the SUT node.
4372f28a4fcSJuraj Linkeš
4385c7b6207SJuraj Linkeš        Test suite execution consists of running the discovered test cases.
4395c7b6207SJuraj Linkeš        A test case run consists of setup, execution and teardown of said test case.
4405c7b6207SJuraj Linkeš
4415c7b6207SJuraj Linkeš        Record the setup and the teardown and handle failures.
4425c7b6207SJuraj Linkeš
4432f28a4fcSJuraj Linkeš        Args:
44485ceeeceSJuraj Linkeš            sut_node: The test run's SUT node.
44585ceeeceSJuraj Linkeš            tg_node: The test run's TG node.
4465d094f9fSJuraj Linkeš            test_suite_result: The test suite level result object associated
4475d094f9fSJuraj Linkeš                with the current test suite.
4485d094f9fSJuraj Linkeš            test_suite_with_cases: The test suite with test cases to run.
4492f28a4fcSJuraj Linkeš
4502f28a4fcSJuraj Linkeš        Raises:
4512f28a4fcSJuraj Linkeš            BlockingTestSuiteError: If a blocking test suite fails.
4522f28a4fcSJuraj Linkeš        """
4535d094f9fSJuraj Linkeš        test_suite_name = test_suite_with_cases.test_suite_class.__name__
45404f5a5a6SJuraj Linkeš        self._logger.set_stage(
45504f5a5a6SJuraj Linkeš            DtsStage.test_suite_setup, Path(SETTINGS.output_dir, test_suite_name)
45604f5a5a6SJuraj Linkeš        )
4570b5ee16dSJuraj Linkeš        test_suite = test_suite_with_cases.test_suite_class(sut_node, tg_node, topology)
4585c7b6207SJuraj Linkeš        try:
4595c7b6207SJuraj Linkeš            self._logger.info(f"Starting test suite setup: {test_suite_name}")
4605c7b6207SJuraj Linkeš            test_suite.set_up_suite()
4615c7b6207SJuraj Linkeš            test_suite_result.update_setup(Result.PASS)
4625c7b6207SJuraj Linkeš            self._logger.info(f"Test suite setup successful: {test_suite_name}")
4635c7b6207SJuraj Linkeš        except Exception as e:
4645c7b6207SJuraj Linkeš            self._logger.exception(f"Test suite setup ERROR: {test_suite_name}")
4655c7b6207SJuraj Linkeš            test_suite_result.update_setup(Result.ERROR, e)
4665c7b6207SJuraj Linkeš
4675c7b6207SJuraj Linkeš        else:
4685d094f9fSJuraj Linkeš            self._execute_test_suite(
4695d094f9fSJuraj Linkeš                test_suite,
4705d094f9fSJuraj Linkeš                test_suite_with_cases.test_cases,
4715d094f9fSJuraj Linkeš                test_suite_result,
4725d094f9fSJuraj Linkeš            )
4735c7b6207SJuraj Linkeš        finally:
4745c7b6207SJuraj Linkeš            try:
47504f5a5a6SJuraj Linkeš                self._logger.set_stage(DtsStage.test_suite_teardown)
4765c7b6207SJuraj Linkeš                test_suite.tear_down_suite()
4775c7b6207SJuraj Linkeš                sut_node.kill_cleanup_dpdk_apps()
4785c7b6207SJuraj Linkeš                test_suite_result.update_teardown(Result.PASS)
4795c7b6207SJuraj Linkeš            except Exception as e:
4805c7b6207SJuraj Linkeš                self._logger.exception(f"Test suite teardown ERROR: {test_suite_name}")
4815c7b6207SJuraj Linkeš                self._logger.warning(
4825c7b6207SJuraj Linkeš                    f"Test suite '{test_suite_name}' teardown failed, "
4835d094f9fSJuraj Linkeš                    "the next test suite may be affected."
4842f28a4fcSJuraj Linkeš                )
4855c7b6207SJuraj Linkeš                test_suite_result.update_setup(Result.ERROR, e)
4865c7b6207SJuraj Linkeš            if len(test_suite_result.get_errors()) > 0 and test_suite.is_blocking:
4875c7b6207SJuraj Linkeš                raise BlockingTestSuiteError(test_suite_name)
4885c7b6207SJuraj Linkeš
4895c7b6207SJuraj Linkeš    def _execute_test_suite(
4905d094f9fSJuraj Linkeš        self,
4915d094f9fSJuraj Linkeš        test_suite: TestSuite,
492b6eb5004SJuraj Linkeš        test_cases: Iterable[type[TestCase]],
4935d094f9fSJuraj Linkeš        test_suite_result: TestSuiteResult,
4945c7b6207SJuraj Linkeš    ) -> None:
4955d094f9fSJuraj Linkeš        """Execute all `test_cases` in `test_suite`.
4965c7b6207SJuraj Linkeš
4975c7b6207SJuraj Linkeš        If the :option:`--re-run` command line argument or the :envvar:`DTS_RERUN` environment
4985c7b6207SJuraj Linkeš        variable is set, in case of a test case failure, the test case will be executed again
4995c7b6207SJuraj Linkeš        until it passes or it fails that many times in addition of the first failure.
5005c7b6207SJuraj Linkeš
5015c7b6207SJuraj Linkeš        Args:
5025c7b6207SJuraj Linkeš            test_suite: The test suite object.
503b6eb5004SJuraj Linkeš            test_cases: The list of test case functions.
5045c7b6207SJuraj Linkeš            test_suite_result: The test suite level result object associated
5055c7b6207SJuraj Linkeš                with the current test suite.
5065c7b6207SJuraj Linkeš        """
50704f5a5a6SJuraj Linkeš        self._logger.set_stage(DtsStage.test_suite)
508b6eb5004SJuraj Linkeš        for test_case in test_cases:
509b6eb5004SJuraj Linkeš            test_case_name = test_case.__name__
5105c7b6207SJuraj Linkeš            test_case_result = test_suite_result.add_test_case(test_case_name)
5115c7b6207SJuraj Linkeš            all_attempts = SETTINGS.re_run + 1
5125c7b6207SJuraj Linkeš            attempt_nr = 1
513566201aeSJuraj Linkeš            if not test_case.skip:
514b6eb5004SJuraj Linkeš                self._run_test_case(test_suite, test_case, test_case_result)
5155c7b6207SJuraj Linkeš                while not test_case_result and attempt_nr < all_attempts:
5165c7b6207SJuraj Linkeš                    attempt_nr += 1
5175c7b6207SJuraj Linkeš                    self._logger.info(
5185c7b6207SJuraj Linkeš                        f"Re-running FAILED test case '{test_case_name}'. "
5195c7b6207SJuraj Linkeš                        f"Attempt number {attempt_nr} out of {all_attempts}."
5205c7b6207SJuraj Linkeš                    )
521b6eb5004SJuraj Linkeš                    self._run_test_case(test_suite, test_case, test_case_result)
522566201aeSJuraj Linkeš            else:
523566201aeSJuraj Linkeš                self._logger.info(
524566201aeSJuraj Linkeš                    f"Test case execution SKIPPED: {test_case_name}. Reason: "
525566201aeSJuraj Linkeš                    f"{test_case.skip_reason}"
526566201aeSJuraj Linkeš                )
527566201aeSJuraj Linkeš                test_case_result.update_setup(Result.SKIP)
5285c7b6207SJuraj Linkeš
5295c7b6207SJuraj Linkeš    def _run_test_case(
5305c7b6207SJuraj Linkeš        self,
5315c7b6207SJuraj Linkeš        test_suite: TestSuite,
532b6eb5004SJuraj Linkeš        test_case: type[TestCase],
5335c7b6207SJuraj Linkeš        test_case_result: TestCaseResult,
5345c7b6207SJuraj Linkeš    ) -> None:
5355d094f9fSJuraj Linkeš        """Setup, execute and teardown `test_case_method` from `test_suite`.
5365c7b6207SJuraj Linkeš
5375c7b6207SJuraj Linkeš        Record the result of the setup and the teardown and handle failures.
5385c7b6207SJuraj Linkeš
5395c7b6207SJuraj Linkeš        Args:
5405c7b6207SJuraj Linkeš            test_suite: The test suite object.
541b6eb5004SJuraj Linkeš            test_case: The test case function.
5425c7b6207SJuraj Linkeš            test_case_result: The test case level result object associated
5435c7b6207SJuraj Linkeš                with the current test case.
5445c7b6207SJuraj Linkeš        """
545b6eb5004SJuraj Linkeš        test_case_name = test_case.__name__
5465c7b6207SJuraj Linkeš
5475c7b6207SJuraj Linkeš        try:
5485c7b6207SJuraj Linkeš            # run set_up function for each case
5495c7b6207SJuraj Linkeš            test_suite.set_up_test_case()
5505c7b6207SJuraj Linkeš            test_case_result.update_setup(Result.PASS)
5515c7b6207SJuraj Linkeš        except SSHTimeoutError as e:
5525c7b6207SJuraj Linkeš            self._logger.exception(f"Test case setup FAILED: {test_case_name}")
5535c7b6207SJuraj Linkeš            test_case_result.update_setup(Result.FAIL, e)
5545c7b6207SJuraj Linkeš        except Exception as e:
5555c7b6207SJuraj Linkeš            self._logger.exception(f"Test case setup ERROR: {test_case_name}")
5565c7b6207SJuraj Linkeš            test_case_result.update_setup(Result.ERROR, e)
5575c7b6207SJuraj Linkeš
5585c7b6207SJuraj Linkeš        else:
5595c7b6207SJuraj Linkeš            # run test case if setup was successful
560b6eb5004SJuraj Linkeš            self._execute_test_case(test_suite, test_case, test_case_result)
5615c7b6207SJuraj Linkeš
5625c7b6207SJuraj Linkeš        finally:
5635c7b6207SJuraj Linkeš            try:
5645c7b6207SJuraj Linkeš                test_suite.tear_down_test_case()
5655c7b6207SJuraj Linkeš                test_case_result.update_teardown(Result.PASS)
5665c7b6207SJuraj Linkeš            except Exception as e:
5675c7b6207SJuraj Linkeš                self._logger.exception(f"Test case teardown ERROR: {test_case_name}")
5685c7b6207SJuraj Linkeš                self._logger.warning(
5695c7b6207SJuraj Linkeš                    f"Test case '{test_case_name}' teardown failed, "
5705c7b6207SJuraj Linkeš                    f"the next test case may be affected."
5715c7b6207SJuraj Linkeš                )
5725c7b6207SJuraj Linkeš                test_case_result.update_teardown(Result.ERROR, e)
5735c7b6207SJuraj Linkeš                test_case_result.update(Result.ERROR)
5745c7b6207SJuraj Linkeš
5755c7b6207SJuraj Linkeš    def _execute_test_case(
5765d094f9fSJuraj Linkeš        self,
5775d094f9fSJuraj Linkeš        test_suite: TestSuite,
578b6eb5004SJuraj Linkeš        test_case: type[TestCase],
5795d094f9fSJuraj Linkeš        test_case_result: TestCaseResult,
5805c7b6207SJuraj Linkeš    ) -> None:
5815d094f9fSJuraj Linkeš        """Execute `test_case_method` from `test_suite`, record the result and handle failures.
5825c7b6207SJuraj Linkeš
5835c7b6207SJuraj Linkeš        Args:
5845d094f9fSJuraj Linkeš            test_suite: The test suite object.
585b6eb5004SJuraj Linkeš            test_case: The test case function.
5865c7b6207SJuraj Linkeš            test_case_result: The test case level result object associated
5875c7b6207SJuraj Linkeš                with the current test case.
5885c7b6207SJuraj Linkeš        """
589b6eb5004SJuraj Linkeš        test_case_name = test_case.__name__
5905c7b6207SJuraj Linkeš        try:
5915c7b6207SJuraj Linkeš            self._logger.info(f"Starting test case execution: {test_case_name}")
592b6eb5004SJuraj Linkeš            # Explicit method binding is required, otherwise mypy complains
593b6eb5004SJuraj Linkeš            MethodType(test_case, test_suite)()
5945c7b6207SJuraj Linkeš            test_case_result.update(Result.PASS)
5955c7b6207SJuraj Linkeš            self._logger.info(f"Test case execution PASSED: {test_case_name}")
5965c7b6207SJuraj Linkeš
5975c7b6207SJuraj Linkeš        except TestCaseVerifyError as e:
5985c7b6207SJuraj Linkeš            self._logger.exception(f"Test case execution FAILED: {test_case_name}")
5995c7b6207SJuraj Linkeš            test_case_result.update(Result.FAIL, e)
6005c7b6207SJuraj Linkeš        except Exception as e:
6015c7b6207SJuraj Linkeš            self._logger.exception(f"Test case execution ERROR: {test_case_name}")
6025c7b6207SJuraj Linkeš            test_case_result.update(Result.ERROR, e)
6035c7b6207SJuraj Linkeš        except KeyboardInterrupt:
6045c7b6207SJuraj Linkeš            self._logger.error(f"Test case execution INTERRUPTED by user: {test_case_name}")
6055c7b6207SJuraj Linkeš            test_case_result.update(Result.SKIP)
6065c7b6207SJuraj Linkeš            raise KeyboardInterrupt("Stop DTS")
6072f28a4fcSJuraj Linkeš
6082f28a4fcSJuraj Linkeš    def _exit_dts(self) -> None:
6092f28a4fcSJuraj Linkeš        """Process all errors and exit with the proper exit code."""
6102f28a4fcSJuraj Linkeš        self._result.process()
6112f28a4fcSJuraj Linkeš
6122f28a4fcSJuraj Linkeš        if self._logger:
6132f28a4fcSJuraj Linkeš            self._logger.info("DTS execution has ended.")
6142f28a4fcSJuraj Linkeš
6152f28a4fcSJuraj Linkeš        sys.exit(self._result.get_return_code())
616cfe40bacSLuca Vizzarro
617cfe40bacSLuca Vizzarro    def _init_random_seed(self, conf: TestRunConfiguration) -> None:
618cfe40bacSLuca Vizzarro        """Initialize the random seed to use for the test run."""
619cfe40bacSLuca Vizzarro        seed = SETTINGS.random_seed or conf.random_seed or random.randrange(0xFFFF_FFFF)
620cfe40bacSLuca Vizzarro        self._logger.info(f"Initializing test run with random seed {seed}.")
621cfe40bacSLuca Vizzarro        random.seed(seed)
622