Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/126841/?format=api
http://patchwork.dpdk.org/api/patches/126841/?format=api", "web_url": "http://patchwork.dpdk.org/project/dpdk/patch/20230512192540.401-3-jspewock@iol.unh.edu/", "project": { "id": 1, "url": "http://patchwork.dpdk.org/api/projects/1/?format=api", "name": "DPDK", "link_name": "dpdk", "list_id": "dev.dpdk.org", "list_email": "dev@dpdk.org", "web_url": "http://core.dpdk.org", "scm_url": "git://dpdk.org/dpdk", "webscm_url": "http://git.dpdk.org/dpdk", "list_archive_url": "https://inbox.dpdk.org/dev", "list_archive_url_format": "https://inbox.dpdk.org/dev/{}", "commit_url_format": "" }, "msgid": "<20230512192540.401-3-jspewock@iol.unh.edu>", "list_archive_url": "https://inbox.dpdk.org/dev/20230512192540.401-3-jspewock@iol.unh.edu", "date": "2023-05-12T19:25:43", "name": "[RFC,v2,1/2] dts: add smoke tests", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": true, "hash": "fc69a37099a65285189a464241a405295b1d737f", "submitter": { "id": 2772, "url": "http://patchwork.dpdk.org/api/people/2772/?format=api", "name": "Jeremy Spewock", "email": "jspewock@iol.unh.edu" }, "delegate": { "id": 1, "url": "http://patchwork.dpdk.org/api/users/1/?format=api", "username": "tmonjalo", "first_name": "Thomas", "last_name": "Monjalon", "email": "thomas@monjalon.net" }, "mbox": "http://patchwork.dpdk.org/project/dpdk/patch/20230512192540.401-3-jspewock@iol.unh.edu/mbox/", "series": [ { "id": 27990, "url": "http://patchwork.dpdk.org/api/series/27990/?format=api", "web_url": "http://patchwork.dpdk.org/project/dpdk/list/?series=27990", "date": "2023-05-12T19:25:40", "name": "add DTS smoke tests", "version": 2, "mbox": "http://patchwork.dpdk.org/series/27990/mbox/" } ], "comments": "http://patchwork.dpdk.org/api/patches/126841/comments/", "check": "warning", "checks": "http://patchwork.dpdk.org/api/patches/126841/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<dev-bounces@dpdk.org>", "X-Original-To": "patchwork@inbox.dpdk.org", "Delivered-To": "patchwork@inbox.dpdk.org", "Received": [ "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 7C43642AE8;\n\tFri, 12 May 2023 21:27:56 +0200 (CEST)", "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 6E843406BA;\n\tFri, 12 May 2023 21:27:56 +0200 (CEST)", "from mail-pl1-f226.google.com (mail-pl1-f226.google.com\n [209.85.214.226])\n by mails.dpdk.org (Postfix) with ESMTP id 32DDF406B3\n for <dev@dpdk.org>; Fri, 12 May 2023 21:27:55 +0200 (CEST)", "by mail-pl1-f226.google.com with SMTP id\n d9443c01a7336-1ab05018381so95667585ad.2\n for <dev@dpdk.org>; Fri, 12 May 2023 12:27:55 -0700 (PDT)", "from postal.iol.unh.edu (postal.iol.unh.edu. [132.177.123.84])\n by smtp-relay.gmail.com with ESMTPS id\n ju13-20020a170903428d00b001a6484fd026sm680934plb.115.2023.05.12.12.27.54\n (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);\n Fri, 12 May 2023 12:27:54 -0700 (PDT)", "from iol.unh.edu (unknown\n [IPv6:2606:4100:3880:1271:90f9:1b64:f6e6:867f])\n by postal.iol.unh.edu (Postfix) with ESMTP id 72ADD605246B;\n Fri, 12 May 2023 15:27:53 -0400 (EDT)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=iol.unh.edu; s=unh-iol; t=1683919674; x=1686511674;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=eFi9qByGtlFnJiQN83k5R0gUjmkvYgFEcabbCTe2qB8=;\n b=evK+wgQ/PB1rMXTHfCyQp/fDPaxPK/mZx48IDTzg9hZR6QRLwzZSEpevwUv+HmTTTk\n SHRfcgIFOT3Uw7w1ck6KMzMvbu6652sNh+n94sjlj5ayC2VsCXhzdMG8N60HsxKuO4Wn\n Zpb2vqcRRoKQYQzJFff9gjIb+xjYOgq5K183Q=", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20221208; t=1683919674; x=1686511674;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=eFi9qByGtlFnJiQN83k5R0gUjmkvYgFEcabbCTe2qB8=;\n b=YrRJAtUB2zxVO6fUTjfIV7cxCif6eJ7Tz7pT4hxVUQXyE08WB1c/dmSGNNidcq/riV\n 5FWL6lcXUdA3av+VtAy3vRDn5+Pv5ff+Kb2+/5RPAwRD8HFmb1OMHjo1CNsVy/1LFrF7\n 4LXBWBa1ic5xanvp/S4thboNrKVlXvH/eGdZXYzN1n15oix/ZT7zjbhdg0XAjJLEmkdi\n 6e0fFg3Ui3rKPUEHCeQrGt5cbfVsufIFqtVHrh9GZxChFNw9hR8rIH6ge9CUe6tf/nUH\n 2U6CYpJFxoPVyGjU9j+/Im9O6W+3a/ZHUFChiEzx3nX5x0LvVzRwV+98LDe/Vx7MpOQx\n Ga4Q==", "X-Gm-Message-State": "AC+VfDx0aNy+1OkHKIwhLBLlMlTyUl3vnqFxydwu23eRj2JhiSChis6m\n JNgnAG9Qo5ORBgZ/XvgMursJuHU4LeFUVrJUa5AldRrvnHq+ogYLYnEtPxPxoOs216CDhCI5e7k\n 6wRRO1Q9CrTIG9ScgMbsuednSe4pig8/oeOOcdNOcfdEkQxDUCRHFIE2kHhbAsIO9XmP1/omLXX\n IqD2cL0A==", "X-Google-Smtp-Source": "\n ACHHUZ5fPK/VH8ZLeYdYjvijUXeex19+reKLltAbZQP+pTaAkYbP2oHs21k33oFHpcjzeqiAZxv1pPJ+Jw5G", "X-Received": "by 2002:a17:903:22c7:b0:1a6:71b1:a0b9 with SMTP id\n y7-20020a17090322c700b001a671b1a0b9mr32345308plg.47.1683919674260;\n Fri, 12 May 2023 12:27:54 -0700 (PDT)", "X-Relaying-Domain": "iol.unh.edu", "From": "jspewock@iol.unh.edu", "To": "dev@dpdk.org", "Cc": "Jeremy Spewock <jspewock@iol.unh.edu>", "Subject": "[RFC v2 1/2] dts: add smoke tests", "Date": "Fri, 12 May 2023 15:25:43 -0400", "Message-Id": "<20230512192540.401-3-jspewock@iol.unh.edu>", "X-Mailer": "git-send-email 2.40.1", "In-Reply-To": "<20230512192540.401-2-jspewock@iol.unh.edu>", "References": "<20230512192540.401-2-jspewock@iol.unh.edu>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "dev@dpdk.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "DPDK patches and discussions <dev.dpdk.org>", "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>", "List-Archive": "<http://mails.dpdk.org/archives/dev/>", "List-Post": "<mailto:dev@dpdk.org>", "List-Help": "<mailto:dev-request@dpdk.org?subject=help>", "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>", "Errors-To": "dev-bounces@dpdk.org" }, "content": "From: Jeremy Spewock <jspewock@iol.unh.edu>\n\nAdds a new test suite for running smoke tests that verify general\nconfiguration aspects of the system under test. If any of these tests\nfail, the DTS execution terminates as part of a \"fail-fast\" model.\n\nSigned-off-by: Jeremy Spewock <jspewock@iol.unh.edu>\n---\n dts/conf.yaml | 9 ++\n dts/framework/config/__init__.py | 21 +++++\n dts/framework/config/conf_yaml_schema.json | 32 ++++++-\n dts/framework/dts.py | 19 +++-\n dts/framework/exception.py | 11 +++\n dts/framework/remote_session/os_session.py | 6 +-\n .../remote_session/remote/__init__.py | 28 ++++++\n dts/framework/test_result.py | 13 ++-\n dts/framework/test_suite.py | 24 ++++-\n dts/framework/testbed_model/__init__.py | 5 +\n .../interactive_apps/__init__.py | 6 ++\n .../interactive_apps/interactive_command.py | 57 +++++++++++\n .../interactive_apps/testpmd_driver.py | 24 +++++\n dts/framework/testbed_model/node.py | 2 +\n dts/framework/testbed_model/sut_node.py | 6 ++\n dts/tests/TestSuite_smoke_tests.py | 94 +++++++++++++++++++\n 16 files changed, 348 insertions(+), 9 deletions(-)\n create mode 100644 dts/framework/testbed_model/interactive_apps/__init__.py\n create mode 100644 dts/framework/testbed_model/interactive_apps/interactive_command.py\n create mode 100644 dts/framework/testbed_model/interactive_apps/testpmd_driver.py\n create mode 100644 dts/tests/TestSuite_smoke_tests.py", "diff": "diff --git a/dts/conf.yaml b/dts/conf.yaml\nindex a9bd8a3e..042ef954 100644\n--- a/dts/conf.yaml\n+++ b/dts/conf.yaml\n@@ -10,13 +10,22 @@ executions:\n compiler_wrapper: ccache\n perf: false\n func: true\n+ nics: #physical devices to be used for testing\n+ - addresses:\n+ - \"0000:11:00.0\"\n+ - \"0000:11:00.1\"\n+ driver: \"i40e\"\n+ vdevs: #names of virtual devices to be used for testing\n+ - \"crypto_openssl\"\n test_suites:\n+ - smoke_tests\n - hello_world\n system_under_test: \"SUT 1\"\n nodes:\n - name: \"SUT 1\"\n hostname: sut1.change.me.localhost\n user: root\n+ password: \"\"\n arch: x86_64\n os: linux\n lcores: \"\"\ndiff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py\nindex ebb0823f..f3b8b6e3 100644\n--- a/dts/framework/config/__init__.py\n+++ b/dts/framework/config/__init__.py\n@@ -106,6 +106,21 @@ def from_dict(d: dict) -> \"NodeConfiguration\":\n hugepages=hugepage_config,\n )\n \n+@dataclass(slots=True, frozen=True)\n+class NICConfiguration:\n+ addresses: list[str]\n+ driver: str\n+\n+ @staticmethod\n+ def from_dict(d:dict) -> \"NICConfiguration\":\n+ return NICConfiguration(\n+ addresses=[addr for addr in d.get(\"addresses\", [])],\n+ driver=d.get(\"driver\")\n+ )\n+ @staticmethod\n+ def from_list(l:list[dict]) -> list[\"NICConfiguration\"]:\n+ return [] + [NICConfiguration.from_dict(x) for x in l]\n+\n \n @dataclass(slots=True, frozen=True)\n class BuildTargetConfiguration:\n@@ -157,6 +172,8 @@ class ExecutionConfiguration:\n func: bool\n test_suites: list[TestSuiteConfig]\n system_under_test: NodeConfiguration\n+ nics: list[NICConfiguration]\n+ vdevs: list[str]\n \n @staticmethod\n def from_dict(d: dict, node_map: dict) -> \"ExecutionConfiguration\":\n@@ -166,7 +183,9 @@ def from_dict(d: dict, node_map: dict) -> \"ExecutionConfiguration\":\n test_suites: list[TestSuiteConfig] = list(\n map(TestSuiteConfig.from_dict, d[\"test_suites\"])\n )\n+ nic_conf: NICConfiguration = NICConfiguration.from_list(d['nics'])\n sut_name = d[\"system_under_test\"]\n+ list_of_vdevs = d[\"vdevs\"]\n assert sut_name in node_map, f\"Unknown SUT {sut_name} in execution {d}\"\n \n return ExecutionConfiguration(\n@@ -174,7 +193,9 @@ def from_dict(d: dict, node_map: dict) -> \"ExecutionConfiguration\":\n perf=d[\"perf\"],\n func=d[\"func\"],\n test_suites=test_suites,\n+ nics=nic_conf,\n system_under_test=node_map[sut_name],\n+ vdevs=list_of_vdevs\n )\n \n \ndiff --git a/dts/framework/config/conf_yaml_schema.json b/dts/framework/config/conf_yaml_schema.json\nindex ca2d4a1e..603859de 100644\n--- a/dts/framework/config/conf_yaml_schema.json\n+++ b/dts/framework/config/conf_yaml_schema.json\n@@ -40,6 +40,18 @@\n \"mscv\"\n ]\n },\n+ \"single_nic\" : {\n+ \"type\":\"object\",\n+ \"description\": \"an object that holds nic information\",\n+ \"properties\": {\n+ \"addresses\": {\n+ \"type\":\"array\",\n+ \"items\": {\n+ \"type\":\"string\"\n+ }\n+ }\n+ }\n+ },\n \"build_target\": {\n \"type\": \"object\",\n \"description\": \"Targets supported by DTS\",\n@@ -97,7 +109,8 @@\n \"test_suite\": {\n \"type\": \"string\",\n \"enum\": [\n- \"hello_world\"\n+ \"hello_world\",\n+ \"smoke_tests\"\n ]\n },\n \"test_target\": {\n@@ -211,6 +224,23 @@\n ]\n }\n },\n+ \"nics\": {\n+ \"type\": \"array\",\n+ \"items\": {\n+ \"oneOf\": [\n+ {\n+ \"$ref\": \"#/definitions/single_nic\"\n+ }\n+ ]\n+ }\n+ },\n+ \"vdevs\": {\n+ \"description\": \"Names of vdevs to be used in execution\",\n+ \"type\": \"array\",\n+ \"items\": {\n+ \"type\":\"string\"\n+ }\n+ },\n \"system_under_test\": {\n \"$ref\": \"#/definitions/node_name\"\n }\ndiff --git a/dts/framework/dts.py b/dts/framework/dts.py\nindex 05022845..0d03e158 100644\n--- a/dts/framework/dts.py\n+++ b/dts/framework/dts.py\n@@ -5,6 +5,8 @@\n \n import sys\n \n+from .exception import BlockingTestSuiteError\n+\n from .config import CONFIGURATION, BuildTargetConfiguration, ExecutionConfiguration\n from .logger import DTSLOG, getLogger\n from .test_result import BuildTargetResult, DTSResult, ExecutionResult, Result\n@@ -49,6 +51,7 @@ def run_all() -> None:\n nodes[sut_node.name] = sut_node\n \n if sut_node:\n+ #SMOKE TEST EXECUTION GOES HERE!\n _run_execution(sut_node, execution, result)\n \n except Exception as e:\n@@ -118,7 +121,7 @@ def _run_build_target(\n \n try:\n sut_node.set_up_build_target(build_target)\n- result.dpdk_version = sut_node.dpdk_version\n+ # result.dpdk_version = sut_node.dpdk_version\n build_target_result.update_setup(Result.PASS)\n except Exception as e:\n dts_logger.exception(\"Build target setup failed.\")\n@@ -146,6 +149,7 @@ def _run_suites(\n with possibly only a subset of test cases.\n If no subset is specified, run all test cases.\n \"\"\"\n+ end_execution = False\n for test_suite_config in execution.test_suites:\n try:\n full_suite_path = f\"tests.TestSuite_{test_suite_config.test_suite}\"\n@@ -160,13 +164,24 @@ def _run_suites(\n \n else:\n for test_suite_class in test_suite_classes:\n+ #HERE NEEDS CHANGING\n test_suite = test_suite_class(\n sut_node,\n test_suite_config.test_cases,\n execution.func,\n build_target_result,\n+ sut_node._build_target_config,\n+ result\n )\n- test_suite.run()\n+ try:\n+ test_suite.run()\n+ except BlockingTestSuiteError as e:\n+ dts_logger.exception(\"An error occurred within a blocking TestSuite, execution will now end.\")\n+ result.add_error(e)\n+ end_execution = True\n+ #if a blocking test failed and we need to bail out of suite executions\n+ if end_execution:\n+ break\n \n \n def _exit_dts() -> None:\ndiff --git a/dts/framework/exception.py b/dts/framework/exception.py\nindex ca353d98..4e3f63d1 100644\n--- a/dts/framework/exception.py\n+++ b/dts/framework/exception.py\n@@ -25,6 +25,7 @@ class ErrorSeverity(IntEnum):\n SSH_ERR = 4\n DPDK_BUILD_ERR = 10\n TESTCASE_VERIFY_ERR = 20\n+ BLOCKING_TESTSUITE_ERR = 25\n \n \n class DTSError(Exception):\n@@ -144,3 +145,13 @@ def __init__(self, value: str):\n \n def __str__(self) -> str:\n return repr(self.value)\n+\n+class BlockingTestSuiteError(DTSError):\n+ suite_name: str\n+ severity: ClassVar[ErrorSeverity] = ErrorSeverity.BLOCKING_TESTSUITE_ERR\n+\n+ def __init__(self, suite_name:str) -> None:\n+ self.suite_name = suite_name\n+\n+ def __str__(self) -> str:\n+ return f\"Blocking suite {self.suite_name} failed.\"\ndiff --git a/dts/framework/remote_session/os_session.py b/dts/framework/remote_session/os_session.py\nindex 4c48ae25..22776bc1 100644\n--- a/dts/framework/remote_session/os_session.py\n+++ b/dts/framework/remote_session/os_session.py\n@@ -12,7 +12,9 @@\n from framework.testbed_model import LogicalCore\n from framework.utils import EnvVarsDict, MesonArgs\n \n-from .remote import CommandResult, RemoteSession, create_remote_session\n+from .remote import CommandResult, RemoteSession, create_remote_session, create_interactive_session\n+\n+from paramiko import SSHClient\n \n \n class OSSession(ABC):\n@@ -26,6 +28,7 @@ class OSSession(ABC):\n name: str\n _logger: DTSLOG\n remote_session: RemoteSession\n+ _interactive_session: SSHClient\n \n def __init__(\n self,\n@@ -37,6 +40,7 @@ def __init__(\n self.name = name\n self._logger = logger\n self.remote_session = create_remote_session(node_config, name, logger)\n+ self._interactive_session = create_interactive_session(node_config, name, logger)\n \n def close(self, force: bool = False) -> None:\n \"\"\"\ndiff --git a/dts/framework/remote_session/remote/__init__.py b/dts/framework/remote_session/remote/__init__.py\nindex 8a151221..abca8edc 100644\n--- a/dts/framework/remote_session/remote/__init__.py\n+++ b/dts/framework/remote_session/remote/__init__.py\n@@ -9,8 +9,36 @@\n from .remote_session import CommandResult, RemoteSession\n from .ssh_session import SSHSession\n \n+from paramiko import SSHClient, AutoAddPolicy\n+from framework.utils import GREEN\n \n def create_remote_session(\n node_config: NodeConfiguration, name: str, logger: DTSLOG\n ) -> RemoteSession:\n return SSHSession(node_config, name, logger)\n+\n+def create_interactive_session(\n+ node_config: NodeConfiguration, name: str, logger: DTSLOG\n+) -> SSHClient:\n+ \"\"\"\n+ Creates a paramiko SSH session that is designed to be used for interactive shells\n+\n+ This session is meant to be used on an \"as needed\" basis and may never be utilized\n+ \"\"\"\n+ client: SSHClient = SSHClient()\n+ client.set_missing_host_key_policy(AutoAddPolicy)\n+ ip: str = node_config.hostname\n+ logger.info(GREEN(f\"Connecting to host {ip}\"))\n+ #Preset to 22 because paramiko doesn't accept None\n+ port: int = 22\n+ if \":\" in node_config.hostname:\n+ ip, port = node_config.hostname.split(\":\")\n+ port = int(port)\n+ client.connect(\n+ ip,\n+ username=node_config.user,\n+ port=port,\n+ password=node_config.password or \"\",\n+ timeout=20 if port else 10\n+ )\n+ return client\ndiff --git a/dts/framework/test_result.py b/dts/framework/test_result.py\nindex 74391982..77202ae2 100644\n--- a/dts/framework/test_result.py\n+++ b/dts/framework/test_result.py\n@@ -8,6 +8,7 @@\n import os.path\n from collections.abc import MutableSequence\n from enum import Enum, auto\n+from typing import Dict\n \n from .config import (\n OS,\n@@ -67,12 +68,13 @@ class Statistics(dict):\n Using a dict provides a convenient way to format the data.\n \"\"\"\n \n- def __init__(self, dpdk_version):\n+ def __init__(self, output_info: Dict[str, str] | None):\n super(Statistics, self).__init__()\n for result in Result:\n self[result.name] = 0\n self[\"PASS RATE\"] = 0.0\n- self[\"DPDK VERSION\"] = dpdk_version\n+ if output_info:\n+ for info_key, info_val in output_info.items(): self[info_key] = info_val\n \n def __iadd__(self, other: Result) -> \"Statistics\":\n \"\"\"\n@@ -258,6 +260,7 @@ class DTSResult(BaseResult):\n \"\"\"\n \n dpdk_version: str | None\n+ output: dict | None\n _logger: DTSLOG\n _errors: list[Exception]\n _return_code: ErrorSeverity\n@@ -267,6 +270,7 @@ class DTSResult(BaseResult):\n def __init__(self, logger: DTSLOG):\n super(DTSResult, self).__init__()\n self.dpdk_version = None\n+ self.output = None\n self._logger = logger\n self._errors = []\n self._return_code = ErrorSeverity.NO_ERR\n@@ -296,7 +300,10 @@ def process(self) -> None:\n for error in self._errors:\n self._logger.debug(repr(error))\n \n- self._stats_result = Statistics(self.dpdk_version)\n+ self._stats_result = Statistics(self.output)\n+ #add information gathered from the smoke tests to the statistics\n+ # for info_key, info_val in smoke_test_info.items(): self._stats_result[info_key] = info_val\n+ # print(self._stats_result)\n self.add_stats(self._stats_result)\n with open(self._stats_filename, \"w+\") as stats_file:\n stats_file.write(str(self._stats_result))\ndiff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py\nindex 0705f38f..1518fb8a 100644\n--- a/dts/framework/test_suite.py\n+++ b/dts/framework/test_suite.py\n@@ -10,11 +10,14 @@\n import inspect\n import re\n from types import MethodType\n+from typing import Dict\n \n-from .exception import ConfigurationError, SSHTimeoutError, TestCaseVerifyError\n+from .config import BuildTargetConfiguration\n+\n+from .exception import BlockingTestSuiteError, ConfigurationError, SSHTimeoutError, TestCaseVerifyError\n from .logger import DTSLOG, getLogger\n from .settings import SETTINGS\n-from .test_result import BuildTargetResult, Result, TestCaseResult, TestSuiteResult\n+from .test_result import BuildTargetResult, DTSResult, Result, TestCaseResult, TestSuiteResult\n from .testbed_model import SutNode\n \n \n@@ -37,10 +40,12 @@ class TestSuite(object):\n \"\"\"\n \n sut_node: SutNode\n+ is_blocking = False\n _logger: DTSLOG\n _test_cases_to_run: list[str]\n _func: bool\n _result: TestSuiteResult\n+ _dts_result: DTSResult\n \n def __init__(\n self,\n@@ -48,6 +53,8 @@ def __init__(\n test_cases: list[str],\n func: bool,\n build_target_result: BuildTargetResult,\n+ build_target_conf: BuildTargetConfiguration,\n+ dts_result: DTSResult\n ):\n self.sut_node = sut_node\n self._logger = getLogger(self.__class__.__name__)\n@@ -55,6 +62,8 @@ def __init__(\n self._test_cases_to_run.extend(SETTINGS.test_cases)\n self._func = func\n self._result = build_target_result.add_test_suite(self.__class__.__name__)\n+ self.build_target_info = build_target_conf\n+ self._dts_result = dts_result\n \n def set_up_suite(self) -> None:\n \"\"\"\n@@ -118,6 +127,9 @@ def run(self) -> None:\n f\"the next test suite may be affected.\"\n )\n self._result.update_setup(Result.ERROR, e)\n+ if len(self._result.get_errors()) > 0 and self.is_blocking:\n+ raise BlockingTestSuiteError(test_suite_name)\n+ \n \n def _execute_test_suite(self) -> None:\n \"\"\"\n@@ -137,6 +149,7 @@ def _execute_test_suite(self) -> None:\n f\"Attempt number {attempt_nr} out of {all_attempts}.\"\n )\n self._run_test_case(test_case_method, test_case_result)\n+ \n \n def _get_functional_test_cases(self) -> list[MethodType]:\n \"\"\"\n@@ -232,6 +245,11 @@ def _execute_test_case(\n test_case_result.update(Result.SKIP)\n raise KeyboardInterrupt(\"Stop DTS\")\n \n+ def write_to_statistics_file(self, output: Dict[str, str]):\n+ if self._dts_result.output != None:\n+ self._dts_result.output.update(output)\n+ else:\n+ self._dts_result.output = output\n \n def get_test_suites(testsuite_module_path: str) -> list[type[TestSuite]]:\n def is_test_suite(object) -> bool:\n@@ -252,3 +270,5 @@ def is_test_suite(object) -> bool:\n test_suite_class\n for _, test_suite_class in inspect.getmembers(testcase_module, is_test_suite)\n ]\n+\n+\ndiff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbed_model/__init__.py\nindex f54a9470..63f17cc3 100644\n--- a/dts/framework/testbed_model/__init__.py\n+++ b/dts/framework/testbed_model/__init__.py\n@@ -20,3 +20,8 @@\n )\n from .node import Node\n from .sut_node import SutNode\n+\n+from .interactive_apps import (\n+ InteractiveScriptHandler,\n+ TestpmdDriver\n+)\ndiff --git a/dts/framework/testbed_model/interactive_apps/__init__.py b/dts/framework/testbed_model/interactive_apps/__init__.py\nnew file mode 100644\nindex 00000000..0382d7e0\n--- /dev/null\n+++ b/dts/framework/testbed_model/interactive_apps/__init__.py\n@@ -0,0 +1,6 @@\n+from .interactive_command import (\n+ InteractiveScriptHandler\n+)\n+from .testpmd_driver import (\n+ TestpmdDriver\n+)\n\\ No newline at end of file\ndiff --git a/dts/framework/testbed_model/interactive_apps/interactive_command.py b/dts/framework/testbed_model/interactive_apps/interactive_command.py\nnew file mode 100644\nindex 00000000..7467911b\n--- /dev/null\n+++ b/dts/framework/testbed_model/interactive_apps/interactive_command.py\n@@ -0,0 +1,57 @@\n+# import paramiko\n+from paramiko import SSHClient, Channel, channel\n+from framework.settings import SETTINGS\n+\n+class InteractiveScriptHandler:\n+\n+ _ssh_client: SSHClient\n+ _stdin: channel.ChannelStdinFile\n+ _ssh_channel: Channel\n+\n+ def __init__(self, ssh_client: SSHClient, timeout:float = SETTINGS.timeout) -> None:\n+ self._ssh_client = ssh_client\n+ self._ssh_channel = self._ssh_client.invoke_shell()\n+ self._stdin = self._ssh_channel.makefile_stdin(\"wb\")\n+ self._ssh_channel.settimeout(timeout)\n+\n+ def send_command(self, command:str) -> None:\n+ \"\"\"\n+ Send command to channel without recording output.\n+\n+ This method will not verify any input or output, it will\n+ simply assume the command succeeded\n+ \"\"\"\n+ self._stdin.write(command + '\\n')\n+ self._stdin.flush()\n+\n+ def send_command_get_output(self, command:str, expect:str) -> str:\n+ \"\"\"\n+ Send a command and get all output before the expected ending string.\n+\n+ **NOTE**\n+ Lines that expect input are not included in the stdout buffer so they cannot be\n+ used for expect. For example, if you were prompted to log into something\n+ with a username and password, you cannot expect \"username:\" because it wont\n+ yet be in the stdout buffer. A work around for this could be consuming an\n+ extra newline character to force the current prompt into the stdout buffer.\n+\n+ *Return*\n+ All output before expected string\n+ \"\"\"\n+ stdout = self._ssh_channel.makefile(\"r\")\n+ self._stdin.write(command + '\\n')\n+ self._stdin.flush()\n+ out:str = \"\"\n+ for line in stdout:\n+ out += str(line)\n+ if expect in str(line):\n+ break\n+ stdout.close() #close the buffer to flush the output\n+ return out\n+\n+ def close(self):\n+ self._stdin.close()\n+ self._ssh_channel.close()\n+\n+ def __del__(self):\n+ self.close()\ndiff --git a/dts/framework/testbed_model/interactive_apps/testpmd_driver.py b/dts/framework/testbed_model/interactive_apps/testpmd_driver.py\nnew file mode 100644\nindex 00000000..1993eae6\n--- /dev/null\n+++ b/dts/framework/testbed_model/interactive_apps/testpmd_driver.py\n@@ -0,0 +1,24 @@\n+from framework.testbed_model.interactive_apps import InteractiveScriptHandler\n+\n+from pathlib import PurePath\n+\n+class TestpmdDriver:\n+ prompt:str = \"testpmd>\"\n+ interactive_handler: InteractiveScriptHandler\n+\n+ def __init__(self, handler: InteractiveScriptHandler, dpdk_build_dir:PurePath, eal_flags:str = \"\", cmd_line_options:str = \"\") -> None:\n+ \"\"\"\n+ Sets the handler to drive the SSH session and starts testpmd\n+ \"\"\"\n+ self.interactive_handler = handler\n+ # self.interactive_handler.send_command(\"sudo su\")\n+ # self.interactive_handler.send_command(\"cd /root/testpmd-testing/dpdk/build\")\n+ self.interactive_handler.send_command_get_output(f\"{dpdk_build_dir}/app/dpdk-testpmd {eal_flags} -- -i {cmd_line_options}\\n\", self.prompt)\n+\n+ def send_command(self, command:str, expect:str = prompt) -> str:\n+ \"\"\"\n+ Specific way of handling the command for testpmd\n+\n+ An extra newline character is consumed in order to force the current line into the stdout buffer\n+ \"\"\"\n+ return self.interactive_handler.send_command_get_output(command + \"\\n\", expect)\n\\ No newline at end of file\ndiff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py\nindex d48fafe6..c5147e0e 100644\n--- a/dts/framework/testbed_model/node.py\n+++ b/dts/framework/testbed_model/node.py\n@@ -40,6 +40,7 @@ class Node(object):\n lcores: list[LogicalCore]\n _logger: DTSLOG\n _other_sessions: list[OSSession]\n+ _execution_config: ExecutionConfiguration\n \n def __init__(self, node_config: NodeConfiguration):\n self.config = node_config\n@@ -64,6 +65,7 @@ def set_up_execution(self, execution_config: ExecutionConfiguration) -> None:\n \"\"\"\n self._setup_hugepages()\n self._set_up_execution(execution_config)\n+ self._execution_config = execution_config\n \n def _set_up_execution(self, execution_config: ExecutionConfiguration) -> None:\n \"\"\"\ndiff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbed_model/sut_node.py\nindex 2b2b50d9..8c39a66d 100644\n--- a/dts/framework/testbed_model/sut_node.py\n+++ b/dts/framework/testbed_model/sut_node.py\n@@ -14,6 +14,7 @@\n \n from .hw import LogicalCoreCount, LogicalCoreList, VirtualDevice\n from .node import Node\n+from .interactive_apps import InteractiveScriptHandler\n \n \n class SutNode(Node):\n@@ -261,6 +262,11 @@ def run_dpdk_app(\n return self.main_session.send_command(\n f\"{app_path} {eal_args}\", timeout, verify=True\n )\n+ def create_interactive_session_handler(self) -> InteractiveScriptHandler:\n+ \"\"\"\n+ Create a handler for interactive sessions\n+ \"\"\"\n+ return InteractiveScriptHandler(self.main_session._interactive_session)\n \n \n class EalParameters(object):\ndiff --git a/dts/tests/TestSuite_smoke_tests.py b/dts/tests/TestSuite_smoke_tests.py\nnew file mode 100644\nindex 00000000..bacf289d\n--- /dev/null\n+++ b/dts/tests/TestSuite_smoke_tests.py\n@@ -0,0 +1,94 @@\n+from framework.test_suite import TestSuite\n+from framework.testbed_model.sut_node import SutNode\n+\n+from framework.testbed_model.interactive_apps import TestpmdDriver\n+\n+def get_compiler_version(compiler_name: str, sut_node: SutNode) -> str:\n+ match compiler_name:\n+ case \"gcc\":\n+ return sut_node.main_session.send_command(f\"{compiler_name} --version\", 60).stdout.split(\"\\n\")[0]\n+ case \"clang\":\n+ return sut_node.main_session.send_command(f\"{compiler_name} --version\", 60).stdout.split(\"\\n\")[0]\n+ case \"msvc\":\n+ return sut_node.main_session.send_command(f\"cl\", 60).stdout\n+ case \"icc\":\n+ return sut_node.main_session.send_command(f\"{compiler_name} -V\", 60).stdout\n+\n+class SmokeTests(TestSuite):\n+ is_blocking = True\n+\n+ def set_up_suite(self) -> None:\n+ \"\"\"\n+ Setup:\n+ build all DPDK\n+ \"\"\"\n+ self.dpdk_build_dir_path = self.sut_node.remote_dpdk_build_dir\n+ \n+\n+ def test_unit_tests(self) -> None:\n+ \"\"\"\n+ Test:\n+ run the fast-test unit-test suite through meson\n+ \"\"\"\n+ self.sut_node.main_session.send_command(f\"meson test -C {self.dpdk_build_dir_path} --suite fast-tests\", 300)\n+\n+ def test_driver_tests(self) -> None:\n+ \"\"\"\n+ Test:\n+ run the driver-test unit-test suite through meson\n+ \"\"\"\n+ list_of_vdevs = \"\"\n+ for dev in self.sut_node._execution_config.vdevs:\n+ list_of_vdevs += f\"{dev},\"\n+ print(list_of_vdevs)\n+ if len(list_of_vdevs) > 0:\n+ self.sut_node.main_session.send_command(f\"meson test -C {self.dpdk_build_dir_path} --suite driver-tests --test-args \\\"--vdev {list_of_vdevs}\\\"\", 300)\n+ else:\n+ self.sut_node.main_session.send_command(f\"meson test -C {self.dpdk_build_dir_path} --suite driver-tests\", 300)\n+\n+ def test_gather_info(self) -> None:\n+ \"\"\"\n+ Test:\n+ gather information about the system and send output to statistics.txt\n+ \"\"\"\n+ out = {}\n+ \n+ out['OS'] = self.sut_node.main_session.send_command(\"awk -F= '$1==\\\"NAME\\\" {print $2}' /etc/os-release\", 60).stdout\n+ out[\"OS VERSION\"] = self.sut_node.main_session.send_command(\"awk -F= '$1==\\\"VERSION\\\" {print $2}' /etc/os-release\", 60, True).stdout\n+ out[\"COMPILER VERSION\"] = get_compiler_version(self.build_target_info.compiler.name, self.sut_node)\n+ out[\"DPDK VERSION\"] = self.sut_node.dpdk_version\n+ if self.build_target_info.os.name == \"linux\":\n+ out['KERNEL VERSION'] = self.sut_node.main_session.send_command(\"uname -r\", 60).stdout\n+ elif self.build_target_info.os.name == \"windows\":\n+ out['KERNEL VERSION'] = self.sut_node.main_session.send_command(\"uname -a\", 60).stdout\n+ self.write_to_statistics_file(out)\n+\n+\n+\n+ def test_start_testpmd(self) -> None:\n+ \"\"\"\n+ Creates and instance of the testpmd driver to run the testpmd app\n+ \"\"\"\n+ driver: TestpmdDriver = TestpmdDriver(self.sut_node.create_interactive_session_handler(), self.dpdk_build_dir_path)\n+\n+ print(driver.send_command(\"show port summary all\"))\n+\n+ def test_device_bound_to_driver(self) -> None:\n+ \"\"\"\n+ Test:\n+ ensure that all drivers listed in the config are bound to the correct drivers\n+ \"\"\"\n+ for nic in self.sut_node._execution_config.nics:\n+ for address in nic.addresses:\n+ out = self.sut_node.main_session.send_command(f\"{self.dpdk_build_dir_path}/../usertools/dpdk-devbind.py --status | grep {address}\", 60)\n+ self.verify(\n+ len(out.stdout) != 0,\n+ f\"Failed to find configured device ({address}) using dpdk-devbind.py\",\n+ )\n+ for string in out.stdout.split(\" \"):\n+ if 'drv=' in string:\n+ self.verify(\n+ string.split(\"=\")[1] == nic.driver.strip(),\n+ f'Driver for device {address} does not match driver listed in configuration (bound to {string.split(\"=\")[1]})',\n+ )\n+ \n\\ No newline at end of file\n", "prefixes": [ "RFC", "v2", "1/2" ] }{ "id": 126841, "url": "