1566201aeSJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause 2566201aeSJuraj Linkeš# Copyright(c) 2024 PANTHEON.tech s.r.o. 3566201aeSJuraj Linkeš 4566201aeSJuraj Linkeš"""Testbed capabilities. 5566201aeSJuraj Linkeš 6eebfb5bbSJuraj LinkešThis module provides a protocol that defines the common attributes of test cases and suites 7eebfb5bbSJuraj Linkešand support for test environment capabilities. 8c89d0038SJuraj Linkeš 9c89d0038SJuraj LinkešMany test cases are testing features not available on all hardware. 10039256daSJuraj LinkešOn the other hand, some test cases or suites may not need the most complex topology available. 11039256daSJuraj Linkeš 12039256daSJuraj LinkešThe module allows developers to mark test cases or suites a requiring certain hardware capabilities 13039256daSJuraj Linkešor a particular topology with the :func:`requires` decorator. 14039256daSJuraj Linkeš 15039256daSJuraj LinkešThere are differences between hardware and topology capabilities: 16039256daSJuraj Linkeš 17039256daSJuraj Linkeš * Hardware capabilities are assumed to not be required when not specified. 18039256daSJuraj Linkeš * However, some topology is always available, so each test case or suite is assigned 19039256daSJuraj Linkeš a default topology if no topology is specified in the decorator. 20c89d0038SJuraj Linkeš 21c89d0038SJuraj LinkešThe module also allows developers to mark test cases or suites as requiring certain 22c89d0038SJuraj Linkešhardware capabilities with the :func:`requires` decorator. 23c0119400SJuraj Linkeš 24039256daSJuraj LinkešExamples: 25039256daSJuraj Linkeš .. code:: python 26039256daSJuraj Linkeš 27039256daSJuraj Linkeš from framework.test_suite import TestSuite, func_test 28039256daSJuraj Linkeš from framework.testbed_model.capability import TopologyType, requires 29039256daSJuraj Linkeš # The whole test suite (each test case within) doesn't require any links. 30039256daSJuraj Linkeš @requires(topology_type=TopologyType.no_link) 31039256daSJuraj Linkeš @func_test 32039256daSJuraj Linkeš class TestHelloWorld(TestSuite): 33039256daSJuraj Linkeš def hello_world_single_core(self): 34039256daSJuraj Linkeš ... 35039256daSJuraj Linkeš 36c0119400SJuraj Linkeš .. code:: python 37c0119400SJuraj Linkeš 38c0119400SJuraj Linkeš from framework.test_suite import TestSuite, func_test 39c0119400SJuraj Linkeš from framework.testbed_model.capability import NicCapability, requires 40c0119400SJuraj Linkeš class TestPmdBufferScatter(TestSuite): 41c0119400SJuraj Linkeš # only the test case requires the SCATTERED_RX_ENABLED capability 42c0119400SJuraj Linkeš # other test cases may not require it 43c0119400SJuraj Linkeš @requires(NicCapability.SCATTERED_RX_ENABLED) 44c0119400SJuraj Linkeš @func_test 45c0119400SJuraj Linkeš def test_scatter_mbuf_2048(self): 46566201aeSJuraj Linkeš""" 47566201aeSJuraj Linkeš 48039256daSJuraj Linkešimport inspect 49eebfb5bbSJuraj Linkešfrom abc import ABC, abstractmethod 50*c64af3c7SLuca Vizzarrofrom collections.abc import MutableSet 51c89d0038SJuraj Linkešfrom dataclasses import dataclass 52*c64af3c7SLuca Vizzarrofrom typing import TYPE_CHECKING, Callable, ClassVar, Protocol 53eebfb5bbSJuraj Linkeš 54eebfb5bbSJuraj Linkešfrom typing_extensions import Self 55eebfb5bbSJuraj Linkeš 56039256daSJuraj Linkešfrom framework.exception import ConfigurationError 57c89d0038SJuraj Linkešfrom framework.logger import get_dts_logger 58c89d0038SJuraj Linkešfrom framework.remote_session.testpmd_shell import ( 59c89d0038SJuraj Linkeš NicCapability, 60c89d0038SJuraj Linkeš TestPmdShell, 61c89d0038SJuraj Linkeš TestPmdShellCapabilityMethod, 62c89d0038SJuraj Linkeš TestPmdShellDecorator, 63c89d0038SJuraj Linkeš TestPmdShellMethod, 64c89d0038SJuraj Linkeš) 65c89d0038SJuraj Linkeš 66eebfb5bbSJuraj Linkešfrom .sut_node import SutNode 67039256daSJuraj Linkešfrom .topology import Topology, TopologyType 68eebfb5bbSJuraj Linkeš 69*c64af3c7SLuca Vizzarroif TYPE_CHECKING: 70*c64af3c7SLuca Vizzarro from framework.test_suite import TestCase 71*c64af3c7SLuca Vizzarro 72eebfb5bbSJuraj Linkeš 73eebfb5bbSJuraj Linkešclass Capability(ABC): 74eebfb5bbSJuraj Linkeš """The base class for various capabilities. 75eebfb5bbSJuraj Linkeš 76eebfb5bbSJuraj Linkeš The same capability should always be represented by the same object, 77eebfb5bbSJuraj Linkeš meaning the same capability required by different test cases or suites 78eebfb5bbSJuraj Linkeš should point to the same object. 79eebfb5bbSJuraj Linkeš 80eebfb5bbSJuraj Linkeš Example: 81eebfb5bbSJuraj Linkeš ``test_case1`` and ``test_case2`` each require ``capability1`` 82eebfb5bbSJuraj Linkeš and in both instances, ``capability1`` should point to the same capability object. 83eebfb5bbSJuraj Linkeš 84eebfb5bbSJuraj Linkeš It is up to the subclasses how they implement this. 85eebfb5bbSJuraj Linkeš 86eebfb5bbSJuraj Linkeš The instances are used in sets so they must be hashable. 87eebfb5bbSJuraj Linkeš """ 88eebfb5bbSJuraj Linkeš 89eebfb5bbSJuraj Linkeš #: A set storing the capabilities whose support should be checked. 90eebfb5bbSJuraj Linkeš capabilities_to_check: ClassVar[set[Self]] = set() 91eebfb5bbSJuraj Linkeš 92eebfb5bbSJuraj Linkeš def register_to_check(self) -> Callable[[SutNode, "Topology"], set[Self]]: 93eebfb5bbSJuraj Linkeš """Register the capability to be checked for support. 94eebfb5bbSJuraj Linkeš 95eebfb5bbSJuraj Linkeš Returns: 96eebfb5bbSJuraj Linkeš The callback function that checks the support of capabilities of the particular subclass 97eebfb5bbSJuraj Linkeš which should be called after all capabilities have been registered. 98eebfb5bbSJuraj Linkeš """ 99eebfb5bbSJuraj Linkeš if not type(self).capabilities_to_check: 100eebfb5bbSJuraj Linkeš type(self).capabilities_to_check = set() 101eebfb5bbSJuraj Linkeš type(self).capabilities_to_check.add(self) 102eebfb5bbSJuraj Linkeš return type(self)._get_and_reset 103eebfb5bbSJuraj Linkeš 104eebfb5bbSJuraj Linkeš def add_to_required(self, test_case_or_suite: type["TestProtocol"]) -> None: 105eebfb5bbSJuraj Linkeš """Add the capability instance to the required test case or suite's capabilities. 106eebfb5bbSJuraj Linkeš 107eebfb5bbSJuraj Linkeš Args: 108eebfb5bbSJuraj Linkeš test_case_or_suite: The test case or suite among whose required capabilities 109eebfb5bbSJuraj Linkeš to add this instance. 110eebfb5bbSJuraj Linkeš """ 111eebfb5bbSJuraj Linkeš if not test_case_or_suite.required_capabilities: 112eebfb5bbSJuraj Linkeš test_case_or_suite.required_capabilities = set() 113eebfb5bbSJuraj Linkeš self._preprocess_required(test_case_or_suite) 114eebfb5bbSJuraj Linkeš test_case_or_suite.required_capabilities.add(self) 115eebfb5bbSJuraj Linkeš 116eebfb5bbSJuraj Linkeš def _preprocess_required(self, test_case_or_suite: type["TestProtocol"]) -> None: 117eebfb5bbSJuraj Linkeš """An optional method that modifies the required capabilities.""" 118eebfb5bbSJuraj Linkeš 119eebfb5bbSJuraj Linkeš @classmethod 120eebfb5bbSJuraj Linkeš def _get_and_reset(cls, sut_node: SutNode, topology: "Topology") -> set[Self]: 121eebfb5bbSJuraj Linkeš """The callback method to be called after all capabilities have been registered. 122eebfb5bbSJuraj Linkeš 123eebfb5bbSJuraj Linkeš Not only does this method check the support of capabilities, 124eebfb5bbSJuraj Linkeš but it also reset the internal set of registered capabilities 125eebfb5bbSJuraj Linkeš so that the "register, then get support" workflow works in subsequent test runs. 126eebfb5bbSJuraj Linkeš """ 127eebfb5bbSJuraj Linkeš supported_capabilities = cls.get_supported_capabilities(sut_node, topology) 128eebfb5bbSJuraj Linkeš cls.capabilities_to_check = set() 129eebfb5bbSJuraj Linkeš return supported_capabilities 130eebfb5bbSJuraj Linkeš 131eebfb5bbSJuraj Linkeš @classmethod 132eebfb5bbSJuraj Linkeš @abstractmethod 133eebfb5bbSJuraj Linkeš def get_supported_capabilities(cls, sut_node: SutNode, topology: "Topology") -> set[Self]: 134eebfb5bbSJuraj Linkeš """Get the support status of each registered capability. 135eebfb5bbSJuraj Linkeš 136eebfb5bbSJuraj Linkeš Each subclass must implement this method and return the subset of supported capabilities 137eebfb5bbSJuraj Linkeš of :attr:`capabilities_to_check`. 138eebfb5bbSJuraj Linkeš 139eebfb5bbSJuraj Linkeš Args: 140eebfb5bbSJuraj Linkeš sut_node: The SUT node of the current test run. 141eebfb5bbSJuraj Linkeš topology: The topology of the current test run. 142eebfb5bbSJuraj Linkeš 143eebfb5bbSJuraj Linkeš Returns: 144eebfb5bbSJuraj Linkeš The supported capabilities. 145eebfb5bbSJuraj Linkeš """ 146eebfb5bbSJuraj Linkeš 147eebfb5bbSJuraj Linkeš @abstractmethod 148eebfb5bbSJuraj Linkeš def __hash__(self) -> int: 149eebfb5bbSJuraj Linkeš """The subclasses must be hashable so that they can be stored in sets.""" 150566201aeSJuraj Linkeš 151566201aeSJuraj Linkeš 152c89d0038SJuraj Linkeš@dataclass 153c89d0038SJuraj Linkešclass DecoratedNicCapability(Capability): 154c89d0038SJuraj Linkeš """A wrapper around :class:`~framework.remote_session.testpmd_shell.NicCapability`. 155c89d0038SJuraj Linkeš 156c89d0038SJuraj Linkeš New instances should be created with the :meth:`create_unique` class method to ensure 157c89d0038SJuraj Linkeš there are no duplicate instances. 158c89d0038SJuraj Linkeš 159c89d0038SJuraj Linkeš Attributes: 160c89d0038SJuraj Linkeš nic_capability: The NIC capability that defines each instance. 161c89d0038SJuraj Linkeš capability_fn: The capability retrieval function of `nic_capability`. 162c89d0038SJuraj Linkeš capability_decorator: The decorator function of `nic_capability`. 163c89d0038SJuraj Linkeš This function will wrap `capability_fn`. 164c89d0038SJuraj Linkeš """ 165c89d0038SJuraj Linkeš 166c89d0038SJuraj Linkeš nic_capability: NicCapability 167c89d0038SJuraj Linkeš capability_fn: TestPmdShellCapabilityMethod 168c89d0038SJuraj Linkeš capability_decorator: TestPmdShellDecorator | None 169c89d0038SJuraj Linkeš _unique_capabilities: ClassVar[dict[NicCapability, Self]] = {} 170c89d0038SJuraj Linkeš 171c89d0038SJuraj Linkeš @classmethod 172c89d0038SJuraj Linkeš def get_unique(cls, nic_capability: NicCapability) -> "DecoratedNicCapability": 173c89d0038SJuraj Linkeš """Get the capability uniquely identified by `nic_capability`. 174c89d0038SJuraj Linkeš 175c89d0038SJuraj Linkeš This is a factory method that implements a quasi-enum pattern. 176c89d0038SJuraj Linkeš The instances of this class are stored in an internal class variable, 177c89d0038SJuraj Linkeš `_unique_capabilities`. 178c89d0038SJuraj Linkeš 179c89d0038SJuraj Linkeš If an instance identified by `nic_capability` doesn't exist, 180c89d0038SJuraj Linkeš it is created and added to `_unique_capabilities`. 181c89d0038SJuraj Linkeš If it exists, it is returned so that a new identical instance is not created. 182c89d0038SJuraj Linkeš 183c89d0038SJuraj Linkeš Args: 184c89d0038SJuraj Linkeš nic_capability: The NIC capability. 185c89d0038SJuraj Linkeš 186c89d0038SJuraj Linkeš Returns: 187c89d0038SJuraj Linkeš The capability uniquely identified by `nic_capability`. 188c89d0038SJuraj Linkeš """ 189c89d0038SJuraj Linkeš decorator_fn = None 190c89d0038SJuraj Linkeš if isinstance(nic_capability.value, tuple): 191c89d0038SJuraj Linkeš capability_fn, decorator_fn = nic_capability.value 192c89d0038SJuraj Linkeš else: 193c89d0038SJuraj Linkeš capability_fn = nic_capability.value 194c89d0038SJuraj Linkeš 195c89d0038SJuraj Linkeš if nic_capability not in cls._unique_capabilities: 196c89d0038SJuraj Linkeš cls._unique_capabilities[nic_capability] = cls( 197c89d0038SJuraj Linkeš nic_capability, capability_fn, decorator_fn 198c89d0038SJuraj Linkeš ) 199c89d0038SJuraj Linkeš return cls._unique_capabilities[nic_capability] 200c89d0038SJuraj Linkeš 201c89d0038SJuraj Linkeš @classmethod 202c89d0038SJuraj Linkeš def get_supported_capabilities( 203c89d0038SJuraj Linkeš cls, sut_node: SutNode, topology: "Topology" 204c89d0038SJuraj Linkeš ) -> set["DecoratedNicCapability"]: 205c89d0038SJuraj Linkeš """Overrides :meth:`~Capability.get_supported_capabilities`. 206c89d0038SJuraj Linkeš 207c89d0038SJuraj Linkeš The capabilities are first sorted by decorators, then reduced into a single function which 208c89d0038SJuraj Linkeš is then passed to the decorator. This way we execute each decorator only once. 209c89d0038SJuraj Linkeš Each capability is first checked whether it's supported/unsupported 210c89d0038SJuraj Linkeš before executing its `capability_fn` so that each capability is retrieved only once. 211c89d0038SJuraj Linkeš """ 212c89d0038SJuraj Linkeš supported_conditional_capabilities: set["DecoratedNicCapability"] = set() 213c89d0038SJuraj Linkeš logger = get_dts_logger(f"{sut_node.name}.{cls.__name__}") 214c89d0038SJuraj Linkeš if topology.type is topology.type.no_link: 215c89d0038SJuraj Linkeš logger.debug( 216c89d0038SJuraj Linkeš "No links available in the current topology, not getting NIC capabilities." 217c89d0038SJuraj Linkeš ) 218c89d0038SJuraj Linkeš return supported_conditional_capabilities 219c89d0038SJuraj Linkeš logger.debug( 220c89d0038SJuraj Linkeš f"Checking which NIC capabilities from {cls.capabilities_to_check} are supported." 221c89d0038SJuraj Linkeš ) 222c89d0038SJuraj Linkeš if cls.capabilities_to_check: 223c89d0038SJuraj Linkeš capabilities_to_check_map = cls._get_decorated_capabilities_map() 224c89d0038SJuraj Linkeš with TestPmdShell( 225c89d0038SJuraj Linkeš sut_node, privileged=True, disable_device_start=True 226c89d0038SJuraj Linkeš ) as testpmd_shell: 227c89d0038SJuraj Linkeš for conditional_capability_fn, capabilities in capabilities_to_check_map.items(): 228c89d0038SJuraj Linkeš supported_capabilities: set[NicCapability] = set() 229c89d0038SJuraj Linkeš unsupported_capabilities: set[NicCapability] = set() 230c89d0038SJuraj Linkeš capability_fn = cls._reduce_capabilities( 231c89d0038SJuraj Linkeš capabilities, supported_capabilities, unsupported_capabilities 232c89d0038SJuraj Linkeš ) 233c89d0038SJuraj Linkeš if conditional_capability_fn: 234c89d0038SJuraj Linkeš capability_fn = conditional_capability_fn(capability_fn) 235c89d0038SJuraj Linkeš capability_fn(testpmd_shell) 236c89d0038SJuraj Linkeš for capability in capabilities: 237c89d0038SJuraj Linkeš if capability.nic_capability in supported_capabilities: 238c89d0038SJuraj Linkeš supported_conditional_capabilities.add(capability) 239c89d0038SJuraj Linkeš 240c89d0038SJuraj Linkeš logger.debug(f"Found supported capabilities {supported_conditional_capabilities}.") 241c89d0038SJuraj Linkeš return supported_conditional_capabilities 242c89d0038SJuraj Linkeš 243c89d0038SJuraj Linkeš @classmethod 244c89d0038SJuraj Linkeš def _get_decorated_capabilities_map( 245c89d0038SJuraj Linkeš cls, 246c89d0038SJuraj Linkeš ) -> dict[TestPmdShellDecorator | None, set["DecoratedNicCapability"]]: 247c89d0038SJuraj Linkeš capabilities_map: dict[TestPmdShellDecorator | None, set["DecoratedNicCapability"]] = {} 248c89d0038SJuraj Linkeš for capability in cls.capabilities_to_check: 249c89d0038SJuraj Linkeš if capability.capability_decorator not in capabilities_map: 250c89d0038SJuraj Linkeš capabilities_map[capability.capability_decorator] = set() 251c89d0038SJuraj Linkeš capabilities_map[capability.capability_decorator].add(capability) 252c89d0038SJuraj Linkeš 253c89d0038SJuraj Linkeš return capabilities_map 254c89d0038SJuraj Linkeš 255c89d0038SJuraj Linkeš @classmethod 256c89d0038SJuraj Linkeš def _reduce_capabilities( 257c89d0038SJuraj Linkeš cls, 258c89d0038SJuraj Linkeš capabilities: set["DecoratedNicCapability"], 259c89d0038SJuraj Linkeš supported_capabilities: MutableSet, 260c89d0038SJuraj Linkeš unsupported_capabilities: MutableSet, 261c89d0038SJuraj Linkeš ) -> TestPmdShellMethod: 262c89d0038SJuraj Linkeš def reduced_fn(testpmd_shell: TestPmdShell) -> None: 263c89d0038SJuraj Linkeš for capability in capabilities: 264c89d0038SJuraj Linkeš if capability not in supported_capabilities | unsupported_capabilities: 265c89d0038SJuraj Linkeš capability.capability_fn( 266c89d0038SJuraj Linkeš testpmd_shell, supported_capabilities, unsupported_capabilities 267c89d0038SJuraj Linkeš ) 268c89d0038SJuraj Linkeš 269c89d0038SJuraj Linkeš return reduced_fn 270c89d0038SJuraj Linkeš 271c89d0038SJuraj Linkeš def __hash__(self) -> int: 272c89d0038SJuraj Linkeš """Instances are identified by :attr:`nic_capability` and :attr:`capability_decorator`.""" 273c89d0038SJuraj Linkeš return hash(self.nic_capability) 274c89d0038SJuraj Linkeš 275c89d0038SJuraj Linkeš def __repr__(self) -> str: 276c89d0038SJuraj Linkeš """Easy to read string of :attr:`nic_capability` and :attr:`capability_decorator`.""" 277c89d0038SJuraj Linkeš return f"{self.nic_capability}" 278c89d0038SJuraj Linkeš 279c89d0038SJuraj Linkeš 280039256daSJuraj Linkeš@dataclass 281039256daSJuraj Linkešclass TopologyCapability(Capability): 282039256daSJuraj Linkeš """A wrapper around :class:`~.topology.TopologyType`. 283039256daSJuraj Linkeš 284039256daSJuraj Linkeš Each test case must be assigned a topology. It could be done explicitly; 285039256daSJuraj Linkeš the implicit default is :attr:`~.topology.TopologyType.default`, which this class defines 286039256daSJuraj Linkeš as equal to :attr:`~.topology.TopologyType.two_links`. 287039256daSJuraj Linkeš 288039256daSJuraj Linkeš Test case topology may be set by setting the topology for the whole suite. 289039256daSJuraj Linkeš The priority in which topology is set is as follows: 290039256daSJuraj Linkeš 291039256daSJuraj Linkeš #. The topology set using the :func:`requires` decorator with a test case, 292039256daSJuraj Linkeš #. The topology set using the :func:`requires` decorator with a test suite, 293039256daSJuraj Linkeš #. The default topology if the decorator is not used. 294039256daSJuraj Linkeš 295039256daSJuraj Linkeš The default topology of test suite (i.e. when not using the decorator 296039256daSJuraj Linkeš or not setting the topology with the decorator) does not affect the topology of test cases. 297039256daSJuraj Linkeš 298039256daSJuraj Linkeš New instances should be created with the :meth:`create_unique` class method to ensure 299039256daSJuraj Linkeš there are no duplicate instances. 300039256daSJuraj Linkeš 301039256daSJuraj Linkeš Attributes: 302039256daSJuraj Linkeš topology_type: The topology type that defines each instance. 303039256daSJuraj Linkeš """ 304039256daSJuraj Linkeš 305039256daSJuraj Linkeš topology_type: TopologyType 306039256daSJuraj Linkeš 307039256daSJuraj Linkeš _unique_capabilities: ClassVar[dict[str, Self]] = {} 308039256daSJuraj Linkeš 309039256daSJuraj Linkeš def _preprocess_required(self, test_case_or_suite: type["TestProtocol"]) -> None: 310039256daSJuraj Linkeš test_case_or_suite.required_capabilities.discard(test_case_or_suite.topology_type) 311039256daSJuraj Linkeš test_case_or_suite.topology_type = self 312039256daSJuraj Linkeš 313039256daSJuraj Linkeš @classmethod 314039256daSJuraj Linkeš def get_unique(cls, topology_type: TopologyType) -> "TopologyCapability": 315039256daSJuraj Linkeš """Get the capability uniquely identified by `topology_type`. 316039256daSJuraj Linkeš 317039256daSJuraj Linkeš This is a factory method that implements a quasi-enum pattern. 318039256daSJuraj Linkeš The instances of this class are stored in an internal class variable, 319039256daSJuraj Linkeš `_unique_capabilities`. 320039256daSJuraj Linkeš 321039256daSJuraj Linkeš If an instance identified by `topology_type` doesn't exist, 322039256daSJuraj Linkeš it is created and added to `_unique_capabilities`. 323039256daSJuraj Linkeš If it exists, it is returned so that a new identical instance is not created. 324039256daSJuraj Linkeš 325039256daSJuraj Linkeš Args: 326039256daSJuraj Linkeš topology_type: The topology type. 327039256daSJuraj Linkeš 328039256daSJuraj Linkeš Returns: 329039256daSJuraj Linkeš The capability uniquely identified by `topology_type`. 330039256daSJuraj Linkeš """ 331039256daSJuraj Linkeš if topology_type.name not in cls._unique_capabilities: 332039256daSJuraj Linkeš cls._unique_capabilities[topology_type.name] = cls(topology_type) 333039256daSJuraj Linkeš return cls._unique_capabilities[topology_type.name] 334039256daSJuraj Linkeš 335039256daSJuraj Linkeš @classmethod 336039256daSJuraj Linkeš def get_supported_capabilities( 337039256daSJuraj Linkeš cls, sut_node: SutNode, topology: "Topology" 338039256daSJuraj Linkeš ) -> set["TopologyCapability"]: 339039256daSJuraj Linkeš """Overrides :meth:`~Capability.get_supported_capabilities`.""" 340039256daSJuraj Linkeš supported_capabilities = set() 341039256daSJuraj Linkeš topology_capability = cls.get_unique(topology.type) 342039256daSJuraj Linkeš for topology_type in TopologyType: 343039256daSJuraj Linkeš candidate_topology_type = cls.get_unique(topology_type) 344039256daSJuraj Linkeš if candidate_topology_type <= topology_capability: 345039256daSJuraj Linkeš supported_capabilities.add(candidate_topology_type) 346039256daSJuraj Linkeš return supported_capabilities 347039256daSJuraj Linkeš 348039256daSJuraj Linkeš def set_required(self, test_case_or_suite: type["TestProtocol"]) -> None: 349039256daSJuraj Linkeš """The logic for setting the required topology of a test case or suite. 350039256daSJuraj Linkeš 351039256daSJuraj Linkeš Decorators are applied on methods of a class first, then on the class. 352039256daSJuraj Linkeš This means we have to modify test case topologies when processing the test suite topologies. 353039256daSJuraj Linkeš At that point, the test case topologies have been set by the :func:`requires` decorator. 354039256daSJuraj Linkeš The test suite topology only affects the test case topologies 355039256daSJuraj Linkeš if not :attr:`~.topology.TopologyType.default`. 356039256daSJuraj Linkeš """ 357039256daSJuraj Linkeš if inspect.isclass(test_case_or_suite): 358039256daSJuraj Linkeš if self.topology_type is not TopologyType.default: 359039256daSJuraj Linkeš self.add_to_required(test_case_or_suite) 360*c64af3c7SLuca Vizzarro for test_case in test_case_or_suite.get_test_cases(): 361039256daSJuraj Linkeš if test_case.topology_type.topology_type is TopologyType.default: 362039256daSJuraj Linkeš # test case topology has not been set, use the one set by the test suite 363039256daSJuraj Linkeš self.add_to_required(test_case) 364039256daSJuraj Linkeš elif test_case.topology_type > test_case_or_suite.topology_type: 365039256daSJuraj Linkeš raise ConfigurationError( 366039256daSJuraj Linkeš "The required topology type of a test case " 367039256daSJuraj Linkeš f"({test_case.__name__}|{test_case.topology_type}) " 368039256daSJuraj Linkeš "cannot be more complex than that of a suite " 369039256daSJuraj Linkeš f"({test_case_or_suite.__name__}|{test_case_or_suite.topology_type})." 370039256daSJuraj Linkeš ) 371039256daSJuraj Linkeš else: 372039256daSJuraj Linkeš self.add_to_required(test_case_or_suite) 373039256daSJuraj Linkeš 374039256daSJuraj Linkeš def __eq__(self, other) -> bool: 375039256daSJuraj Linkeš """Compare the :attr:`~TopologyCapability.topology_type`s. 376039256daSJuraj Linkeš 377039256daSJuraj Linkeš Args: 378039256daSJuraj Linkeš other: The object to compare with. 379039256daSJuraj Linkeš 380039256daSJuraj Linkeš Returns: 381039256daSJuraj Linkeš :data:`True` if the topology types are the same. 382039256daSJuraj Linkeš """ 383039256daSJuraj Linkeš return self.topology_type == other.topology_type 384039256daSJuraj Linkeš 385039256daSJuraj Linkeš def __lt__(self, other) -> bool: 386039256daSJuraj Linkeš """Compare the :attr:`~TopologyCapability.topology_type`s. 387039256daSJuraj Linkeš 388039256daSJuraj Linkeš Args: 389039256daSJuraj Linkeš other: The object to compare with. 390039256daSJuraj Linkeš 391039256daSJuraj Linkeš Returns: 392039256daSJuraj Linkeš :data:`True` if the instance's topology type is less complex than the compared object's. 393039256daSJuraj Linkeš """ 394039256daSJuraj Linkeš return self.topology_type < other.topology_type 395039256daSJuraj Linkeš 396039256daSJuraj Linkeš def __gt__(self, other) -> bool: 397039256daSJuraj Linkeš """Compare the :attr:`~TopologyCapability.topology_type`s. 398039256daSJuraj Linkeš 399039256daSJuraj Linkeš Args: 400039256daSJuraj Linkeš other: The object to compare with. 401039256daSJuraj Linkeš 402039256daSJuraj Linkeš Returns: 403039256daSJuraj Linkeš :data:`True` if the instance's topology type is more complex than the compared object's. 404039256daSJuraj Linkeš """ 405039256daSJuraj Linkeš return other < self 406039256daSJuraj Linkeš 407039256daSJuraj Linkeš def __le__(self, other) -> bool: 408039256daSJuraj Linkeš """Compare the :attr:`~TopologyCapability.topology_type`s. 409039256daSJuraj Linkeš 410039256daSJuraj Linkeš Args: 411039256daSJuraj Linkeš other: The object to compare with. 412039256daSJuraj Linkeš 413039256daSJuraj Linkeš Returns: 414039256daSJuraj Linkeš :data:`True` if the instance's topology type is less complex or equal than 415039256daSJuraj Linkeš the compared object's. 416039256daSJuraj Linkeš """ 417039256daSJuraj Linkeš return not self > other 418039256daSJuraj Linkeš 419039256daSJuraj Linkeš def __hash__(self): 420039256daSJuraj Linkeš """Each instance is identified by :attr:`topology_type`.""" 421039256daSJuraj Linkeš return self.topology_type.__hash__() 422039256daSJuraj Linkeš 423039256daSJuraj Linkeš def __str__(self): 424039256daSJuraj Linkeš """Easy to read string of class and name of :attr:`topology_type`. 425039256daSJuraj Linkeš 426039256daSJuraj Linkeš Converts :attr:`TopologyType.default` to the actual value. 427039256daSJuraj Linkeš """ 428039256daSJuraj Linkeš name = self.topology_type.name 429039256daSJuraj Linkeš if self.topology_type is TopologyType.default: 430039256daSJuraj Linkeš name = TopologyType.get_from_value(self.topology_type.value).name 431039256daSJuraj Linkeš return f"{type(self.topology_type).__name__}.{name}" 432039256daSJuraj Linkeš 433039256daSJuraj Linkeš def __repr__(self): 434039256daSJuraj Linkeš """Easy to read string of class and name of :attr:`topology_type`.""" 435039256daSJuraj Linkeš return self.__str__() 436039256daSJuraj Linkeš 437039256daSJuraj Linkeš 438566201aeSJuraj Linkešclass TestProtocol(Protocol): 439566201aeSJuraj Linkeš """Common test suite and test case attributes.""" 440566201aeSJuraj Linkeš 441566201aeSJuraj Linkeš #: Whether to skip the test case or suite. 442566201aeSJuraj Linkeš skip: ClassVar[bool] = False 443566201aeSJuraj Linkeš #: The reason for skipping the test case or suite. 444566201aeSJuraj Linkeš skip_reason: ClassVar[str] = "" 445039256daSJuraj Linkeš #: The topology type of the test case or suite. 446039256daSJuraj Linkeš topology_type: ClassVar[TopologyCapability] = TopologyCapability(TopologyType.default) 447eebfb5bbSJuraj Linkeš #: The capabilities the test case or suite requires in order to be executed. 448eebfb5bbSJuraj Linkeš required_capabilities: ClassVar[set[Capability]] = set() 449566201aeSJuraj Linkeš 450566201aeSJuraj Linkeš @classmethod 451*c64af3c7SLuca Vizzarro def get_test_cases(cls) -> list[type["TestCase"]]: 452566201aeSJuraj Linkeš """Get test cases. Should be implemented by subclasses containing test cases. 453566201aeSJuraj Linkeš 454566201aeSJuraj Linkeš Raises: 455566201aeSJuraj Linkeš NotImplementedError: The subclass does not implement the method. 456566201aeSJuraj Linkeš """ 457566201aeSJuraj Linkeš raise NotImplementedError() 458eebfb5bbSJuraj Linkeš 459eebfb5bbSJuraj Linkeš 460c89d0038SJuraj Linkešdef requires( 461c89d0038SJuraj Linkeš *nic_capabilities: NicCapability, 462039256daSJuraj Linkeš topology_type: TopologyType = TopologyType.default, 463c89d0038SJuraj Linkeš) -> Callable[[type[TestProtocol]], type[TestProtocol]]: 464c89d0038SJuraj Linkeš """A decorator that adds the required capabilities to a test case or test suite. 465c89d0038SJuraj Linkeš 466c89d0038SJuraj Linkeš Args: 467c89d0038SJuraj Linkeš nic_capabilities: The NIC capabilities that are required by the test case or test suite. 468039256daSJuraj Linkeš topology_type: The topology type the test suite or case requires. 469c89d0038SJuraj Linkeš 470c89d0038SJuraj Linkeš Returns: 471c89d0038SJuraj Linkeš The decorated test case or test suite. 472c89d0038SJuraj Linkeš """ 473c89d0038SJuraj Linkeš 474c89d0038SJuraj Linkeš def add_required_capability(test_case_or_suite: type[TestProtocol]) -> type[TestProtocol]: 475c89d0038SJuraj Linkeš for nic_capability in nic_capabilities: 476c89d0038SJuraj Linkeš decorated_nic_capability = DecoratedNicCapability.get_unique(nic_capability) 477c89d0038SJuraj Linkeš decorated_nic_capability.add_to_required(test_case_or_suite) 478c89d0038SJuraj Linkeš 479039256daSJuraj Linkeš topology_capability = TopologyCapability.get_unique(topology_type) 480039256daSJuraj Linkeš topology_capability.set_required(test_case_or_suite) 481039256daSJuraj Linkeš 482c89d0038SJuraj Linkeš return test_case_or_suite 483c89d0038SJuraj Linkeš 484c89d0038SJuraj Linkeš return add_required_capability 485c89d0038SJuraj Linkeš 486c89d0038SJuraj Linkeš 487eebfb5bbSJuraj Linkešdef get_supported_capabilities( 488eebfb5bbSJuraj Linkeš sut_node: SutNode, 489eebfb5bbSJuraj Linkeš topology_config: Topology, 490eebfb5bbSJuraj Linkeš capabilities_to_check: set[Capability], 491eebfb5bbSJuraj Linkeš) -> set[Capability]: 492eebfb5bbSJuraj Linkeš """Probe the environment for `capabilities_to_check` and return the supported ones. 493eebfb5bbSJuraj Linkeš 494eebfb5bbSJuraj Linkeš Args: 495eebfb5bbSJuraj Linkeš sut_node: The SUT node to check for capabilities. 496eebfb5bbSJuraj Linkeš topology_config: The topology config to check for capabilities. 497eebfb5bbSJuraj Linkeš capabilities_to_check: The capabilities to check. 498eebfb5bbSJuraj Linkeš 499eebfb5bbSJuraj Linkeš Returns: 500eebfb5bbSJuraj Linkeš The capabilities supported by the environment. 501eebfb5bbSJuraj Linkeš """ 502eebfb5bbSJuraj Linkeš callbacks = set() 503eebfb5bbSJuraj Linkeš for capability_to_check in capabilities_to_check: 504eebfb5bbSJuraj Linkeš callbacks.add(capability_to_check.register_to_check()) 505eebfb5bbSJuraj Linkeš supported_capabilities = set() 506eebfb5bbSJuraj Linkeš for callback in callbacks: 507eebfb5bbSJuraj Linkeš supported_capabilities.update(callback(sut_node, topology_config)) 508eebfb5bbSJuraj Linkeš 509eebfb5bbSJuraj Linkeš return supported_capabilities 510