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