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