xref: /dpdk/dts/framework/testbed_model/capability.py (revision c64af3c7a80ee76a771017b56487be1062c7ae1f)
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