Applied to next-dts, thanks. On Fri, Jul 4, 2025 at 11:30 AM Luca Vizzarro <luca.vizza...@arm.com> wrote:
> Add a generic blocking app class to allow a test writer to run any app > in the background. Make the original BlockingDPDKApp class inherit from > this one. Implement the new BlockingApp class in the packet_capture test > suite. > > Signed-off-by: Luca Vizzarro <luca.vizza...@arm.com> > Reviewed-by: Paul Szczepanek <paul.szczepa...@arm.com> > --- > dts/framework/remote_session/blocking_app.py | 135 +++++++++++++++++++ > dts/framework/remote_session/dpdk_app.py | 80 ----------- > dts/tests/TestSuite_packet_capture.py | 66 +++------ > 3 files changed, 154 insertions(+), 127 deletions(-) > create mode 100644 dts/framework/remote_session/blocking_app.py > delete mode 100644 dts/framework/remote_session/dpdk_app.py > > diff --git a/dts/framework/remote_session/blocking_app.py > b/dts/framework/remote_session/blocking_app.py > new file mode 100644 > index 0000000000..8de536c259 > --- /dev/null > +++ b/dts/framework/remote_session/blocking_app.py > @@ -0,0 +1,135 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2025 Arm Limited > + > +"""Class to run blocking apps in the background. > + > +The class won't automatically start the app. The start-up is done as part > of the > +:meth:`BlockingApp.wait_until_ready` method, which will return execution > to the caller only > +when the desired stdout has been returned by the app. Usually this is > used to detect when the app > +has been loaded and ready to be used. > + > +This module also provides the class :class:`BlockingDPDKApp` useful to > run any DPDK app from the > +DPDK build dir. > + > +Example: > + ..code:: python > + > + pdump = BlockingDPDKApp( > + PurePath("app/dpdk-pdump"), > + app_params="--pdump 'port=0,queue=*,rx-dev=/tmp/rx-dev.pcap'" > + ) > + pdump.wait_until_ready("65535") # start app > + > + # pdump is now ready to capture > + > + pdump.close() # stop/close app > +""" > + > +from pathlib import PurePath > +from typing import Generic, TypeVar, cast > + > +from typing_extensions import Self > + > +from framework.context import get_ctx > +from framework.params import Params > +from framework.params.eal import EalParams > +from framework.remote_session.dpdk_shell import compute_eal_params > +from framework.remote_session.interactive_shell import InteractiveShell > +from framework.testbed_model.node import Node > + > +P = TypeVar("P", bound=Params) > + > + > +class BlockingApp(InteractiveShell, Generic[P]): > + """Class to manage generic blocking apps.""" > + > + _app_params: P > + > + def __init__( > + self, > + node: Node, > + path: PurePath, > + name: str | None = None, > + privileged: bool = False, > + app_params: P | str = "", > + ) -> None: > + """Constructor. > + > + Args: > + node: The node to run the app on. > + path: Path to the application on the node. > + name: Name to identify this application. > + privileged: Run as privileged user. > + app_params: The application parameters. Can be of any type > inheriting :class:`Params` or > + a plain string. > + """ > + if isinstance(app_params, str): > + params = Params() > + params.append_str(app_params) > + app_params = cast(P, params) > + > + self._path = path > + > + super().__init__(node, name, privileged, app_params) > + > + @property > + def path(self) -> PurePath: > + """The path of the DPDK app relative to the DPDK build folder.""" > + return self._path > + > + def wait_until_ready(self, end_token: str) -> Self: > + """Start app and wait until ready. > + > + Args: > + end_token: The string at the end of a line that indicates the > app is ready. > + > + Returns: > + Itself. > + """ > + self.start_application(end_token) > + return self > + > + def close(self) -> None: > + """Close the application. > + > + Sends a SIGINT to close the application. > + """ > + self.send_command("\x03") > + super().close() > + > + > +PE = TypeVar("PE", bound=EalParams) > + > + > +class BlockingDPDKApp(BlockingApp, Generic[PE]): > + """Class to manage blocking DPDK apps on the SUT.""" > + > + _app_params: PE > + > + def __init__( > + self, > + path: PurePath, > + name: str | None = None, > + privileged: bool = True, > + app_params: PE | str = "", > + ) -> None: > + """Constructor. > + > + Args: > + path: Path relative to the DPDK build to the executable. > + name: Name to identify this application. > + privileged: Run as privileged user. > + app_params: The application parameters. If a string or an > incomplete :class:`EalParams` > + object are passed, the EAL params are computed based on > the current context. > + """ > + if isinstance(app_params, str): > + eal_params = compute_eal_params() > + eal_params.append_str(app_params) > + app_params = cast(PE, eal_params) > + else: > + app_params = cast(PE, compute_eal_params(app_params)) > + > + node = get_ctx().sut_node > + path = > PurePath(get_ctx().dpdk_build.remote_dpdk_build_dir).joinpath(self.path) > + > + super().__init__(node, path, name, privileged, app_params) > diff --git a/dts/framework/remote_session/dpdk_app.py > b/dts/framework/remote_session/dpdk_app.py > deleted file mode 100644 > index dc4b817bdd..0000000000 > --- a/dts/framework/remote_session/dpdk_app.py > +++ /dev/null > @@ -1,80 +0,0 @@ > -# SPDX-License-Identifier: BSD-3-Clause > -# Copyright(c) 2025 Arm Limited > - > -"""Class to run blocking DPDK apps in the background. > - > -The class won't automatically start the app. The start-up is done as part > of the > -:meth:`BlockingDPDKApp.wait_until_ready` method, which will return > execution to the caller only > -when the desired stdout has been returned by the app. Usually this is > used to detect when the app > -has been loaded and ready to be used. > - > -Example: > - ..code:: python > - > - pdump = BlockingDPDKApp( > - PurePath("app/dpdk-pdump"), > - app_params="--pdump 'port=0,queue=*,rx-dev=/tmp/rx-dev.pcap'" > - ) > - pdump.wait_until_ready("65535") # start app > - > - # pdump is now ready to capture > - > - pdump.close() # stop/close app > -""" > - > -from pathlib import PurePath > - > -from framework.params.eal import EalParams > -from framework.remote_session.dpdk_shell import DPDKShell > - > - > -class BlockingDPDKApp(DPDKShell): > - """Class to manage blocking DPDK apps.""" > - > - def __init__( > - self, > - path: PurePath, > - name: str | None = None, > - privileged: bool = True, > - app_params: EalParams | str = "", > - ) -> None: > - """Constructor. > - > - Overrides :meth:`~.dpdk_shell.DPDKShell.__init__`. > - > - Args: > - path: Path relative to the DPDK build to the executable. > - name: Name to identify this application. > - privileged: Run as privileged user. > - app_params: The application parameters. If a string or an > incomplete :class:`EalParams` > - object are passed, the EAL params are computed based on > the current context. > - """ > - if isinstance(app_params, str): > - eal_params = EalParams() > - eal_params.append_str(app_params) > - app_params = eal_params > - > - self._path = path > - > - super().__init__(name, privileged, app_params) > - > - @property > - def path(self) -> PurePath: > - """The path of the DPDK app relative to the DPDK build folder.""" > - return self._path > - > - def wait_until_ready(self, end_token: str) -> None: > - """Start app and wait until ready. > - > - Args: > - end_token: The string at the end of a line that indicates the > app is ready. > - """ > - self.start_application(end_token) > - > - def close(self) -> None: > - """Close the application. > - > - Sends a SIGINT to close the application. > - """ > - self.send_command("\x03") > - super().close() > diff --git a/dts/tests/TestSuite_packet_capture.py > b/dts/tests/TestSuite_packet_capture.py > index e162bded87..bad243a571 100644 > --- a/dts/tests/TestSuite_packet_capture.py > +++ b/dts/tests/TestSuite_packet_capture.py > @@ -24,12 +24,10 @@ > from scapy.layers.sctp import SCTP > from scapy.packet import Packet, Raw, raw > from scapy.utils import rdpcap > -from typing_extensions import Self > > -from framework.context import get_ctx > from framework.params import Params > +from framework.remote_session.blocking_app import BlockingApp > from framework.remote_session.dpdk_shell import compute_eal_params > -from framework.remote_session.interactive_shell import InteractiveShell > from framework.remote_session.testpmd_shell import TestPmdShell > from framework.settings import SETTINGS > from framework.test_suite import TestSuite, func_test > @@ -61,57 +59,31 @@ class DumpcapParams(Params): > packet_filter: str | None = field(default=None, > metadata=Params.short("f")) > > > -class Dumpcap(InteractiveShell): > - """Class to spawn and manage a dpdk-dumpcap process. > - > - The dpdk-dumpcap is a DPDK app but instead of providing a regular > DPDK EAL interface to the > - user, it replicates the Wireshark dumpcap app. > - """ > - > - _app_params: DumpcapParams > - > - def __init__(self, params: DumpcapParams) -> None: > - """Extends > :meth:`~.interactive_shell.InteractiveShell.__init__`.""" > - self.ctx = get_ctx() > - eal_params = compute_eal_params() > - params.lcore_list = eal_params.lcore_list > - params.file_prefix = eal_params.prefix > - > - super().__init__(self.ctx.sut_node, name=None, privileged=True, > app_params=params) > - > - @property > - def path(self) -> PurePath: > - """Path to the shell executable.""" > - return > PurePath(self.ctx.dpdk_build.remote_dpdk_build_dir).joinpath("app/dpdk-dumpcap") > - > - def wait_until_ready(self) -> Self: > - """Start app and wait until ready.""" > - self.start_application(f"Capturing on > '{self._app_params.interface}'") > - return self > - > - def close(self) -> None: > - """Close the application. > - > - Sends a SIGINT to close the application. > - """ > - self.send_command("\x03") > - super().close() > - > - > @requires(topology_type=TopologyType.two_links) > class TestPacketCapture(TestSuite): > """Packet Capture TestSuite. > > Attributes: > - packets: List of packets to send for testing pdump. > - rx_pcap_path: The remote path where to create the Rx packets pcap > with pdump. > - tx_pcap_path: The remote path where to create the Tx packets pcap > with pdump. > + packets: List of packets to send for testing dumpcap. > + rx_pcap_path: The remote path where to create the Rx packets pcap > with dumpcap. > + tx_pcap_path: The remote path where to create the Tx packets pcap > with dumpcap. > """ > > packets: list[Packet] > rx_pcap_path: PurePath > tx_pcap_path: PurePath > > + def _run_dumpcap(self, params: DumpcapParams) -> BlockingApp: > + eal_params = compute_eal_params() > + params.lcore_list = eal_params.lcore_list > + params.file_prefix = eal_params.prefix > + return BlockingApp( > + self._ctx.sut_node, > + self._ctx.dpdk_build.get_app("dumpcap"), > + app_params=params, > + privileged=True, > + ).wait_until_ready(f"Capturing on '{params.interface}'") > + > def set_up_suite(self) -> None: > """Test suite setup. > > @@ -147,21 +119,21 @@ def _load_pcap_packets(self, remote_pcap_path: > PurePath) -> list[Packet]: > def _send_and_dump( > self, packet_filter: str | None = None, rx_only: bool = False > ) -> list[Packet]: > - dumpcap_rx = Dumpcap( > + dumpcap_rx = self._run_dumpcap( > DumpcapParams( > interface=self.topology.sut_port_ingress.pci, > output_pcap_path=self.rx_pcap_path, > packet_filter=packet_filter, > ) > - ).wait_until_ready() > + ) > if not rx_only: > - dumpcap_tx = Dumpcap( > + dumpcap_tx = self._run_dumpcap( > DumpcapParams( > interface=self.topology.sut_port_egress.pci, > output_pcap_path=self.tx_pcap_path, > packet_filter=packet_filter, > ) > - ).wait_until_ready() > + ) > > received_packets = self.send_packets_and_capture( > self.packets, PacketFilteringConfig(no_lldp=False) > -- > 2.43.0 > >