From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 4D2D043349; Thu, 16 Nov 2023 22:05:09 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 24A7E4027D; Thu, 16 Nov 2023 22:05:09 +0100 (CET) Received: from mail-pl1-f169.google.com (mail-pl1-f169.google.com [209.85.214.169]) by mails.dpdk.org (Postfix) with ESMTP id 38EB440150 for ; Thu, 16 Nov 2023 22:05:07 +0100 (CET) Received: by mail-pl1-f169.google.com with SMTP id d9443c01a7336-1cc7077d34aso10932995ad.2 for ; Thu, 16 Nov 2023 13:05:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1700168706; x=1700773506; darn=dpdk.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=CQzqVg0V5cIFs4cgnooiUQPIURhxIdrqe0ZKvRgVgTA=; b=SSmyFeNpBWhmELn9ZEXB+60lq8wt67tr2zzyTegqyy8gH/+oj2LE3a45QtYj4LGyaV t8+aY5JsfyK5TeTGqD14sf9yi030IC53UB72W3qbD3dWzu8RjDYzMm+R1SRX8cTa3XWA 4Yrcj5NV11JyjCOQ/jLMpv+6d9KCTopYNiPFk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700168706; x=1700773506; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=CQzqVg0V5cIFs4cgnooiUQPIURhxIdrqe0ZKvRgVgTA=; b=SB6anZLsk/d4GG/0z7gYH/ype8i81ANCmQf7jkEjocjDEtxMIMQxrlfd2XKJS0zPNW xV9UGafjfLLWdNmwC0RH6OThzToMvIcxM9H3zo9xkeoDJnRHtvcb96FJXTW1pOHeuiON d17QbYeXej+1bj4ZPjdo51nCdk27S8tAU6iiLfWSaTDSnLnJ5SKvxHsZPJ7iIIL/H1no xYsttWEOfsNc2YjIIB76N6Ve4iyNXkurUmZRRzj6+BVTMP6Kzg2ZHG/39mEnho2UOij4 9putQIkse3MkEnGnbKRVXkdHWWFJ62FIxduPtM1e/Z/xcex8ZlscLvymPyz9U8rVZYS+ 0Acg== X-Gm-Message-State: AOJu0Yzb+tIA3y4JvO4cT5WzHWLskNhKL2tyjhRABfnkzcU+5/QIXRKV kCsUetRXTZ7Lg1g4t7gbPohB9OzzG9TIjo5tgu+TJYcAhtfMqjgQ X-Google-Smtp-Source: AGHT+IHlzkCiU4sTGC7iN6NZm5slGpN/DjT8nDpPNoqJged/TqfhEyWDoZLJYDtOCgkcZM7fGoTOFyin+eoIo+XNSMw= X-Received: by 2002:a17:90a:1088:b0:280:cd5f:bf90 with SMTP id c8-20020a17090a108800b00280cd5fbf90mr16052690pja.23.1700168705309; Thu, 16 Nov 2023 13:05:05 -0800 (PST) MIME-Version: 1.0 References: <20231108125324.191005-23-juraj.linkes@pantheon.tech> <20231115130959.39420-1-juraj.linkes@pantheon.tech> <20231115130959.39420-2-juraj.linkes@pantheon.tech> In-Reply-To: <20231115130959.39420-2-juraj.linkes@pantheon.tech> From: Jeremy Spewock Date: Thu, 16 Nov 2023 16:04:54 -0500 Message-ID: Subject: Re: [PATCH v7 01/21] dts: code adjustments for doc generation To: =?UTF-8?Q?Juraj_Linke=C5=A1?= Cc: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, probb@iol.unh.edu, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com, dev@dpdk.org Content-Type: multipart/alternative; boundary="000000000000bd18b0060a4b5f19" X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org --000000000000bd18b0060a4b5f19 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Wed, Nov 15, 2023 at 8:11=E2=80=AFAM Juraj Linke=C5=A1 wrote: > The standard Python tool for generating API documentation, Sphinx, > imports modules one-by-one when generating the documentation. This > requires code changes: > * properly guarding argument parsing in the if __name__ =3D=3D '__main__' > block, > * the logger used by DTS runner underwent the same treatment so that it > doesn't create log files outside of a DTS run, > * however, DTS uses the arguments to construct an object holding global > variables. The defaults for the global variables needed to be moved > from argument parsing elsewhere, > * importing the remote_session module from framework resulted in > circular imports because of one module trying to import another > module. This is fixed by reorganizing the code, > * some code reorganization was done because the resulting structure > makes more sense, improving documentation clarity. > > The are some other changes which are documentation related: > * added missing type annotation so they appear in the generated docs, > * reordered arguments in some methods, > * removed superfluous arguments and attributes, > * change private functions/methods/attributes to private and vice-versa. > > The above all appear in the generated documentation and the with them, > the documentation is improved. > > Signed-off-by: Juraj Linke=C5=A1 > --- > dts/framework/config/__init__.py | 10 ++- > dts/framework/dts.py | 33 +++++-- > dts/framework/exception.py | 54 +++++------- > dts/framework/remote_session/__init__.py | 41 ++++----- > .../interactive_remote_session.py | 0 > .../{remote =3D> }/interactive_shell.py | 0 > .../{remote =3D> }/python_shell.py | 0 > .../remote_session/remote/__init__.py | 27 ------ > .../{remote =3D> }/remote_session.py | 0 > .../{remote =3D> }/ssh_session.py | 12 +-- > .../{remote =3D> }/testpmd_shell.py | 0 > dts/framework/settings.py | 87 +++++++++++-------- > dts/framework/test_result.py | 4 +- > dts/framework/test_suite.py | 7 +- > dts/framework/testbed_model/__init__.py | 12 +-- > dts/framework/testbed_model/{hw =3D> }/cpu.py | 13 +++ > dts/framework/testbed_model/hw/__init__.py | 27 ------ > .../linux_session.py | 6 +- > dts/framework/testbed_model/node.py | 25 ++++-- > .../os_session.py | 22 ++--- > dts/framework/testbed_model/{hw =3D> }/port.py | 0 > .../posix_session.py | 4 +- > dts/framework/testbed_model/sut_node.py | 8 +- > dts/framework/testbed_model/tg_node.py | 30 +------ > .../traffic_generator/__init__.py | 24 +++++ > .../capturing_traffic_generator.py | 6 +- > .../{ =3D> traffic_generator}/scapy.py | 23 ++--- > .../traffic_generator.py | 16 +++- > .../testbed_model/{hw =3D> }/virtual_device.py | 0 > dts/framework/utils.py | 46 +++------- > dts/main.py | 9 +- > 31 files changed, 258 insertions(+), 288 deletions(-) > rename dts/framework/remote_session/{remote =3D> > }/interactive_remote_session.py (100%) > rename dts/framework/remote_session/{remote =3D> }/interactive_shell.py > (100%) > rename dts/framework/remote_session/{remote =3D> }/python_shell.py (100%= ) > delete mode 100644 dts/framework/remote_session/remote/__init__.py > rename dts/framework/remote_session/{remote =3D> }/remote_session.py (10= 0%) > rename dts/framework/remote_session/{remote =3D> }/ssh_session.py (91%) > rename dts/framework/remote_session/{remote =3D> }/testpmd_shell.py (100= %) > rename dts/framework/testbed_model/{hw =3D> }/cpu.py (95%) > delete mode 100644 dts/framework/testbed_model/hw/__init__.py > rename dts/framework/{remote_session =3D> testbed_model}/linux_session.p= y > (97%) > rename dts/framework/{remote_session =3D> testbed_model}/os_session.py (= 95%) > rename dts/framework/testbed_model/{hw =3D> }/port.py (100%) > rename dts/framework/{remote_session =3D> testbed_model}/posix_session.p= y > (98%) > create mode 100644 > dts/framework/testbed_model/traffic_generator/__init__.py > rename dts/framework/testbed_model/{ =3D> > traffic_generator}/capturing_traffic_generator.py (96%) > rename dts/framework/testbed_model/{ =3D> traffic_generator}/scapy.py (9= 5%) > rename dts/framework/testbed_model/{ =3D> > traffic_generator}/traffic_generator.py (80%) > rename dts/framework/testbed_model/{hw =3D> }/virtual_device.py (100%) > > diff --git a/dts/framework/config/__init__.py > b/dts/framework/config/__init__.py > index cb7e00ba34..2044c82611 100644 > --- a/dts/framework/config/__init__.py > +++ b/dts/framework/config/__init__.py > @@ -17,6 +17,7 @@ > import warlock # type: ignore[import] > import yaml > > +from framework.exception import ConfigurationError > from framework.settings import SETTINGS > from framework.utils import StrEnum > > @@ -89,7 +90,7 @@ class TrafficGeneratorConfig: > traffic_generator_type: TrafficGeneratorType > > @staticmethod > - def from_dict(d: dict): > + def from_dict(d: dict) -> "ScapyTrafficGeneratorConfig": > # This looks useless now, but is designed to allow expansion to > traffic > # generators that require more configuration later. > match TrafficGeneratorType(d["type"]): > @@ -97,6 +98,10 @@ def from_dict(d: dict): > return ScapyTrafficGeneratorConfig( > traffic_generator_type=3DTrafficGeneratorType.SCAPY > ) > + case _: > + raise ConfigurationError( > + f'Unknown traffic generator type "{d["type"]}".' > + ) > > > @dataclass(slots=3DTrue, frozen=3DTrue) > @@ -324,6 +329,3 @@ def load_config() -> Configuration: > config: dict[str, Any] =3D warlock.model_factory(schema, > name=3D"_Config")(config_data) > config_obj: Configuration =3D Configuration.from_dict(dict(config)) > return config_obj > - > - > -CONFIGURATION =3D load_config() > diff --git a/dts/framework/dts.py b/dts/framework/dts.py > index f773f0c38d..4c7fb0c40a 100644 > --- a/dts/framework/dts.py > +++ b/dts/framework/dts.py > @@ -6,19 +6,19 @@ > import sys > > from .config import ( > - CONFIGURATION, > BuildTargetConfiguration, > ExecutionConfiguration, > TestSuiteConfig, > + load_config, > ) > from .exception import BlockingTestSuiteError > from .logger import DTSLOG, getLogger > from .test_result import BuildTargetResult, DTSResult, ExecutionResult, > Result > from .test_suite import get_test_suites > from .testbed_model import SutNode, TGNode > -from .utils import check_dts_python_version > > -dts_logger: DTSLOG =3D getLogger("DTSRunner") > +# dummy defaults to satisfy linters > +dts_logger: DTSLOG =3D None # type: ignore[assignment] > result: DTSResult =3D DTSResult(dts_logger) > > > @@ -30,14 +30,18 @@ def run_all() -> None: > global dts_logger > global result > > + # create a regular DTS logger and create a new result with it > + dts_logger =3D getLogger("DTSRunner") > + result =3D DTSResult(dts_logger) > + > # check the python version of the server that run dts > - check_dts_python_version() > + _check_dts_python_version() > > sut_nodes: dict[str, SutNode] =3D {} > tg_nodes: dict[str, TGNode] =3D {} > try: > # for all Execution sections > - for execution in CONFIGURATION.executions: > + for execution in load_config().executions: > sut_node =3D sut_nodes.get( > execution.system_under_test_node.name) > tg_node =3D tg_nodes.get(execution.traffic_generator_node.na= me) > > @@ -82,6 +86,25 @@ def run_all() -> None: > _exit_dts() > > > +def _check_dts_python_version() -> None: > + def RED(text: str) -> str: > + return f"\u001B[31;1m{str(text)}\u001B[0m" > + > + if sys.version_info.major < 3 or ( > + sys.version_info.major =3D=3D 3 and sys.version_info.minor < 10 > + ): > + print( > + RED( > + ( > + "WARNING: DTS execution node's python version is > lower than" > + "python 3.10, is deprecated and will not work in > future releases." > + ) > + ), > + file=3Dsys.stderr, > + ) > + print(RED("Please use Python >=3D 3.10 instead"), file=3Dsys.std= err) > + > + > def _run_execution( > sut_node: SutNode, > tg_node: TGNode, > diff --git a/dts/framework/exception.py b/dts/framework/exception.py > index 001a5a5496..7489c03570 100644 > --- a/dts/framework/exception.py > +++ b/dts/framework/exception.py > @@ -42,19 +42,14 @@ class SSHTimeoutError(DTSError): > Command execution timeout. > """ > > - command: str > - output: str > severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH_ERR > + _command: str > > - def __init__(self, command: str, output: str): > - self.command =3D command > - self.output =3D output > + def __init__(self, command: str): > + self._command =3D command > > def __str__(self) -> str: > - return f"TIMEOUT on {self.command}" > - > - def get_output(self) -> str: > - return self.output > + return f"TIMEOUT on {self._command}" > > > class SSHConnectionError(DTSError): > @@ -62,18 +57,18 @@ class SSHConnectionError(DTSError): > SSH connection error. > """ > > - host: str > - errors: list[str] > severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH_ERR > + _host: str > + _errors: list[str] > > def __init__(self, host: str, errors: list[str] | None =3D None): > - self.host =3D host > - self.errors =3D [] if errors is None else errors > + self._host =3D host > + self._errors =3D [] if errors is None else errors > > def __str__(self) -> str: > - message =3D f"Error trying to connect with {self.host}." > - if self.errors: > - message +=3D f" Errors encountered while retrying: {', > '.join(self.errors)}" > + message =3D f"Error trying to connect with {self._host}." > + if self._errors: > + message +=3D f" Errors encountered while retrying: {', > '.join(self._errors)}" > > return message > > @@ -84,14 +79,14 @@ class SSHSessionDeadError(DTSError): > It can no longer be used. > """ > > - host: str > severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH_ERR > + _host: str > > def __init__(self, host: str): > - self.host =3D host > + self._host =3D host > > def __str__(self) -> str: > - return f"SSH session with {self.host} has died" > + return f"SSH session with {self._host} has died" > > > class ConfigurationError(DTSError): > @@ -107,18 +102,18 @@ class RemoteCommandExecutionError(DTSError): > Raised when a command executed on a Node returns a non-zero exit > status. > """ > > - command: str > - command_return_code: int > severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.REMOTE_CMD_EXEC_= ERR > + command: str > + _command_return_code: int > > def __init__(self, command: str, command_return_code: int): > self.command =3D command > - self.command_return_code =3D command_return_code > + self._command_return_code =3D command_return_code > > def __str__(self) -> str: > return ( > f"Command {self.command} returned a non-zero exit code: " > - f"{self.command_return_code}" > + f"{self._command_return_code}" > ) > > > @@ -143,22 +138,15 @@ class TestCaseVerifyError(DTSError): > Used in test cases to verify the expected behavior. > """ > > - value: str > severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.TESTCASE_VERIFY_= ERR > > - def __init__(self, value: str): > - self.value =3D value > - > - def __str__(self) -> str: > - return repr(self.value) > - > Does this change mean we are no longer providing descriptions for what failing the verification means? I guess there isn't really harm in removing that functionality, but I'm not sure I see the value in removing the extra information either. > > class BlockingTestSuiteError(DTSError): > - suite_name: str > severity: ClassVar[ErrorSeverity] =3D > ErrorSeverity.BLOCKING_TESTSUITE_ERR > + _suite_name: str > > def __init__(self, suite_name: str) -> None: > - self.suite_name =3D suite_name > + self._suite_name =3D suite_name > > def __str__(self) -> str: > - return f"Blocking suite {self.suite_name} failed." > + return f"Blocking suite {self._suite_name} failed." > diff --git a/dts/framework/remote_session/__init__.py > b/dts/framework/remote_session/__init__.py > index 00b6d1f03a..5e7ddb2b05 100644 > --- a/dts/framework/remote_session/__init__.py > +++ b/dts/framework/remote_session/__init__.py > @@ -12,29 +12,24 @@ > > # pylama:ignore=3DW0611 > > -from framework.config import OS, NodeConfiguration > -from framework.exception import ConfigurationError > +from framework.config import NodeConfiguration > from framework.logger import DTSLOG > > -from .linux_session import LinuxSession > -from .os_session import InteractiveShellType, OSSession > -from .remote import ( > - CommandResult, > - InteractiveRemoteSession, > - InteractiveShell, > - PythonShell, > - RemoteSession, > - SSHSession, > - TestPmdDevice, > - TestPmdShell, > -) > - > - > -def create_session( > +from .interactive_remote_session import InteractiveRemoteSession > +from .interactive_shell import InteractiveShell > +from .python_shell import PythonShell > +from .remote_session import CommandResult, RemoteSession > +from .ssh_session import SSHSession > +from .testpmd_shell import TestPmdShell > + > + > +def create_remote_session( > node_config: NodeConfiguration, name: str, logger: DTSLOG > -) -> OSSession: > - match node_config.os: > - case OS.linux: > - return LinuxSession(node_config, name, logger) > - case _: > - raise ConfigurationError(f"Unsupported OS {node_config.os}") > +) -> RemoteSession: > + return SSHSession(node_config, name, logger) > + > + > +def create_interactive_session( > + node_config: NodeConfiguration, logger: DTSLOG > +) -> InteractiveRemoteSession: > + return InteractiveRemoteSession(node_config, logger) > diff --git > a/dts/framework/remote_session/remote/interactive_remote_session.py > b/dts/framework/remote_session/interactive_remote_session.py > similarity index 100% > rename from > dts/framework/remote_session/remote/interactive_remote_session.py > rename to dts/framework/remote_session/interactive_remote_session.py > diff --git a/dts/framework/remote_session/remote/interactive_shell.py > b/dts/framework/remote_session/interactive_shell.py > similarity index 100% > rename from dts/framework/remote_session/remote/interactive_shell.py > rename to dts/framework/remote_session/interactive_shell.py > diff --git a/dts/framework/remote_session/remote/python_shell.py > b/dts/framework/remote_session/python_shell.py > similarity index 100% > rename from dts/framework/remote_session/remote/python_shell.py > rename to dts/framework/remote_session/python_shell.py > diff --git a/dts/framework/remote_session/remote/__init__.py > b/dts/framework/remote_session/remote/__init__.py > deleted file mode 100644 > index 06403691a5..0000000000 > --- a/dts/framework/remote_session/remote/__init__.py > +++ /dev/null > @@ -1,27 +0,0 @@ > -# SPDX-License-Identifier: BSD-3-Clause > -# Copyright(c) 2023 PANTHEON.tech s.r.o. > -# Copyright(c) 2023 University of New Hampshire > - > -# pylama:ignore=3DW0611 > - > -from framework.config import NodeConfiguration > -from framework.logger import DTSLOG > - > -from .interactive_remote_session import InteractiveRemoteSession > -from .interactive_shell import InteractiveShell > -from .python_shell import PythonShell > -from .remote_session import CommandResult, RemoteSession > -from .ssh_session import SSHSession > -from .testpmd_shell import TestPmdDevice, TestPmdShell > - > - > -def create_remote_session( > - node_config: NodeConfiguration, name: str, logger: DTSLOG > -) -> RemoteSession: > - return SSHSession(node_config, name, logger) > - > - > -def create_interactive_session( > - node_config: NodeConfiguration, logger: DTSLOG > -) -> InteractiveRemoteSession: > - return InteractiveRemoteSession(node_config, logger) > diff --git a/dts/framework/remote_session/remote/remote_session.py > b/dts/framework/remote_session/remote_session.py > similarity index 100% > rename from dts/framework/remote_session/remote/remote_session.py > rename to dts/framework/remote_session/remote_session.py > diff --git a/dts/framework/remote_session/remote/ssh_session.py > b/dts/framework/remote_session/ssh_session.py > similarity index 91% > rename from dts/framework/remote_session/remote/ssh_session.py > rename to dts/framework/remote_session/ssh_session.py > index 8d127f1601..cee11d14d6 100644 > --- a/dts/framework/remote_session/remote/ssh_session.py > +++ b/dts/framework/remote_session/ssh_session.py > @@ -18,9 +18,7 @@ > SSHException, > ) > > -from framework.config import NodeConfiguration > from framework.exception import SSHConnectionError, SSHSessionDeadError, > SSHTimeoutError > -from framework.logger import DTSLOG > > from .remote_session import CommandResult, RemoteSession > > @@ -45,14 +43,6 @@ class SSHSession(RemoteSession): > > session: Connection > > - def __init__( > - self, > - node_config: NodeConfiguration, > - session_name: str, > - logger: DTSLOG, > - ): > - super(SSHSession, self).__init__(node_config, session_name, > logger) > - > def _connect(self) -> None: > errors =3D [] > retry_attempts =3D 10 > @@ -117,7 +107,7 @@ def _send_command( > > except CommandTimedOut as e: > self._logger.exception(e) > - raise SSHTimeoutError(command, e.result.stderr) from e > + raise SSHTimeoutError(command) from e > > return CommandResult( > self.name, command, output.stdout, output.stderr, > output.return_code > diff --git a/dts/framework/remote_session/remote/testpmd_shell.py > b/dts/framework/remote_session/testpmd_shell.py > similarity index 100% > rename from dts/framework/remote_session/remote/testpmd_shell.py > rename to dts/framework/remote_session/testpmd_shell.py > diff --git a/dts/framework/settings.py b/dts/framework/settings.py > index cfa39d011b..7f5841d073 100644 > --- a/dts/framework/settings.py > +++ b/dts/framework/settings.py > @@ -6,7 +6,7 @@ > import argparse > import os > from collections.abc import Callable, Iterable, Sequence > -from dataclasses import dataclass > +from dataclasses import dataclass, field > from pathlib import Path > from typing import Any, TypeVar > > @@ -22,8 +22,8 @@ def __init__( > option_strings: Sequence[str], > dest: str, > nargs: str | int | None =3D None, > - const: str | None =3D None, > - default: str =3D None, > + const: bool | None =3D None, > + default: Any =3D None, > type: Callable[[str], _T | argparse.FileType | None] =3D Non= e, > choices: Iterable[_T] | None =3D None, > required: bool =3D False, > @@ -32,6 +32,12 @@ def __init__( > ) -> None: > env_var_value =3D os.environ.get(env_var) > default =3D env_var_value or default > + if const is not None: > + nargs =3D 0 > + default =3D const if env_var_value else default > + type =3D None > + choices =3D None > + metavar =3D None > super(_EnvironmentArgument, self).__init__( > option_strings, > dest, > @@ -52,22 +58,28 @@ def __call__( > values: Any, > option_string: str =3D None, > ) -> None: > - setattr(namespace, self.dest, values) > + if self.const is not None: > + setattr(namespace, self.dest, self.const) > + else: > + setattr(namespace, self.dest, values) > > return _EnvironmentArgument > > > -@dataclass(slots=3DTrue, frozen=3DTrue) > -class _Settings: > - config_file_path: str > - output_dir: str > - timeout: float > - verbose: bool > - skip_setup: bool > - dpdk_tarball_path: Path > - compile_timeout: float > - test_cases: list > - re_run: int > +@dataclass(slots=3DTrue) > +class Settings: > + config_file_path: Path =3D > Path(__file__).parent.parent.joinpath("conf.yaml") > + output_dir: str =3D "output" > + timeout: float =3D 15 > + verbose: bool =3D False > + skip_setup: bool =3D False > + dpdk_tarball_path: Path | str =3D "dpdk.tar.xz" > + compile_timeout: float =3D 1200 > + test_cases: list[str] =3D field(default_factory=3Dlist) > + re_run: int =3D 0 > + > + > +SETTINGS: Settings =3D Settings() > > > def _get_parser() -> argparse.ArgumentParser: > @@ -81,7 +93,8 @@ def _get_parser() -> argparse.ArgumentParser: > parser.add_argument( > "--config-file", > action=3D_env_arg("DTS_CFG_FILE"), > - default=3D"conf.yaml", > + default=3DSETTINGS.config_file_path, > + type=3DPath, > help=3D"[DTS_CFG_FILE] configuration file that describes the tes= t > cases, SUTs " > "and targets.", > ) > @@ -90,7 +103,7 @@ def _get_parser() -> argparse.ArgumentParser: > "--output-dir", > "--output", > action=3D_env_arg("DTS_OUTPUT_DIR"), > - default=3D"output", > + default=3DSETTINGS.output_dir, > help=3D"[DTS_OUTPUT_DIR] Output directory where dts logs and > results are saved.", > ) > > @@ -98,7 +111,7 @@ def _get_parser() -> argparse.ArgumentParser: > "-t", > "--timeout", > action=3D_env_arg("DTS_TIMEOUT"), > - default=3D15, > + default=3DSETTINGS.timeout, > type=3Dfloat, > help=3D"[DTS_TIMEOUT] The default timeout for all DTS operations > except for " > "compiling DPDK.", > @@ -108,8 +121,9 @@ def _get_parser() -> argparse.ArgumentParser: > "-v", > "--verbose", > action=3D_env_arg("DTS_VERBOSE"), > - default=3D"N", > - help=3D"[DTS_VERBOSE] Set to 'Y' to enable verbose output, loggi= ng > all messages " > + default=3DSETTINGS.verbose, > + const=3DTrue, > + help=3D"[DTS_VERBOSE] Specify to enable verbose output, logging = all > messages " > "to the console.", > ) > > @@ -117,8 +131,8 @@ def _get_parser() -> argparse.ArgumentParser: > "-s", > "--skip-setup", > action=3D_env_arg("DTS_SKIP_SETUP"), > - default=3D"N", > - help=3D"[DTS_SKIP_SETUP] Set to 'Y' to skip all setup steps on S= UT > and TG nodes.", > + const=3DTrue, > + help=3D"[DTS_SKIP_SETUP] Specify to skip all setup steps on SUT = and > TG nodes.", > ) > > parser.add_argument( > @@ -126,7 +140,7 @@ def _get_parser() -> argparse.ArgumentParser: > "--snapshot", > "--git-ref", > action=3D_env_arg("DTS_DPDK_TARBALL"), > - default=3D"dpdk.tar.xz", > + default=3DSETTINGS.dpdk_tarball_path, > type=3DPath, > help=3D"[DTS_DPDK_TARBALL] Path to DPDK source code tarball or a > git commit ID, " > "tag ID or tree ID to test. To test local changes, first commit > them, " > @@ -136,7 +150,7 @@ def _get_parser() -> argparse.ArgumentParser: > parser.add_argument( > "--compile-timeout", > action=3D_env_arg("DTS_COMPILE_TIMEOUT"), > - default=3D1200, > + default=3DSETTINGS.compile_timeout, > type=3Dfloat, > help=3D"[DTS_COMPILE_TIMEOUT] The timeout for compiling DPDK.", > ) > @@ -153,7 +167,7 @@ def _get_parser() -> argparse.ArgumentParser: > "--re-run", > "--re_run", > action=3D_env_arg("DTS_RERUN"), > - default=3D0, > + default=3DSETTINGS.re_run, > type=3Dint, > help=3D"[DTS_RERUN] Re-run each test case the specified amount o= f > times " > "if a test failure occurs", > @@ -162,23 +176,22 @@ def _get_parser() -> argparse.ArgumentParser: > return parser > > > -def _get_settings() -> _Settings: > +def get_settings() -> Settings: > parsed_args =3D _get_parser().parse_args() > - return _Settings( > + return Settings( > config_file_path=3Dparsed_args.config_file, > output_dir=3Dparsed_args.output_dir, > timeout=3Dparsed_args.timeout, > - verbose=3D(parsed_args.verbose =3D=3D "Y"), > - skip_setup=3D(parsed_args.skip_setup =3D=3D "Y"), > + verbose=3Dparsed_args.verbose, > + skip_setup=3Dparsed_args.skip_setup, > dpdk_tarball_path=3DPath( > - DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir) > - ) > - if not os.path.exists(parsed_args.tarball) > - else Path(parsed_args.tarball), > + Path(DPDKGitTarball(parsed_args.tarball, > parsed_args.output_dir)) > + if not os.path.exists(parsed_args.tarball) > + else Path(parsed_args.tarball) > + ), > compile_timeout=3Dparsed_args.compile_timeout, > - test_cases=3Dparsed_args.test_cases.split(",") if > parsed_args.test_cases else [], > + test_cases=3D( > + parsed_args.test_cases.split(",") if parsed_args.test_cases > else [] > + ), > re_run=3Dparsed_args.re_run, > ) > - > - > -SETTINGS: _Settings =3D _get_settings() > diff --git a/dts/framework/test_result.py b/dts/framework/test_result.py > index f0fbe80f6f..603e18872c 100644 > --- a/dts/framework/test_result.py > +++ b/dts/framework/test_result.py > @@ -254,7 +254,7 @@ def add_build_target( > self._inner_results.append(build_target_result) > return build_target_result > > - def add_sut_info(self, sut_info: NodeInfo): > + def add_sut_info(self, sut_info: NodeInfo) -> None: > self.sut_os_name =3D sut_info.os_name > self.sut_os_version =3D sut_info.os_version > self.sut_kernel_version =3D sut_info.kernel_version > @@ -297,7 +297,7 @@ def add_execution(self, sut_node: NodeConfiguration) > -> ExecutionResult: > self._inner_results.append(execution_result) > return execution_result > > - def add_error(self, error) -> None: > + def add_error(self, error: Exception) -> None: > self._errors.append(error) > > def process(self) -> None: > diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py > index 3b890c0451..d53553bf34 100644 > --- a/dts/framework/test_suite.py > +++ b/dts/framework/test_suite.py > @@ -11,7 +11,7 @@ > import re > from ipaddress import IPv4Interface, IPv6Interface, ip_interface > from types import MethodType > -from typing import Union > +from typing import Any, Union > > from scapy.layers.inet import IP # type: ignore[import] > from scapy.layers.l2 import Ether # type: ignore[import] > @@ -26,8 +26,7 @@ > from .logger import DTSLOG, getLogger > from .settings import SETTINGS > from .test_result import BuildTargetResult, Result, TestCaseResult, > TestSuiteResult > -from .testbed_model import SutNode, TGNode > -from .testbed_model.hw.port import Port, PortLink > +from .testbed_model import Port, PortLink, SutNode, TGNode > from .utils import get_packet_summaries > > > @@ -453,7 +452,7 @@ def _execute_test_case( > > > def get_test_suites(testsuite_module_path: str) -> list[type[TestSuite]]= : > - def is_test_suite(object) -> bool: > + def is_test_suite(object: Any) -> bool: > try: > if issubclass(object, TestSuite) and object is not TestSuite= : > return True > diff --git a/dts/framework/testbed_model/__init__.py > b/dts/framework/testbed_model/__init__.py > index 5cbb859e47..8ced05653b 100644 > --- a/dts/framework/testbed_model/__init__.py > +++ b/dts/framework/testbed_model/__init__.py > @@ -9,15 +9,9 @@ > > # pylama:ignore=3DW0611 > > -from .hw import ( > - LogicalCore, > - LogicalCoreCount, > - LogicalCoreCountFilter, > - LogicalCoreList, > - LogicalCoreListFilter, > - VirtualDevice, > - lcore_filter, > -) > +from .cpu import LogicalCoreCount, LogicalCoreCountFilter, LogicalCoreLi= st > from .node import Node > +from .port import Port, PortLink > from .sut_node import SutNode > from .tg_node import TGNode > +from .virtual_device import VirtualDevice > diff --git a/dts/framework/testbed_model/hw/cpu.py > b/dts/framework/testbed_model/cpu.py > similarity index 95% > rename from dts/framework/testbed_model/hw/cpu.py > rename to dts/framework/testbed_model/cpu.py > index d1918a12dc..8fe785dfe4 100644 > --- a/dts/framework/testbed_model/hw/cpu.py > +++ b/dts/framework/testbed_model/cpu.py > @@ -272,3 +272,16 @@ def filter(self) -> list[LogicalCore]: > ) > > return filtered_lcores > + > + > +def lcore_filter( > + core_list: list[LogicalCore], > + filter_specifier: LogicalCoreCount | LogicalCoreList, > + ascending: bool, > +) -> LogicalCoreFilter: > + if isinstance(filter_specifier, LogicalCoreList): > + return LogicalCoreListFilter(core_list, filter_specifier, > ascending) > + elif isinstance(filter_specifier, LogicalCoreCount): > + return LogicalCoreCountFilter(core_list, filter_specifier, > ascending) > + else: > + raise ValueError(f"Unsupported filter r{filter_specifier}") > diff --git a/dts/framework/testbed_model/hw/__init__.py > b/dts/framework/testbed_model/hw/__init__.py > deleted file mode 100644 > index 88ccac0b0e..0000000000 > --- a/dts/framework/testbed_model/hw/__init__.py > +++ /dev/null > @@ -1,27 +0,0 @@ > -# SPDX-License-Identifier: BSD-3-Clause > -# Copyright(c) 2023 PANTHEON.tech s.r.o. > - > -# pylama:ignore=3DW0611 > - > -from .cpu import ( > - LogicalCore, > - LogicalCoreCount, > - LogicalCoreCountFilter, > - LogicalCoreFilter, > - LogicalCoreList, > - LogicalCoreListFilter, > -) > -from .virtual_device import VirtualDevice > - > - > -def lcore_filter( > - core_list: list[LogicalCore], > - filter_specifier: LogicalCoreCount | LogicalCoreList, > - ascending: bool, > -) -> LogicalCoreFilter: > - if isinstance(filter_specifier, LogicalCoreList): > - return LogicalCoreListFilter(core_list, filter_specifier, > ascending) > - elif isinstance(filter_specifier, LogicalCoreCount): > - return LogicalCoreCountFilter(core_list, filter_specifier, > ascending) > - else: > - raise ValueError(f"Unsupported filter r{filter_specifier}") > diff --git a/dts/framework/remote_session/linux_session.py > b/dts/framework/testbed_model/linux_session.py > similarity index 97% > rename from dts/framework/remote_session/linux_session.py > rename to dts/framework/testbed_model/linux_session.py > index a3f1a6bf3b..f472bb8f0f 100644 > --- a/dts/framework/remote_session/linux_session.py > +++ b/dts/framework/testbed_model/linux_session.py > @@ -9,10 +9,10 @@ > from typing_extensions import NotRequired > > from framework.exception import RemoteCommandExecutionError > -from framework.testbed_model import LogicalCore > -from framework.testbed_model.hw.port import Port > from framework.utils import expand_range > > +from .cpu import LogicalCore > +from .port import Port > from .posix_session import PosixSession > > > @@ -64,7 +64,7 @@ def get_remote_cpus(self, use_first_core: bool) -> > list[LogicalCore]: > lcores.append(LogicalCore(lcore, core, socket, node)) > return lcores > > - def get_dpdk_file_prefix(self, dpdk_prefix) -> str: > + def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str: > return dpdk_prefix > > def setup_hugepages(self, hugepage_amount: int, force_first_numa: > bool) -> None: > diff --git a/dts/framework/testbed_model/node.py > b/dts/framework/testbed_model/node.py > index fc01e0bf8e..fa5b143cdd 100644 > --- a/dts/framework/testbed_model/node.py > +++ b/dts/framework/testbed_model/node.py > @@ -12,23 +12,26 @@ > from typing import Any, Callable, Type, Union > > from framework.config import ( > + OS, > BuildTargetConfiguration, > ExecutionConfiguration, > NodeConfiguration, > ) > +from framework.exception import ConfigurationError > from framework.logger import DTSLOG, getLogger > -from framework.remote_session import InteractiveShellType, OSSession, > create_session > from framework.settings import SETTINGS > > -from .hw import ( > +from .cpu import ( > LogicalCore, > LogicalCoreCount, > LogicalCoreList, > LogicalCoreListFilter, > - VirtualDevice, > lcore_filter, > ) > -from .hw.port import Port > +from .linux_session import LinuxSession > +from .os_session import InteractiveShellType, OSSession > +from .port import Port > +from .virtual_device import VirtualDevice > > > class Node(ABC): > @@ -172,9 +175,9 @@ def create_interactive_shell( > > return self.main_session.create_interactive_shell( > shell_cls, > - app_args, > timeout, > privileged, > + app_args, > ) > > def filter_lcores( > @@ -205,7 +208,7 @@ def _get_remote_cpus(self) -> None: > self._logger.info("Getting CPU information.") > self.lcores =3D > self.main_session.get_remote_cpus(self.config.use_first_core) > > - def _setup_hugepages(self): > + def _setup_hugepages(self) -> None: > """ > Setup hugepages on the Node. Different architectures can supply > different > amounts of memory for hugepages and numa-based hugepage > allocation may need > @@ -249,3 +252,13 @@ def skip_setup(func: Callable[..., Any]) -> > Callable[..., Any]: > return lambda *args: None > else: > return func > + > + > +def create_session( > + node_config: NodeConfiguration, name: str, logger: DTSLOG > +) -> OSSession: > + match node_config.os: > + case OS.linux: > + return LinuxSession(node_config, name, logger) > + case _: > + raise ConfigurationError(f"Unsupported OS {node_config.os}") > diff --git a/dts/framework/remote_session/os_session.py > b/dts/framework/testbed_model/os_session.py > similarity index 95% > rename from dts/framework/remote_session/os_session.py > rename to dts/framework/testbed_model/os_session.py > index 8a709eac1c..76e595a518 100644 > --- a/dts/framework/remote_session/os_session.py > +++ b/dts/framework/testbed_model/os_session.py > @@ -10,19 +10,19 @@ > > from framework.config import Architecture, NodeConfiguration, NodeInfo > from framework.logger import DTSLOG > -from framework.remote_session.remote import InteractiveShell > -from framework.settings import SETTINGS > -from framework.testbed_model import LogicalCore > -from framework.testbed_model.hw.port import Port > -from framework.utils import MesonArgs > - > -from .remote import ( > +from framework.remote_session import ( > CommandResult, > InteractiveRemoteSession, > + InteractiveShell, > RemoteSession, > create_interactive_session, > create_remote_session, > ) > +from framework.settings import SETTINGS > +from framework.utils import MesonArgs > + > +from .cpu import LogicalCore > +from .port import Port > > InteractiveShellType =3D TypeVar("InteractiveShellType", > bound=3DInteractiveShell) > > @@ -85,9 +85,9 @@ def send_command( > def create_interactive_shell( > self, > shell_cls: Type[InteractiveShellType], > - eal_parameters: str, > timeout: float, > privileged: bool, > + app_args: str, > ) -> InteractiveShellType: > """ > See "create_interactive_shell" in SutNode > @@ -96,7 +96,7 @@ def create_interactive_shell( > self.interactive_session.session, > self._logger, > self._get_privileged_command if privileged else None, > - eal_parameters, > + app_args, > timeout, > ) > > @@ -113,7 +113,7 @@ def _get_privileged_command(command: str) -> str: > """ > > @abstractmethod > - def guess_dpdk_remote_dir(self, remote_dir) -> PurePath: > + def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> > PurePath: > """ > Try to find DPDK remote dir in remote_dir. > """ > @@ -227,7 +227,7 @@ def kill_cleanup_dpdk_apps(self, dpdk_prefix_list: > Iterable[str]) -> None: > """ > > @abstractmethod > - def get_dpdk_file_prefix(self, dpdk_prefix) -> str: > + def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str: > """ > Get the DPDK file prefix that will be used when running DPDK app= s. > """ > diff --git a/dts/framework/testbed_model/hw/port.py > b/dts/framework/testbed_model/port.py > similarity index 100% > rename from dts/framework/testbed_model/hw/port.py > rename to dts/framework/testbed_model/port.py > diff --git a/dts/framework/remote_session/posix_session.py > b/dts/framework/testbed_model/posix_session.py > similarity index 98% > rename from dts/framework/remote_session/posix_session.py > rename to dts/framework/testbed_model/posix_session.py > index 5da0516e05..1d1d5b1b26 100644 > --- a/dts/framework/remote_session/posix_session.py > +++ b/dts/framework/testbed_model/posix_session.py > @@ -32,7 +32,7 @@ def combine_short_options(**opts: bool) -> str: > > return ret_opts > > - def guess_dpdk_remote_dir(self, remote_dir) -> PurePosixPath: > + def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> > PurePosixPath: > remote_guess =3D self.join_remote_path(remote_dir, "dpdk-*") > result =3D self.send_command(f"ls -d {remote_guess} | tail -1") > return PurePosixPath(result.stdout) > @@ -219,7 +219,7 @@ def _remove_dpdk_runtime_dirs( > for dpdk_runtime_dir in dpdk_runtime_dirs: > self.remove_remote_dir(dpdk_runtime_dir) > > - def get_dpdk_file_prefix(self, dpdk_prefix) -> str: > + def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str: > return "" > > def get_compiler_version(self, compiler_name: str) -> str: > diff --git a/dts/framework/testbed_model/sut_node.py > b/dts/framework/testbed_model/sut_node.py > index 4161d3a4d5..17deea06e2 100644 > --- a/dts/framework/testbed_model/sut_node.py > +++ b/dts/framework/testbed_model/sut_node.py > @@ -15,12 +15,14 @@ > NodeInfo, > SutNodeConfiguration, > ) > -from framework.remote_session import CommandResult, InteractiveShellType= , > OSSession > +from framework.remote_session import CommandResult > from framework.settings import SETTINGS > from framework.utils import MesonArgs > > -from .hw import LogicalCoreCount, LogicalCoreList, VirtualDevice > +from .cpu import LogicalCoreCount, LogicalCoreList > from .node import Node > +from .os_session import InteractiveShellType, OSSession > +from .virtual_device import VirtualDevice > > > class EalParameters(object): > @@ -307,7 +309,7 @@ def create_eal_parameters( > prefix: str =3D "dpdk", > append_prefix_timestamp: bool =3D True, > no_pci: bool =3D False, > - vdevs: list[VirtualDevice] =3D None, > + vdevs: list[VirtualDevice] | None =3D None, > other_eal_param: str =3D "", > ) -> "EalParameters": > """ > diff --git a/dts/framework/testbed_model/tg_node.py > b/dts/framework/testbed_model/tg_node.py > index 27025cfa31..166eb8430e 100644 > --- a/dts/framework/testbed_model/tg_node.py > +++ b/dts/framework/testbed_model/tg_node.py > @@ -16,16 +16,11 @@ > > from scapy.packet import Packet # type: ignore[import] > > -from framework.config import ( > - ScapyTrafficGeneratorConfig, > - TGNodeConfiguration, > - TrafficGeneratorType, > -) > -from framework.exception import ConfigurationError > - > -from .capturing_traffic_generator import CapturingTrafficGenerator > -from .hw.port import Port > +from framework.config import TGNodeConfiguration > + > from .node import Node > +from .port import Port > +from .traffic_generator import CapturingTrafficGenerator, > create_traffic_generator > > > class TGNode(Node): > @@ -80,20 +75,3 @@ def close(self) -> None: > """Free all resources used by the node""" > self.traffic_generator.close() > super(TGNode, self).close() > - > - > -def create_traffic_generator( > - tg_node: TGNode, traffic_generator_config: ScapyTrafficGeneratorConf= ig > -) -> CapturingTrafficGenerator: > - """A factory function for creating traffic generator object from use= r > config.""" > - > - from .scapy import ScapyTrafficGenerator > - > - match traffic_generator_config.traffic_generator_type: > - case TrafficGeneratorType.SCAPY: > - return ScapyTrafficGenerator(tg_node, > traffic_generator_config) > - case _: > - raise ConfigurationError( > - "Unknown traffic generator: " > - f"{traffic_generator_config.traffic_generator_type}" > - ) > diff --git a/dts/framework/testbed_model/traffic_generator/__init__.py > b/dts/framework/testbed_model/traffic_generator/__init__.py > new file mode 100644 > index 0000000000..11bfa1ee0f > --- /dev/null > +++ b/dts/framework/testbed_model/traffic_generator/__init__.py > @@ -0,0 +1,24 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2023 PANTHEON.tech s.r.o. > + > +from framework.config import ScapyTrafficGeneratorConfig, > TrafficGeneratorType > +from framework.exception import ConfigurationError > +from framework.testbed_model.node import Node > + > +from .capturing_traffic_generator import CapturingTrafficGenerator > +from .scapy import ScapyTrafficGenerator > + > + > +def create_traffic_generator( > + tg_node: Node, traffic_generator_config: ScapyTrafficGeneratorConfig > +) -> CapturingTrafficGenerator: > + """A factory function for creating traffic generator object from use= r > config.""" > + > + match traffic_generator_config.traffic_generator_type: > + case TrafficGeneratorType.SCAPY: > + return ScapyTrafficGenerator(tg_node, > traffic_generator_config) > + case _: > + raise ConfigurationError( > + "Unknown traffic generator: " > + f"{traffic_generator_config.traffic_generator_type}" > + ) > diff --git a/dts/framework/testbed_model/capturing_traffic_generator.py > b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generat= or.py > similarity index 96% > rename from dts/framework/testbed_model/capturing_traffic_generator.py > rename to > dts/framework/testbed_model/traffic_generator/capturing_traffic_generator= .py > index ab98987f8e..e521211ef0 100644 > --- a/dts/framework/testbed_model/capturing_traffic_generator.py > +++ > b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generat= or.py > @@ -16,9 +16,9 @@ > from scapy.packet import Packet # type: ignore[import] > > from framework.settings import SETTINGS > +from framework.testbed_model.port import Port > from framework.utils import get_packet_summaries > > -from .hw.port import Port > from .traffic_generator import TrafficGenerator > > > @@ -130,7 +130,9 @@ def _send_packets_and_capture( > for the specified duration. It must be able to handle no receive= d > packets. > """ > > - def _write_capture_from_packets(self, capture_name: str, packets: > list[Packet]): > + def _write_capture_from_packets( > + self, capture_name: str, packets: list[Packet] > + ) -> None: > file_name =3D f"{SETTINGS.output_dir}/{capture_name}.pcap" > self._logger.debug(f"Writing packets to {file_name}.") > scapy.utils.wrpcap(file_name, packets) > diff --git a/dts/framework/testbed_model/scapy.py > b/dts/framework/testbed_model/traffic_generator/scapy.py > similarity index 95% > rename from dts/framework/testbed_model/scapy.py > rename to dts/framework/testbed_model/traffic_generator/scapy.py > index af0d4dbb25..51864b6e6b 100644 > --- a/dts/framework/testbed_model/scapy.py > +++ b/dts/framework/testbed_model/traffic_generator/scapy.py > @@ -24,16 +24,15 @@ > from scapy.packet import Packet # type: ignore[import] > > from framework.config import OS, ScapyTrafficGeneratorConfig > -from framework.logger import DTSLOG, getLogger > from framework.remote_session import PythonShell > from framework.settings import SETTINGS > +from framework.testbed_model.node import Node > +from framework.testbed_model.port import Port > > from .capturing_traffic_generator import ( > CapturingTrafficGenerator, > _get_default_capture_name, > ) > -from .hw.port import Port > -from .tg_node import TGNode > > """ > =3D=3D=3D=3D=3D=3D=3D=3D=3D BEGIN RPC FUNCTIONS =3D=3D=3D=3D=3D=3D=3D=3D= =3D > @@ -146,7 +145,7 @@ def quit(self) -> None: > self._BaseServer__shutdown_request =3D True > return None > > - def add_rpc_function(self, name: str, function_bytes: > xmlrpc.client.Binary): > + def add_rpc_function(self, name: str, function_bytes: > xmlrpc.client.Binary) -> None: > """Add a function to the server. > > This is meant to be executed remotely. > @@ -191,15 +190,9 @@ class > ScapyTrafficGenerator(CapturingTrafficGenerator): > session: PythonShell > rpc_server_proxy: xmlrpc.client.ServerProxy > _config: ScapyTrafficGeneratorConfig > - _tg_node: TGNode > - _logger: DTSLOG > - > - def __init__(self, tg_node: TGNode, config: > ScapyTrafficGeneratorConfig): > - self._config =3D config > - self._tg_node =3D tg_node > - self._logger =3D getLogger( > - f"{self._tg_node.name} {self._config.traffic_generator_type}= " > - ) > + > + def __init__(self, tg_node: Node, config: > ScapyTrafficGeneratorConfig): > + super().__init__(tg_node, config) > > assert ( > self._tg_node.config.os =3D=3D OS.linux > @@ -235,7 +228,7 @@ def __init__(self, tg_node: TGNode, config: > ScapyTrafficGeneratorConfig): > function_bytes =3D marshal.dumps(function.__code__) > self.rpc_server_proxy.add_rpc_function(function.__name__, > function_bytes) > > - def _start_xmlrpc_server_in_remote_python(self, listen_port: int): > + def _start_xmlrpc_server_in_remote_python(self, listen_port: int) -> > None: > # load the source of the function > src =3D inspect.getsource(QuittableXMLRPCServer) > # Lines with only whitespace break the repl if in the middle of = a > function > @@ -280,7 +273,7 @@ def _send_packets_and_capture( > scapy_packets =3D [Ether(packet.data) for packet in xmlrpc_packe= ts] > return scapy_packets > > - def close(self): > + def close(self) -> None: > try: > self.rpc_server_proxy.quit() > except ConnectionRefusedError: > diff --git a/dts/framework/testbed_model/traffic_generator.py > b/dts/framework/testbed_model/traffic_generator/traffic_generator.py > similarity index 80% > rename from dts/framework/testbed_model/traffic_generator.py > rename to > dts/framework/testbed_model/traffic_generator/traffic_generator.py > index 28c35d3ce4..ea7c3963da 100644 > --- a/dts/framework/testbed_model/traffic_generator.py > +++ b/dts/framework/testbed_model/traffic_generator/traffic_generator.py > @@ -12,11 +12,12 @@ > > from scapy.packet import Packet # type: ignore[import] > > -from framework.logger import DTSLOG > +from framework.config import TrafficGeneratorConfig > +from framework.logger import DTSLOG, getLogger > +from framework.testbed_model.node import Node > +from framework.testbed_model.port import Port > from framework.utils import get_packet_summaries > > -from .hw.port import Port > - > > class TrafficGenerator(ABC): > """The base traffic generator. > @@ -24,8 +25,17 @@ class TrafficGenerator(ABC): > Defines the few basic methods that each traffic generator must > implement. > """ > > + _config: TrafficGeneratorConfig > + _tg_node: Node > Is there a benefit to changing this to be a node instead of a TGNode? Wouldn't we want the capabilities of the TGNode to be accessible in the TrafficGenerator class? > _logger: DTSLOG > > + def __init__(self, tg_node: Node, config: TrafficGeneratorConfig): > + self._config =3D config > + self._tg_node =3D tg_node > + self._logger =3D getLogger( > + f"{self._tg_node.name} {self._config.traffic_generator_type}= " > + ) > + > def send_packet(self, packet: Packet, port: Port) -> None: > """Send a packet and block until it is fully sent. > > diff --git a/dts/framework/testbed_model/hw/virtual_device.py > b/dts/framework/testbed_model/virtual_device.py > similarity index 100% > rename from dts/framework/testbed_model/hw/virtual_device.py > rename to dts/framework/testbed_model/virtual_device.py > diff --git a/dts/framework/utils.py b/dts/framework/utils.py > index d27c2c5b5f..f0c916471c 100644 > --- a/dts/framework/utils.py > +++ b/dts/framework/utils.py > @@ -7,7 +7,6 @@ > import json > import os > import subprocess > -import sys > from enum import Enum > from pathlib import Path > from subprocess import SubprocessError > @@ -16,35 +15,7 @@ > > from .exception import ConfigurationError > > - > -class StrEnum(Enum): > - @staticmethod > - def _generate_next_value_( > - name: str, start: int, count: int, last_values: object > - ) -> str: > - return name > - > - def __str__(self) -> str: > - return self.name > - > - > -REGEX_FOR_PCI_ADDRESS =3D > "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/" > - > - > -def check_dts_python_version() -> None: > - if sys.version_info.major < 3 or ( > - sys.version_info.major =3D=3D 3 and sys.version_info.minor < 10 > - ): > - print( > - RED( > - ( > - "WARNING: DTS execution node's python version is > lower than" > - "python 3.10, is deprecated and will not work in > future releases." > - ) > - ), > - file=3Dsys.stderr, > - ) > - print(RED("Please use Python >=3D 3.10 instead"), file=3Dsys.std= err) > +REGEX_FOR_PCI_ADDRESS: str =3D > "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/" > > > def expand_range(range_str: str) -> list[int]: > @@ -67,7 +38,7 @@ def expand_range(range_str: str) -> list[int]: > return expanded_range > > > -def get_packet_summaries(packets: list[Packet]): > +def get_packet_summaries(packets: list[Packet]) -> str: > if len(packets) =3D=3D 1: > packet_summaries =3D packets[0].summary() > else: > @@ -77,8 +48,15 @@ def get_packet_summaries(packets: list[Packet]): > return f"Packet contents: \n{packet_summaries}" > > > -def RED(text: str) -> str: > - return f"\u001B[31;1m{str(text)}\u001B[0m" > +class StrEnum(Enum): > + @staticmethod > + def _generate_next_value_( > + name: str, start: int, count: int, last_values: object > + ) -> str: > + return name > + > + def __str__(self) -> str: > + return self.name > > > class MesonArgs(object): > @@ -225,5 +203,5 @@ def _delete_tarball(self) -> None: > if self._tarball_path and os.path.exists(self._tarball_path): > os.remove(self._tarball_path) > > - def __fspath__(self): > + def __fspath__(self) -> str: > return str(self._tarball_path) > diff --git a/dts/main.py b/dts/main.py > index 43311fa847..5d4714b0c3 100755 > --- a/dts/main.py > +++ b/dts/main.py > @@ -10,10 +10,17 @@ > > import logging > > -from framework import dts > +from framework import settings > > > def main() -> None: > + """Set DTS settings, then run DTS. > + > + The DTS settings are taken from the command line arguments and the > environment variables. > + """ > + settings.SETTINGS =3D settings.get_settings() > + from framework import dts > + > dts.run_all() > > > -- > 2.34.1 > > --000000000000bd18b0060a4b5f19 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


<= div dir=3D"ltr" class=3D"gmail_attr">On Wed, Nov 15, 2023 at 8:11=E2=80=AFA= M Juraj Linke=C5=A1 <juraj.linkes@pantheon.tech> wrote:
The standard Python tool for = generating API documentation, Sphinx,
imports modules one-by-one when generating the documentation. This
requires code changes:
* properly guarding argument parsing in the if __name__ =3D=3D '__main_= _'
=C2=A0 block,
* the logger used by DTS runner underwent the same treatment so that it
=C2=A0 doesn't create log files outside of a DTS run,
* however, DTS uses the arguments to construct an object holding global
=C2=A0 variables. The defaults for the global variables needed to be moved<= br> =C2=A0 from argument parsing elsewhere,
* importing the remote_session module from framework resulted in
=C2=A0 circular imports because of one module trying to import another
=C2=A0 module. This is fixed by reorganizing the code,
* some code reorganization was done because the resulting structure
=C2=A0 makes more sense, improving documentation clarity.

The are some other changes which are documentation related:
* added missing type annotation so they appear in the generated docs,
* reordered arguments in some methods,
* removed superfluous arguments and attributes,
* change private functions/methods/attributes to private and vice-versa.
The above all appear in the generated documentation and the with them,
the documentation is improved.

Signed-off-by: Juraj Linke=C5=A1 <juraj.linkes@pantheon.tech>
---
=C2=A0dts/framework/config/__init__.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 | 10 ++-
=C2=A0dts/framework/dts.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | 33 +++++--
=C2=A0dts/framework/exception.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 | 54 +++++-------
=C2=A0dts/framework/remote_session/__init__.py=C2=A0 =C2=A0 =C2=A0 | 41 +++= +-----
=C2=A0.../interactive_remote_session.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0|=C2=A0 0
=C2=A0.../{remote =3D> }/interactive_shell.py=C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0|=C2=A0 0
=C2=A0.../{remote =3D> }/python_shell.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 |=C2=A0 0
=C2=A0.../remote_session/remote/__init__.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0| 27 ------
=C2=A0.../{remote =3D> }/remote_session.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 |=C2=A0 0
=C2=A0.../{remote =3D> }/ssh_session.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0| 12 +--
=C2=A0.../{remote =3D> }/testpmd_shell.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0|=C2=A0 0
=C2=A0dts/framework/settings.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| 87 +++++++++++--------
=C2=A0dts/framework/test_result.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 |=C2=A0 4 +-
=C2=A0dts/framework/test_suite.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 7 +-
=C2=A0dts/framework/testbed_model/__init__.py=C2=A0 =C2=A0 =C2=A0 =C2=A0| 1= 2 +--
=C2=A0dts/framework/testbed_model/{hw =3D> }/cpu.py=C2=A0 =C2=A0| 13 +++=
=C2=A0dts/framework/testbed_model/hw/__init__.py=C2=A0 =C2=A0 | 27 ------ =C2=A0.../linux_session.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 |=C2=A0 6 +-
=C2=A0dts/framework/testbed_model/node.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0| 25 ++++--
=C2=A0.../os_session.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| 22 ++---
=C2=A0dts/framework/testbed_model/{hw =3D> }/port.py=C2=A0 |=C2=A0 0
=C2=A0.../posix_session.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 |=C2=A0 4 +-
=C2=A0dts/framework/testbed_model/sut_node.py=C2=A0 =C2=A0 =C2=A0 =C2=A0|= =C2=A0 8 +-
=C2=A0dts/framework/testbed_model/tg_node.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 | 3= 0 +------
=C2=A0.../traffic_generator/__init__.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0| 24 +++++
=C2=A0.../capturing_traffic_generator.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 |=C2=A0 6 +-
=C2=A0.../{ =3D> traffic_generator}/scapy.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 | 23 ++---
=C2=A0.../traffic_generator.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | 16 +++-
=C2=A0.../testbed_model/{hw =3D> }/virtual_device.py=C2=A0 |=C2=A0 0
=C2=A0dts/framework/utils.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | 46 +++-------
=C2=A0dts/main.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2= =A0 9 +-
=C2=A031 files changed, 258 insertions(+), 288 deletions(-)
=C2=A0rename dts/framework/remote_session/{remote =3D> }/interactive_rem= ote_session.py (100%)
=C2=A0rename dts/framework/remote_session/{remote =3D> }/interactive_she= ll.py (100%)
=C2=A0rename dts/framework/remote_session/{remote =3D> }/python_shell.py= (100%)
=C2=A0delete mode 100644 dts/framework/remote_session/remote/__init__.py =C2=A0rename dts/framework/remote_session/{remote =3D> }/remote_session.= py (100%)
=C2=A0rename dts/framework/remote_session/{remote =3D> }/ssh_session.py = (91%)
=C2=A0rename dts/framework/remote_session/{remote =3D> }/testpmd_shell.p= y (100%)
=C2=A0rename dts/framework/testbed_model/{hw =3D> }/cpu.py (95%)
=C2=A0delete mode 100644 dts/framework/testbed_model/hw/__init__.py
=C2=A0rename dts/framework/{remote_session =3D> testbed_model}/linux_ses= sion.py (97%)
=C2=A0rename dts/framework/{remote_session =3D> testbed_model}/os_sessio= n.py (95%)
=C2=A0rename dts/framework/testbed_model/{hw =3D> }/port.py (100%)
=C2=A0rename dts/framework/{remote_session =3D> testbed_model}/posix_ses= sion.py (98%)
=C2=A0create mode 100644 dts/framework/testbed_model/traffic_generator/__in= it__.py
=C2=A0rename dts/framework/testbed_model/{ =3D> traffic_generator}/captu= ring_traffic_generator.py (96%)
=C2=A0rename dts/framework/testbed_model/{ =3D> traffic_generator}/scapy= .py (95%)
=C2=A0rename dts/framework/testbed_model/{ =3D> traffic_generator}/traff= ic_generator.py (80%)
=C2=A0rename dts/framework/testbed_model/{hw =3D> }/virtual_device.py (1= 00%)

diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init= __.py
index cb7e00ba34..2044c82611 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -17,6 +17,7 @@
=C2=A0import warlock=C2=A0 # type: ignore[import]
=C2=A0import yaml

+from framework.exception import ConfigurationError
=C2=A0from framework.settings import SETTINGS
=C2=A0from framework.utils import StrEnum

@@ -89,7 +90,7 @@ class TrafficGeneratorConfig:
=C2=A0 =C2=A0 =C2=A0traffic_generator_type: TrafficGeneratorType

=C2=A0 =C2=A0 =C2=A0@staticmethod
-=C2=A0 =C2=A0 def from_dict(d: dict):
+=C2=A0 =C2=A0 def from_dict(d: dict) -> "ScapyTrafficGeneratorConf= ig":
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# This looks useless now, but is designed= to allow expansion to traffic
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# generators that require more configurat= ion later.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0match TrafficGeneratorType(d["type&q= uot;]):
@@ -97,6 +98,10 @@ def from_dict(d: dict):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return ScapyT= rafficGeneratorConfig(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0traffic_generator_type=3DTrafficGeneratorType.SCAPY
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 case _:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise Configuratio= nError(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f= 9;Unknown traffic generator type "{d["type"]}".' +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )


=C2=A0@dataclass(slots=3DTrue, frozen=3DTrue)
@@ -324,6 +329,3 @@ def load_config() -> Configuration:
=C2=A0 =C2=A0 =C2=A0config: dict[str, Any] =3D warlock.model_factory(schema= , name=3D"_Config")(config_data)
=C2=A0 =C2=A0 =C2=A0config_obj: Configuration =3D Configuration.from_dict(d= ict(config))
=C2=A0 =C2=A0 =C2=A0return config_obj
-
-
-CONFIGURATION =3D load_config()
diff --git a/dts/framework/dts.py b/dts/framework/dts.py
index f773f0c38d..4c7fb0c40a 100644
--- a/dts/framework/dts.py
+++ b/dts/framework/dts.py
@@ -6,19 +6,19 @@
=C2=A0import sys

=C2=A0from .config import (
-=C2=A0 =C2=A0 CONFIGURATION,
=C2=A0 =C2=A0 =C2=A0BuildTargetConfiguration,
=C2=A0 =C2=A0 =C2=A0ExecutionConfiguration,
=C2=A0 =C2=A0 =C2=A0TestSuiteConfig,
+=C2=A0 =C2=A0 load_config,
=C2=A0)
=C2=A0from .exception import BlockingTestSuiteError
=C2=A0from .logger import DTSLOG, getLogger
=C2=A0from .test_result import BuildTargetResult, DTSResult, ExecutionResul= t, Result
=C2=A0from .test_suite import get_test_suites
=C2=A0from .testbed_model import SutNode, TGNode
-from .utils import check_dts_python_version

-dts_logger: DTSLOG =3D getLogger("DTSRunner")
+# dummy defaults to satisfy linters
+dts_logger: DTSLOG =3D None=C2=A0 # type: ignore[assignment]
=C2=A0result: DTSResult =3D DTSResult(dts_logger)


@@ -30,14 +30,18 @@ def run_all() -> None:
=C2=A0 =C2=A0 =C2=A0global dts_logger
=C2=A0 =C2=A0 =C2=A0global result

+=C2=A0 =C2=A0 # create a regular DTS logger and create a new result with i= t
+=C2=A0 =C2=A0 dts_logger =3D getLogger("DTSRunner")
+=C2=A0 =C2=A0 result =3D DTSResult(dts_logger)
+
=C2=A0 =C2=A0 =C2=A0# check the python version of the server that run dts -=C2=A0 =C2=A0 check_dts_python_version()
+=C2=A0 =C2=A0 _check_dts_python_version()

=C2=A0 =C2=A0 =C2=A0sut_nodes: dict[str, SutNode] =3D {}
=C2=A0 =C2=A0 =C2=A0tg_nodes: dict[str, TGNode] =3D {}
=C2=A0 =C2=A0 =C2=A0try:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# for all Execution sections
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 for execution in CONFIGURATION.executions:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 for execution in load_config().executions:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0sut_node =3D sut_nodes.get(= execution.system_under_test_node.name)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0tg_node =3D tg_nodes.get(execution.traffic_generator_node.name)

@@ -82,6 +86,25 @@ def run_all() -> None:
=C2=A0 =C2=A0 =C2=A0_exit_dts()


+def _check_dts_python_version() -> None:
+=C2=A0 =C2=A0 def RED(text: str) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"\u001B[31;1m{str(text)}\u001B[0m= "
+
+=C2=A0 =C2=A0 if sys.version_info.major < 3 or (
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 sys.version_info.major =3D=3D 3 and sys.versio= n_info.minor < 10
+=C2=A0 =C2=A0 ):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 print(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 RED(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quo= t;WARNING: DTS execution node's python version is lower than"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quo= t;python 3.10, is deprecated and will not work in future releases." +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ),
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 file=3Dsys.stderr,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 print(RED("Please use Python >=3D 3.10= instead"), file=3Dsys.stderr)
+
+
=C2=A0def _run_execution(
=C2=A0 =C2=A0 =C2=A0sut_node: SutNode,
=C2=A0 =C2=A0 =C2=A0tg_node: TGNode,
diff --git a/dts/framework/exception.py b/dts/framework/exception.py
index 001a5a5496..7489c03570 100644
--- a/dts/framework/exception.py
+++ b/dts/framework/exception.py
@@ -42,19 +42,14 @@ class SSHTimeoutError(DTSError):
=C2=A0 =C2=A0 =C2=A0Command execution timeout.
=C2=A0 =C2=A0 =C2=A0"""

-=C2=A0 =C2=A0 command: str
-=C2=A0 =C2=A0 output: str
=C2=A0 =C2=A0 =C2=A0severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH= _ERR
+=C2=A0 =C2=A0 _command: str

-=C2=A0 =C2=A0 def __init__(self, command: str, output: str):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.command =3D command
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.output =3D output
+=C2=A0 =C2=A0 def __init__(self, command: str):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._command =3D command

=C2=A0 =C2=A0 =C2=A0def __str__(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"TIMEOUT on {self.command}"<= br> -
-=C2=A0 =C2=A0 def get_output(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self.output
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"TIMEOUT on {self._command}"=


=C2=A0class SSHConnectionError(DTSError):
@@ -62,18 +57,18 @@ class SSHConnectionError(DTSError):
=C2=A0 =C2=A0 =C2=A0SSH connection error.
=C2=A0 =C2=A0 =C2=A0"""

-=C2=A0 =C2=A0 host: str
-=C2=A0 =C2=A0 errors: list[str]
=C2=A0 =C2=A0 =C2=A0severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH= _ERR
+=C2=A0 =C2=A0 _host: str
+=C2=A0 =C2=A0 _errors: list[str]

=C2=A0 =C2=A0 =C2=A0def __init__(self, host: str, errors: list[str] | None = =3D None):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.host =3D host
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.errors =3D [] if errors is None else erro= rs
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._host =3D host
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._errors =3D [] if errors is None else err= ors

=C2=A0 =C2=A0 =C2=A0def __str__(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 message =3D f"Error trying to connect wit= h {self.host}."
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 if self.errors:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 message +=3D f" Errors enco= untered while retrying: {', '.join(self.errors)}"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 message =3D f"Error trying to connect wit= h {self._host}."
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if self._errors:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 message +=3D f" Errors enco= untered while retrying: {', '.join(self._errors)}"

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return message

@@ -84,14 +79,14 @@ class SSHSessionDeadError(DTSError):
=C2=A0 =C2=A0 =C2=A0It can no longer be used.
=C2=A0 =C2=A0 =C2=A0"""

-=C2=A0 =C2=A0 host: str
=C2=A0 =C2=A0 =C2=A0severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH= _ERR
+=C2=A0 =C2=A0 _host: str

=C2=A0 =C2=A0 =C2=A0def __init__(self, host: str):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.host =3D host
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._host =3D host

=C2=A0 =C2=A0 =C2=A0def __str__(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"SSH session with {self.host} has= died"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"SSH session with {self._host} ha= s died"


=C2=A0class ConfigurationError(DTSError):
@@ -107,18 +102,18 @@ class RemoteCommandExecutionError(DTSError):
=C2=A0 =C2=A0 =C2=A0Raised when a command executed on a Node returns a non-= zero exit status.
=C2=A0 =C2=A0 =C2=A0"""

-=C2=A0 =C2=A0 command: str
-=C2=A0 =C2=A0 command_return_code: int
=C2=A0 =C2=A0 =C2=A0severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.REM= OTE_CMD_EXEC_ERR
+=C2=A0 =C2=A0 command: str
+=C2=A0 =C2=A0 _command_return_code: int

=C2=A0 =C2=A0 =C2=A0def __init__(self, command: str, command_return_code: i= nt):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.command =3D command
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.command_return_code =3D command_return_co= de
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._command_return_code =3D command_return_c= ode

=C2=A0 =C2=A0 =C2=A0def __str__(self) -> str:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return (
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0f"Command {self.comman= d} returned a non-zero exit code: "
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{self.command_return_code= }"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{self._command_return_cod= e}"
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)


@@ -143,22 +138,15 @@ class TestCaseVerifyError(DTSError):
=C2=A0 =C2=A0 =C2=A0Used in test cases to verify the expected behavior.
=C2=A0 =C2=A0 =C2=A0"""

-=C2=A0 =C2=A0 value: str
=C2=A0 =C2=A0 =C2=A0severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.TES= TCASE_VERIFY_ERR

-=C2=A0 =C2=A0 def __init__(self, value: str):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.value =3D value
-
-=C2=A0 =C2=A0 def __str__(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return repr(self.value)
-

Does this change mean we are no longer provi= ding descriptions for what failing the verification means? I guess there is= n't really harm in removing that functionality, but I'm not sure I = see the value in removing the extra information either.
=C2=A0

=C2=A0class BlockingTestSuiteError(DTSError):
-=C2=A0 =C2=A0 suite_name: str
=C2=A0 =C2=A0 =C2=A0severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.BLO= CKING_TESTSUITE_ERR
+=C2=A0 =C2=A0 _suite_name: str

=C2=A0 =C2=A0 =C2=A0def __init__(self, suite_name: str) -> None:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.suite_name =3D suite_name
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._suite_name =3D suite_name

=C2=A0 =C2=A0 =C2=A0def __str__(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"Blocking suite {self.suite_name}= failed."
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"Blocking suite {self._suite_name= } failed."
diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remot= e_session/__init__.py
index 00b6d1f03a..5e7ddb2b05 100644
--- a/dts/framework/remote_session/__init__.py
+++ b/dts/framework/remote_session/__init__.py
@@ -12,29 +12,24 @@

=C2=A0# pylama:ignore=3DW0611

-from framework.config import OS, NodeConfiguration
-from framework.exception import ConfigurationError
+from framework.config import NodeConfiguration
=C2=A0from framework.logger import DTSLOG

-from .linux_session import LinuxSession
-from .os_session import InteractiveShellType, OSSession
-from .remote import (
-=C2=A0 =C2=A0 CommandResult,
-=C2=A0 =C2=A0 InteractiveRemoteSession,
-=C2=A0 =C2=A0 InteractiveShell,
-=C2=A0 =C2=A0 PythonShell,
-=C2=A0 =C2=A0 RemoteSession,
-=C2=A0 =C2=A0 SSHSession,
-=C2=A0 =C2=A0 TestPmdDevice,
-=C2=A0 =C2=A0 TestPmdShell,
-)
-
-
-def create_session(
+from .interactive_remote_session import InteractiveRemoteSession
+from .interactive_shell import InteractiveShell
+from .python_shell import PythonShell
+from .remote_session import CommandResult, RemoteSession
+from .ssh_session import SSHSession
+from .testpmd_shell import TestPmdShell
+
+
+def create_remote_session(
=C2=A0 =C2=A0 =C2=A0node_config: NodeConfiguration, name: str, logger: DTSL= OG
-) -> OSSession:
-=C2=A0 =C2=A0 match node_config.os:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 case OS.linux:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return LinuxSession(node_config,= name, logger)
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 case _:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise ConfigurationError(f"= Unsupported OS {node_config.os}")
+) -> RemoteSession:
+=C2=A0 =C2=A0 return SSHSession(node_config, name, logger)
+
+
+def create_interactive_session(
+=C2=A0 =C2=A0 node_config: NodeConfiguration, logger: DTSLOG
+) -> InteractiveRemoteSession:
+=C2=A0 =C2=A0 return InteractiveRemoteSession(node_config, logger)
diff --git a/dts/framework/remote_session/remote/interactive_remote_session= .py b/dts/framework/remote_session/interactive_remote_session.py
similarity index 100%
rename from dts/framework/remote_session/remote/interactive_remote_session.= py
rename to dts/framework/remote_session/interactive_remote_session.py
diff --git a/dts/framework/remote_session/remote/interactive_shell.py b/dts= /framework/remote_session/interactive_shell.py
similarity index 100%
rename from dts/framework/remote_session/remote/interactive_shell.py
rename to dts/framework/remote_session/interactive_shell.py
diff --git a/dts/framework/remote_session/remote/python_shell.py b/dts/fram= ework/remote_session/python_shell.py
similarity index 100%
rename from dts/framework/remote_session/remote/python_shell.py
rename to dts/framework/remote_session/python_shell.py
diff --git a/dts/framework/remote_session/remote/__init__.py b/dts/framewor= k/remote_session/remote/__init__.py
deleted file mode 100644
index 06403691a5..0000000000
--- a/dts/framework/remote_session/remote/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2023 PANTHEON.tech s.r.o.
-# Copyright(c) 2023 University of New Hampshire
-
-# pylama:ignore=3DW0611
-
-from framework.config import NodeConfiguration
-from framework.logger import DTSLOG
-
-from .interactive_remote_session import InteractiveRemoteSession
-from .interactive_shell import InteractiveShell
-from .python_shell import PythonShell
-from .remote_session import CommandResult, RemoteSession
-from .ssh_session import SSHSession
-from .testpmd_shell import TestPmdDevice, TestPmdShell
-
-
-def create_remote_session(
-=C2=A0 =C2=A0 node_config: NodeConfiguration, name: str, logger: DTSLOG -) -> RemoteSession:
-=C2=A0 =C2=A0 return SSHSession(node_config, name, logger)
-
-
-def create_interactive_session(
-=C2=A0 =C2=A0 node_config: NodeConfiguration, logger: DTSLOG
-) -> InteractiveRemoteSession:
-=C2=A0 =C2=A0 return InteractiveRemoteSession(node_config, logger)
diff --git a/dts/framework/remote_session/remote/remote_session.py b/dts/fr= amework/remote_session/remote_session.py
similarity index 100%
rename from dts/framework/remote_session/remote/remote_session.py
rename to dts/framework/remote_session/remote_session.py
diff --git a/dts/framework/remote_session/remote/ssh_session.py b/dts/frame= work/remote_session/ssh_session.py
similarity index 91%
rename from dts/framework/remote_session/remote/ssh_session.py
rename to dts/framework/remote_session/ssh_session.py
index 8d127f1601..cee11d14d6 100644
--- a/dts/framework/remote_session/remote/ssh_session.py
+++ b/dts/framework/remote_session/ssh_session.py
@@ -18,9 +18,7 @@
=C2=A0 =C2=A0 =C2=A0SSHException,
=C2=A0)

-from framework.config import NodeConfiguration
=C2=A0from framework.exception import SSHConnectionError, SSHSessionDeadErr= or, SSHTimeoutError
-from framework.logger import DTSLOG

=C2=A0from .remote_session import CommandResult, RemoteSession

@@ -45,14 +43,6 @@ class SSHSession(RemoteSession):

=C2=A0 =C2=A0 =C2=A0session: Connection

-=C2=A0 =C2=A0 def __init__(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 node_config: NodeConfiguration,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 session_name: str,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 logger: DTSLOG,
-=C2=A0 =C2=A0 ):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 super(SSHSession, self).__init__(node_config, = session_name, logger)
-
=C2=A0 =C2=A0 =C2=A0def _connect(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0errors =3D []
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0retry_attempts =3D 10
@@ -117,7 +107,7 @@ def _send_command(

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0except CommandTimedOut as e:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._logger.exception(e) -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise SSHTimeoutError(command, e= .result.stderr) from e
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise SSHTimeoutError(command) f= rom e

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return CommandResult(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.name, command, output.stdou= t, output.stderr, output.return_code
diff --git a/dts/framework/remote_session/remote/testpmd_shell.py b/dts/fra= mework/remote_session/testpmd_shell.py
similarity index 100%
rename from dts/framework/remote_session/remote/testpmd_shell.py
rename to dts/framework/remote_session/testpmd_shell.py
diff --git a/dts/framework/settings.py b/dts/framework/settings.py
index cfa39d011b..7f5841d073 100644
--- a/dts/framework/settings.py
+++ b/dts/framework/settings.py
@@ -6,7 +6,7 @@
=C2=A0import argparse
=C2=A0import os
=C2=A0from collections.abc import Callable, Iterable, Sequence
-from dataclasses import dataclass
+from dataclasses import dataclass, field
=C2=A0from pathlib import Path
=C2=A0from typing import Any, TypeVar

@@ -22,8 +22,8 @@ def __init__(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0option_strings: Sequence[st= r],
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0dest: str,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0nargs: str | int | None =3D= None,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 const: str | None =3D None,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 default: str =3D None,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 const: bool | None =3D None,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 default: Any =3D None,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0type: Callable[[str], _T | = argparse.FileType | None] =3D None,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0choices: Iterable[_T] | Non= e =3D None,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0required: bool =3D False, @@ -32,6 +32,12 @@ def __init__(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0env_var_value =3D os.enviro= n.get(env_var)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0default =3D env_var_value o= r default
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if const is not None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 nargs =3D 0
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 default =3D const = if env_var_value else default
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 type =3D None
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 choices =3D None +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 metavar =3D None =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super(_EnvironmentArgument,= self).__init__(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0option_string= s,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0dest,
@@ -52,22 +58,28 @@ def __call__(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0values: Any,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0option_string: str =3D None= ,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0) -> None:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 setattr(namespace, self.dest, va= lues)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if self.const is not None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 setattr(namespace,= self.dest, self.const)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 else:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 setattr(namespace,= self.dest, values)

=C2=A0 =C2=A0 =C2=A0return _EnvironmentArgument


-@dataclass(slots=3DTrue, frozen=3DTrue)
-class _Settings:
-=C2=A0 =C2=A0 config_file_path: str
-=C2=A0 =C2=A0 output_dir: str
-=C2=A0 =C2=A0 timeout: float
-=C2=A0 =C2=A0 verbose: bool
-=C2=A0 =C2=A0 skip_setup: bool
-=C2=A0 =C2=A0 dpdk_tarball_path: Path
-=C2=A0 =C2=A0 compile_timeout: float
-=C2=A0 =C2=A0 test_cases: list
-=C2=A0 =C2=A0 re_run: int
+@dataclass(slots=3DTrue)
+class Settings:
+=C2=A0 =C2=A0 config_file_path: Path =3D Path(__file__).parent.parent.join= path("conf.yaml")
+=C2=A0 =C2=A0 output_dir: str =3D "output"
+=C2=A0 =C2=A0 timeout: float =3D 15
+=C2=A0 =C2=A0 verbose: bool =3D False
+=C2=A0 =C2=A0 skip_setup: bool =3D False
+=C2=A0 =C2=A0 dpdk_tarball_path: Path | str =3D "dpdk.tar.xz" +=C2=A0 =C2=A0 compile_timeout: float =3D 1200
+=C2=A0 =C2=A0 test_cases: list[str] =3D field(default_factory=3Dlist)
+=C2=A0 =C2=A0 re_run: int =3D 0
+
+
+SETTINGS: Settings =3D Settings()


=C2=A0def _get_parser() -> argparse.ArgumentParser:
@@ -81,7 +93,8 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0parser.add_argument(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--config-file",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_CFG_FILE"= ;),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D"conf.yaml",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.config_file_path,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 type=3DPath,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0help=3D"[DTS_CFG_FILE] configuration= file that describes the test cases, SUTs "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"and targets.",
=C2=A0 =C2=A0 =C2=A0)
@@ -90,7 +103,7 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--output-dir",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--output",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_OUTPUT_DIR&qu= ot;),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D"output",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.output_dir,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0help=3D"[DTS_OUTPUT_DIR] Output dire= ctory where dts logs and results are saved.",
=C2=A0 =C2=A0 =C2=A0)

@@ -98,7 +111,7 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"-t",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--timeout",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_TIMEOUT"= ),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D15,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.timeout,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0type=3Dfloat,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0help=3D"[DTS_TIMEOUT] The default ti= meout for all DTS operations except for "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"compiling DPDK.",
@@ -108,8 +121,9 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"-v",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--verbose",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_VERBOSE"= ),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D"N",
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 help=3D"[DTS_VERBOSE] Set to 'Y' = to enable verbose output, logging all messages "
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.verbose,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 const=3DTrue,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 help=3D"[DTS_VERBOSE] Specify to enable v= erbose output, logging all messages "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"to the console.",
=C2=A0 =C2=A0 =C2=A0)

@@ -117,8 +131,8 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"-s",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--skip-setup",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_SKIP_SETUP&qu= ot;),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D"N",
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 help=3D"[DTS_SKIP_SETUP] Set to 'Y= 9; to skip all setup steps on SUT and TG nodes.",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 const=3DTrue,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 help=3D"[DTS_SKIP_SETUP] Specify to skip = all setup steps on SUT and TG nodes.",
=C2=A0 =C2=A0 =C2=A0)

=C2=A0 =C2=A0 =C2=A0parser.add_argument(
@@ -126,7 +140,7 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--snapshot",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--git-ref",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_DPDK_TARBALL&= quot;),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D"dpdk.tar.xz",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.dpdk_tarball_path,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0type=3DPath,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0help=3D"[DTS_DPDK_TARBALL] Path to D= PDK source code tarball or a git commit ID, "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"tag ID or tree ID to test. To test = local changes, first commit them, "
@@ -136,7 +150,7 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0parser.add_argument(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--compile-timeout",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_COMPILE_TIMEO= UT"),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D1200,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.compile_timeout,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0type=3Dfloat,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0help=3D"[DTS_COMPILE_TIMEOUT] The ti= meout for compiling DPDK.",
=C2=A0 =C2=A0 =C2=A0)
@@ -153,7 +167,7 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--re-run",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"--re_run",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0action=3D_env_arg("DTS_RERUN"),=
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3D0,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 default=3DSETTINGS.re_run,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0type=3Dint,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0help=3D"[DTS_RERUN] Re-run each test= case the specified amount of times "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"if a test failure occurs",
@@ -162,23 +176,22 @@ def _get_parser() -> argparse.ArgumentParser:
=C2=A0 =C2=A0 =C2=A0return parser


-def _get_settings() -> _Settings:
+def get_settings() -> Settings:
=C2=A0 =C2=A0 =C2=A0parsed_args =3D _get_parser().parse_args()
-=C2=A0 =C2=A0 return _Settings(
+=C2=A0 =C2=A0 return Settings(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0config_file_path=3Dparsed_args.config_fil= e,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0output_dir=3Dparsed_args.output_dir,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0timeout=3Dparsed_args.timeout,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 verbose=3D(parsed_args.verbose =3D=3D "Y&= quot;),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 skip_setup=3D(parsed_args.skip_setup =3D=3D &q= uot;Y"),
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 verbose=3Dparsed_args.verbose,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 skip_setup=3Dparsed_args.skip_setup,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0dpdk_tarball_path=3DPath(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 DPDKGitTarball(parsed_args.tarba= ll, parsed_args.output_dir)
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 )
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 if not os.path.exists(parsed_args.tarball)
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 else Path(parsed_args.tarball),
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Path(DPDKGitTarball(parsed_args.= tarball, parsed_args.output_dir))
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if not os.path.exists(parsed_arg= s.tarball)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 else Path(parsed_args.tarball) +=C2=A0 =C2=A0 =C2=A0 =C2=A0 ),
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0compile_timeout=3Dparsed_args.compile_tim= eout,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 test_cases=3Dparsed_args.test_cases.split(&quo= t;,") if parsed_args.test_cases else [],
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 test_cases=3D(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 parsed_args.test_cases.split(&qu= ot;,") if parsed_args.test_cases else []
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ),
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0re_run=3Dparsed_args.re_run,
=C2=A0 =C2=A0 =C2=A0)
-
-
-SETTINGS: _Settings =3D _get_settings()
diff --git a/dts/framework/test_result.py b/dts/framework/test_result.py index f0fbe80f6f..603e18872c 100644
--- a/dts/framework/test_result.py
+++ b/dts/framework/test_result.py
@@ -254,7 +254,7 @@ def add_build_target(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._inner_results.append(build_target_r= esult)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return build_target_result

-=C2=A0 =C2=A0 def add_sut_info(self, sut_info: NodeInfo):
+=C2=A0 =C2=A0 def add_sut_info(self, sut_info: NodeInfo) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.sut_os_name =3D sut_info.os_name
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.sut_os_version =3D sut_info.os_versi= on
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.sut_kernel_version =3D sut_info.kern= el_version
@@ -297,7 +297,7 @@ def add_execution(self, sut_node: NodeConfiguration) -&= gt; ExecutionResult:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._inner_results.append(execution_resu= lt)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return execution_result

-=C2=A0 =C2=A0 def add_error(self, error) -> None:
+=C2=A0 =C2=A0 def add_error(self, error: Exception) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._errors.append(error)

=C2=A0 =C2=A0 =C2=A0def process(self) -> None:
diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py
index 3b890c0451..d53553bf34 100644
--- a/dts/framework/test_suite.py
+++ b/dts/framework/test_suite.py
@@ -11,7 +11,7 @@
=C2=A0import re
=C2=A0from ipaddress import IPv4Interface, IPv6Interface, ip_interface
=C2=A0from types import MethodType
-from typing import Union
+from typing import Any, Union

=C2=A0from scapy.layers.inet import IP=C2=A0 # type: ignore[import]
=C2=A0from scapy.layers.l2 import Ether=C2=A0 # type: ignore[import]
@@ -26,8 +26,7 @@
=C2=A0from .logger import DTSLOG, getLogger
=C2=A0from .settings import SETTINGS
=C2=A0from .test_result import BuildTargetResult, Result, TestCaseResult, T= estSuiteResult
-from .testbed_model import SutNode, TGNode
-from .testbed_model.hw.port import Port, PortLink
+from .testbed_model import Port, PortLink, SutNode, TGNode
=C2=A0from .utils import get_packet_summaries


@@ -453,7 +452,7 @@ def _execute_test_case(


=C2=A0def get_test_suites(testsuite_module_path: str) -> list[type[TestS= uite]]:
-=C2=A0 =C2=A0 def is_test_suite(object) -> bool:
+=C2=A0 =C2=A0 def is_test_suite(object: Any) -> bool:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0try:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if issubclass(object, TestS= uite) and object is not TestSuite:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return True diff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbe= d_model/__init__.py
index 5cbb859e47..8ced05653b 100644
--- a/dts/framework/testbed_model/__init__.py
+++ b/dts/framework/testbed_model/__init__.py
@@ -9,15 +9,9 @@

=C2=A0# pylama:ignore=3DW0611

-from .hw import (
-=C2=A0 =C2=A0 LogicalCore,
-=C2=A0 =C2=A0 LogicalCoreCount,
-=C2=A0 =C2=A0 LogicalCoreCountFilter,
-=C2=A0 =C2=A0 LogicalCoreList,
-=C2=A0 =C2=A0 LogicalCoreListFilter,
-=C2=A0 =C2=A0 VirtualDevice,
-=C2=A0 =C2=A0 lcore_filter,
-)
+from .cpu import LogicalCoreCount, LogicalCoreCountFilter, LogicalCoreList=
=C2=A0from .node import Node
+from .port import Port, PortLink
=C2=A0from .sut_node import SutNode
=C2=A0from .tg_node import TGNode
+from .virtual_device import VirtualDevice
diff --git a/dts/framework/testbed_model/hw/cpu.py b/dts/framework/testbed_= model/cpu.py
similarity index 95%
rename from dts/framework/testbed_model/hw/cpu.py
rename to dts/framework/testbed_model/cpu.py
index d1918a12dc..8fe785dfe4 100644
--- a/dts/framework/testbed_model/hw/cpu.py
+++ b/dts/framework/testbed_model/cpu.py
@@ -272,3 +272,16 @@ def filter(self) -> list[LogicalCore]:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return filtered_lcores
+
+
+def lcore_filter(
+=C2=A0 =C2=A0 core_list: list[LogicalCore],
+=C2=A0 =C2=A0 filter_specifier: LogicalCoreCount | LogicalCoreList,
+=C2=A0 =C2=A0 ascending: bool,
+) -> LogicalCoreFilter:
+=C2=A0 =C2=A0 if isinstance(filter_specifier, LogicalCoreList):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return LogicalCoreListFilter(core_list, filter= _specifier, ascending)
+=C2=A0 =C2=A0 elif isinstance(filter_specifier, LogicalCoreCount):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return LogicalCoreCountFilter(core_list, filte= r_specifier, ascending)
+=C2=A0 =C2=A0 else:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 raise ValueError(f"Unsupported filter r{f= ilter_specifier}")
diff --git a/dts/framework/testbed_model/hw/__init__.py b/dts/framework/tes= tbed_model/hw/__init__.py
deleted file mode 100644
index 88ccac0b0e..0000000000
--- a/dts/framework/testbed_model/hw/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2023 PANTHEON.tech s.r.o.
-
-# pylama:ignore=3DW0611
-
-from .cpu import (
-=C2=A0 =C2=A0 LogicalCore,
-=C2=A0 =C2=A0 LogicalCoreCount,
-=C2=A0 =C2=A0 LogicalCoreCountFilter,
-=C2=A0 =C2=A0 LogicalCoreFilter,
-=C2=A0 =C2=A0 LogicalCoreList,
-=C2=A0 =C2=A0 LogicalCoreListFilter,
-)
-from .virtual_device import VirtualDevice
-
-
-def lcore_filter(
-=C2=A0 =C2=A0 core_list: list[LogicalCore],
-=C2=A0 =C2=A0 filter_specifier: LogicalCoreCount | LogicalCoreList,
-=C2=A0 =C2=A0 ascending: bool,
-) -> LogicalCoreFilter:
-=C2=A0 =C2=A0 if isinstance(filter_specifier, LogicalCoreList):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return LogicalCoreListFilter(core_list, filter= _specifier, ascending)
-=C2=A0 =C2=A0 elif isinstance(filter_specifier, LogicalCoreCount):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return LogicalCoreCountFilter(core_list, filte= r_specifier, ascending)
-=C2=A0 =C2=A0 else:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 raise ValueError(f"Unsupported filter r{f= ilter_specifier}")
diff --git a/dts/framework/remote_session/linux_session.py b/dts/framework/= testbed_model/linux_session.py
similarity index 97%
rename from dts/framework/remote_session/linux_session.py
rename to dts/framework/testbed_model/linux_session.py
index a3f1a6bf3b..f472bb8f0f 100644
--- a/dts/framework/remote_session/linux_session.py
+++ b/dts/framework/testbed_model/linux_session.py
@@ -9,10 +9,10 @@
=C2=A0from typing_extensions import NotRequired

=C2=A0from framework.exception import RemoteCommandExecutionError
-from framework.testbed_model import LogicalCore
-from framework.testbed_model.hw.port import Port
=C2=A0from framework.utils import expand_range

+from .cpu import LogicalCore
+from .port import Port
=C2=A0from .posix_session import PosixSession


@@ -64,7 +64,7 @@ def get_remote_cpus(self, use_first_core: bool) -> lis= t[LogicalCore]:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0lcores.append(LogicalCore(l= core, core, socket, node))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return lcores

-=C2=A0 =C2=A0 def get_dpdk_file_prefix(self, dpdk_prefix) -> str:
+=C2=A0 =C2=A0 def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return dpdk_prefix

=C2=A0 =C2=A0 =C2=A0def setup_hugepages(self, hugepage_amount: int, force_f= irst_numa: bool) -> None:
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_mo= del/node.py
index fc01e0bf8e..fa5b143cdd 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -12,23 +12,26 @@
=C2=A0from typing import Any, Callable, Type, Union

=C2=A0from framework.config import (
+=C2=A0 =C2=A0 OS,
=C2=A0 =C2=A0 =C2=A0BuildTargetConfiguration,
=C2=A0 =C2=A0 =C2=A0ExecutionConfiguration,
=C2=A0 =C2=A0 =C2=A0NodeConfiguration,
=C2=A0)
+from framework.exception import ConfigurationError
=C2=A0from framework.logger import DTSLOG, getLogger
-from framework.remote_session import InteractiveShellType, OSSession, crea= te_session
=C2=A0from framework.settings import SETTINGS

-from .hw import (
+from .cpu import (
=C2=A0 =C2=A0 =C2=A0LogicalCore,
=C2=A0 =C2=A0 =C2=A0LogicalCoreCount,
=C2=A0 =C2=A0 =C2=A0LogicalCoreList,
=C2=A0 =C2=A0 =C2=A0LogicalCoreListFilter,
-=C2=A0 =C2=A0 VirtualDevice,
=C2=A0 =C2=A0 =C2=A0lcore_filter,
=C2=A0)
-from .hw.port import Port
+from .linux_session import LinuxSession
+from .os_session import InteractiveShellType, OSSession
+from .port import Port
+from .virtual_device import VirtualDevice


=C2=A0class Node(ABC):
@@ -172,9 +175,9 @@ def create_interactive_shell(

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return self.main_session.create_interacti= ve_shell(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0shell_cls,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 app_args,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0timeout,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0privileged,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 app_args,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)

=C2=A0 =C2=A0 =C2=A0def filter_lcores(
@@ -205,7 +208,7 @@ def _get_remote_cpus(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._logger.info("Getting CPU informa= tion.")
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.lcores =3D self.main_session.get_rem= ote_cpus(self.config.use_first_core)

-=C2=A0 =C2=A0 def _setup_hugepages(self):
+=C2=A0 =C2=A0 def _setup_hugepages(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Setup hugepages on the Node. Different ar= chitectures can supply different
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0amounts of memory for hugepages and numa-= based hugepage allocation may need
@@ -249,3 +252,13 @@ def skip_setup(func: Callable[..., Any]) -> Callabl= e[..., Any]:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return lambda *args: None =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0else:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return func
+
+
+def create_session(
+=C2=A0 =C2=A0 node_config: NodeConfiguration, name: str, logger: DTSLOG +) -> OSSession:
+=C2=A0 =C2=A0 match node_config.os:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 case OS.linux:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return LinuxSession(node_config,= name, logger)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 case _:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise ConfigurationError(f"= Unsupported OS {node_config.os}")
diff --git a/dts/framework/remote_session/os_session.py b/dts/framework/tes= tbed_model/os_session.py
similarity index 95%
rename from dts/framework/remote_session/os_session.py
rename to dts/framework/testbed_model/os_session.py
index 8a709eac1c..76e595a518 100644
--- a/dts/framework/remote_session/os_session.py
+++ b/dts/framework/testbed_model/os_session.py
@@ -10,19 +10,19 @@

=C2=A0from framework.config import Architecture, NodeConfiguration, NodeInf= o
=C2=A0from framework.logger import DTSLOG
-from framework.remote_session.remote import InteractiveShell
-from framework.settings import SETTINGS
-from framework.testbed_model import LogicalCore
-from framework.testbed_model.hw.port import Port
-from framework.utils import MesonArgs
-
-from .remote import (
+from framework.remote_session import (
=C2=A0 =C2=A0 =C2=A0CommandResult,
=C2=A0 =C2=A0 =C2=A0InteractiveRemoteSession,
+=C2=A0 =C2=A0 InteractiveShell,
=C2=A0 =C2=A0 =C2=A0RemoteSession,
=C2=A0 =C2=A0 =C2=A0create_interactive_session,
=C2=A0 =C2=A0 =C2=A0create_remote_session,
=C2=A0)
+from framework.settings import SETTINGS
+from framework.utils import MesonArgs
+
+from .cpu import LogicalCore
+from .port import Port

=C2=A0InteractiveShellType =3D TypeVar("InteractiveShellType", bo= und=3DInteractiveShell)

@@ -85,9 +85,9 @@ def send_command(
=C2=A0 =C2=A0 =C2=A0def create_interactive_shell(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0shell_cls: Type[InteractiveShellType], -=C2=A0 =C2=A0 =C2=A0 =C2=A0 eal_parameters: str,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0timeout: float,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0privileged: bool,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 app_args: str,
=C2=A0 =C2=A0 =C2=A0) -> InteractiveShellType:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0See "create_interactive_shell" = in SutNode
@@ -96,7 +96,7 @@ def create_interactive_shell(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.interactive_session.se= ssion,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._logger,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._get_privileged_comman= d if privileged else None,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 eal_parameters,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 app_args,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0timeout,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)

@@ -113,7 +113,7 @@ def _get_privileged_command(command: str) -> str: =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""

=C2=A0 =C2=A0 =C2=A0@abstractmethod
-=C2=A0 =C2=A0 def guess_dpdk_remote_dir(self, remote_dir) -> PurePath:<= br> +=C2=A0 =C2=A0 def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) = -> PurePath:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Try to find DPDK remote dir in remote_dir= .
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
@@ -227,7 +227,7 @@ def kill_cleanup_dpdk_apps(self, dpdk_prefix_list: Iter= able[str]) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""

=C2=A0 =C2=A0 =C2=A0@abstractmethod
-=C2=A0 =C2=A0 def get_dpdk_file_prefix(self, dpdk_prefix) -> str:
+=C2=A0 =C2=A0 def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Get the DPDK file prefix that will be use= d when running DPDK apps.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
diff --git a/dts/framework/testbed_model/hw/port.py b/dts/framework/testbed= _model/port.py
similarity index 100%
rename from dts/framework/testbed_model/hw/port.py
rename to dts/framework/testbed_model/port.py
diff --git a/dts/framework/remote_session/posix_session.py b/dts/framework/= testbed_model/posix_session.py
similarity index 98%
rename from dts/framework/remote_session/posix_session.py
rename to dts/framework/testbed_model/posix_session.py
index 5da0516e05..1d1d5b1b26 100644
--- a/dts/framework/remote_session/posix_session.py
+++ b/dts/framework/testbed_model/posix_session.py
@@ -32,7 +32,7 @@ def combine_short_options(**opts: bool) -> str:

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return ret_opts

-=C2=A0 =C2=A0 def guess_dpdk_remote_dir(self, remote_dir) -> PurePosixP= ath:
+=C2=A0 =C2=A0 def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) = -> PurePosixPath:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0remote_guess =3D self.join_remote_path(re= mote_dir, "dpdk-*")
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0result =3D self.send_command(f"ls -d= {remote_guess} | tail -1")
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return PurePosixPath(result.stdout)
@@ -219,7 +219,7 @@ def _remove_dpdk_runtime_dirs(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0for dpdk_runtime_dir in dpdk_runtime_dirs= :
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.remove_remote_dir(dpdk= _runtime_dir)

-=C2=A0 =C2=A0 def get_dpdk_file_prefix(self, dpdk_prefix) -> str:
+=C2=A0 =C2=A0 def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return ""

=C2=A0 =C2=A0 =C2=A0def get_compiler_version(self, compiler_name: str) ->= ; str:
diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbe= d_model/sut_node.py
index 4161d3a4d5..17deea06e2 100644
--- a/dts/framework/testbed_model/sut_node.py
+++ b/dts/framework/testbed_model/sut_node.py
@@ -15,12 +15,14 @@
=C2=A0 =C2=A0 =C2=A0NodeInfo,
=C2=A0 =C2=A0 =C2=A0SutNodeConfiguration,
=C2=A0)
-from framework.remote_session import CommandResult, InteractiveShellType, = OSSession
+from framework.remote_session import CommandResult
=C2=A0from framework.settings import SETTINGS
=C2=A0from framework.utils import MesonArgs

-from .hw import LogicalCoreCount, LogicalCoreList, VirtualDevice
+from .cpu import LogicalCoreCount, LogicalCoreList
=C2=A0from .node import Node
+from .os_session import InteractiveShellType, OSSession
+from .virtual_device import VirtualDevice


=C2=A0class EalParameters(object):
@@ -307,7 +309,7 @@ def create_eal_parameters(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0prefix: str =3D "dpdk",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0append_prefix_timestamp: bool =3D True, =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0no_pci: bool =3D False,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 vdevs: list[VirtualDevice] =3D None,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 vdevs: list[VirtualDevice] | None =3D None, =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0other_eal_param: str =3D "", =C2=A0 =C2=A0 =C2=A0) -> "EalParameters":
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
diff --git a/dts/framework/testbed_model/tg_node.py b/dts/framework/testbed= _model/tg_node.py
index 27025cfa31..166eb8430e 100644
--- a/dts/framework/testbed_model/tg_node.py
+++ b/dts/framework/testbed_model/tg_node.py
@@ -16,16 +16,11 @@

=C2=A0from scapy.packet import Packet=C2=A0 # type: ignore[import]

-from framework.config import (
-=C2=A0 =C2=A0 ScapyTrafficGeneratorConfig,
-=C2=A0 =C2=A0 TGNodeConfiguration,
-=C2=A0 =C2=A0 TrafficGeneratorType,
-)
-from framework.exception import ConfigurationError
-
-from .capturing_traffic_generator import CapturingTrafficGenerator
-from .hw.port import Port
+from framework.config import TGNodeConfiguration
+
=C2=A0from .node import Node
+from .port import Port
+from .traffic_generator import CapturingTrafficGenerator, create_traffic_g= enerator


=C2=A0class TGNode(Node):
@@ -80,20 +75,3 @@ def close(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""Free all resources used= by the node"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.traffic_generator.close()
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super(TGNode, self).close()
-
-
-def create_traffic_generator(
-=C2=A0 =C2=A0 tg_node: TGNode, traffic_generator_config: ScapyTrafficGener= atorConfig
-) -> CapturingTrafficGenerator:
-=C2=A0 =C2=A0 """A factory function for creating traffic ge= nerator object from user config."""
-
-=C2=A0 =C2=A0 from .scapy import ScapyTrafficGenerator
-
-=C2=A0 =C2=A0 match traffic_generator_config.traffic_generator_type:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 case TrafficGeneratorType.SCAPY:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return ScapyTrafficGenerator(tg_= node, traffic_generator_config)
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 case _:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise ConfigurationError(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "Unknown traf= fic generator: "
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{traffic_ge= nerator_config.traffic_generator_type}"
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
diff --git a/dts/framework/testbed_model/traffic_generator/__init__.py b/dt= s/framework/testbed_model/traffic_generator/__init__.py
new file mode 100644
index 0000000000..11bfa1ee0f
--- /dev/null
+++ b/dts/framework/testbed_model/traffic_generator/__init__.py
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+from framework.config import ScapyTrafficGeneratorConfig, TrafficGenerator= Type
+from framework.exception import ConfigurationError
+from framework.testbed_model.node import Node
+
+from .capturing_traffic_generator import CapturingTrafficGenerator
+from .scapy import ScapyTrafficGenerator
+
+
+def create_traffic_generator(
+=C2=A0 =C2=A0 tg_node: Node, traffic_generator_config: ScapyTrafficGenerat= orConfig
+) -> CapturingTrafficGenerator:
+=C2=A0 =C2=A0 """A factory function for creating traffic ge= nerator object from user config."""
+
+=C2=A0 =C2=A0 match traffic_generator_config.traffic_generator_type:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 case TrafficGeneratorType.SCAPY:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return ScapyTrafficGenerator(tg_= node, traffic_generator_config)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 case _:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise ConfigurationError(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "Unknown traf= fic generator: "
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{traffic_ge= nerator_config.traffic_generator_type}"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
diff --git a/dts/framework/testbed_model/capturing_traffic_generator.py b/d= ts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py=
similarity index 96%
rename from dts/framework/testbed_model/capturing_traffic_generator.py
rename to dts/framework/testbed_model/traffic_generator/capturing_traffic_g= enerator.py
index ab98987f8e..e521211ef0 100644
--- a/dts/framework/testbed_model/capturing_traffic_generator.py
+++ b/dts/framework/testbed_model/traffic_generator/capturing_traffic_gener= ator.py
@@ -16,9 +16,9 @@
=C2=A0from scapy.packet import Packet=C2=A0 # type: ignore[import]

=C2=A0from framework.settings import SETTINGS
+from framework.testbed_model.port import Port
=C2=A0from framework.utils import get_packet_summaries

-from .hw.port import Port
=C2=A0from .traffic_generator import TrafficGenerator


@@ -130,7 +130,9 @@ def _send_packets_and_capture(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0for the specified duration. It must be ab= le to handle no received packets.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""

-=C2=A0 =C2=A0 def _write_capture_from_packets(self, capture_name: str, pac= kets: list[Packet]):
+=C2=A0 =C2=A0 def _write_capture_from_packets(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self, capture_name: str, packets: list[Packet]=
+=C2=A0 =C2=A0 ) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0file_name =3D f"{SETTINGS.output_dir= }/{capture_name}.pcap"
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._logger.debug(f"Writing packets= to {file_name}.")
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0scapy.utils.wrpcap(file_name, packets) diff --git a/dts/framework/testbed_model/scapy.py b/dts/framework/testbed_m= odel/traffic_generator/scapy.py
similarity index 95%
rename from dts/framework/testbed_model/scapy.py
rename to dts/framework/testbed_model/traffic_generator/scapy.py
index af0d4dbb25..51864b6e6b 100644
--- a/dts/framework/testbed_model/scapy.py
+++ b/dts/framework/testbed_model/traffic_generator/scapy.py
@@ -24,16 +24,15 @@
=C2=A0from scapy.packet import Packet=C2=A0 # type: ignore[import]

=C2=A0from framework.config import OS, ScapyTrafficGeneratorConfig
-from framework.logger import DTSLOG, getLogger
=C2=A0from framework.remote_session import PythonShell
=C2=A0from framework.settings import SETTINGS
+from framework.testbed_model.node import Node
+from framework.testbed_model.port import Port

=C2=A0from .capturing_traffic_generator import (
=C2=A0 =C2=A0 =C2=A0CapturingTrafficGenerator,
=C2=A0 =C2=A0 =C2=A0_get_default_capture_name,
=C2=A0)
-from .hw.port import Port
-from .tg_node import TGNode

=C2=A0"""
=C2=A0=3D=3D=3D=3D=3D=3D=3D=3D=3D BEGIN RPC FUNCTIONS =3D=3D=3D=3D=3D=3D=3D= =3D=3D
@@ -146,7 +145,7 @@ def quit(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._BaseServer__shutdown_request =3D Tr= ue
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return None

-=C2=A0 =C2=A0 def add_rpc_function(self, name: str, function_bytes: xmlrpc= .client.Binary):
+=C2=A0 =C2=A0 def add_rpc_function(self, name: str, function_bytes: xmlrpc= .client.Binary) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""Add a function to the s= erver.

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0This is meant to be executed remotely. @@ -191,15 +190,9 @@ class ScapyTrafficGenerator(CapturingTrafficGenerator)= :
=C2=A0 =C2=A0 =C2=A0session: PythonShell
=C2=A0 =C2=A0 =C2=A0rpc_server_proxy: xmlrpc.client.ServerProxy
=C2=A0 =C2=A0 =C2=A0_config: ScapyTrafficGeneratorConfig
-=C2=A0 =C2=A0 _tg_node: TGNode
-=C2=A0 =C2=A0 _logger: DTSLOG
-
-=C2=A0 =C2=A0 def __init__(self, tg_node: TGNode, config: ScapyTrafficGene= ratorConfig):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._config =3D config
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._tg_node =3D tg_node
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._logger =3D getLogger(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{self._tg_node.name} {self.= _config.traffic_generator_type}"
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+
+=C2=A0 =C2=A0 def __init__(self, tg_node: Node, config: ScapyTrafficGenera= torConfig):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 super().__init__(tg_node, config)

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0assert (
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._tg_node.config.os =3D= =3D OS.linux
@@ -235,7 +228,7 @@ def __init__(self, tg_node: TGNode, config: ScapyTraffi= cGeneratorConfig):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0function_bytes =3D marshal.= dumps(function.__code__)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.rpc_server_proxy.add_r= pc_function(function.__name__, function_bytes)

-=C2=A0 =C2=A0 def _start_xmlrpc_server_in_remote_python(self, listen_port:= int):
+=C2=A0 =C2=A0 def _start_xmlrpc_server_in_remote_python(self, listen_port:= int) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# load the source of the function
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0src =3D inspect.getsource(QuittableXMLRPC= Server)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# Lines with only whitespace break the re= pl if in the middle of a function
@@ -280,7 +273,7 @@ def _send_packets_and_capture(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0scapy_packets =3D [Ether(packet.data) for= packet in xmlrpc_packets]
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return scapy_packets

-=C2=A0 =C2=A0 def close(self):
+=C2=A0 =C2=A0 def close(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0try:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.rpc_server_proxy.quit(= )
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0except ConnectionRefusedError:
diff --git a/dts/framework/testbed_model/traffic_generator.py b/dts/framewo= rk/testbed_model/traffic_generator/traffic_generator.py
similarity index 80%
rename from dts/framework/testbed_model/traffic_generator.py
rename to dts/framework/testbed_model/traffic_generator/traffic_generator.p= y
index 28c35d3ce4..ea7c3963da 100644
--- a/dts/framework/testbed_model/traffic_generator.py
+++ b/dts/framework/testbed_model/traffic_generator/traffic_generator.py @@ -12,11 +12,12 @@

=C2=A0from scapy.packet import Packet=C2=A0 # type: ignore[import]

-from framework.logger import DTSLOG
+from framework.config import TrafficGeneratorConfig
+from framework.logger import DTSLOG, getLogger
+from framework.testbed_model.node import Node
+from framework.testbed_model.port import Port
=C2=A0from framework.utils import get_packet_summaries

-from .hw.port import Port
-

=C2=A0class TrafficGenerator(ABC):
=C2=A0 =C2=A0 =C2=A0"""The base traffic generator.
@@ -24,8 +25,17 @@ class TrafficGenerator(ABC):
=C2=A0 =C2=A0 =C2=A0Defines the few basic methods that each traffic generat= or must implement.
=C2=A0 =C2=A0 =C2=A0"""

+=C2=A0 =C2=A0 _config: TrafficGeneratorConfig
+=C2=A0 =C2=A0 _tg_node: Node

Is there a benef= it to changing this to be a node instead of a TGNode? Wouldn't we want = the capabilities of the TGNode to be accessible in the TrafficGenerator cla= ss?
=C2=A0
=C2=A0 =C2=A0 =C2=A0_logger: DTSLOG

+=C2=A0 =C2=A0 def __init__(self, tg_node: Node, config: TrafficGeneratorCo= nfig):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._config =3D config
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._tg_node =3D tg_node
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._logger =3D getLogger(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{self._tg_node.name} {self.= _config.traffic_generator_type}"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+
=C2=A0 =C2=A0 =C2=A0def send_packet(self, packet: Packet, port: Port) ->= None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""Send a packet and block= until it is fully sent.

diff --git a/dts/framework/testbed_model/hw/virtual_device.py b/dts/framewo= rk/testbed_model/virtual_device.py
similarity index 100%
rename from dts/framework/testbed_model/hw/virtual_device.py
rename to dts/framework/testbed_model/virtual_device.py
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index d27c2c5b5f..f0c916471c 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -7,7 +7,6 @@
=C2=A0import json
=C2=A0import os
=C2=A0import subprocess
-import sys
=C2=A0from enum import Enum
=C2=A0from pathlib import Path
=C2=A0from subprocess import SubprocessError
@@ -16,35 +15,7 @@

=C2=A0from .exception import ConfigurationError

-
-class StrEnum(Enum):
-=C2=A0 =C2=A0 @staticmethod
-=C2=A0 =C2=A0 def _generate_next_value_(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 name: str, start: int, count: int, last_values= : object
-=C2=A0 =C2=A0 ) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return name
-
-=C2=A0 =C2=A0 def __str__(self) -> str:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self.name
-
-
-REGEX_FOR_PCI_ADDRESS =3D "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]= {2}.[0-9]{1}/"
-
-
-def check_dts_python_version() -> None:
-=C2=A0 =C2=A0 if sys.version_info.major < 3 or (
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 sys.version_info.major =3D=3D 3 and sys.versio= n_info.minor < 10
-=C2=A0 =C2=A0 ):
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 print(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 RED(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quo= t;WARNING: DTS execution node's python version is lower than"
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quo= t;python 3.10, is deprecated and will not work in future releases." -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 file=3Dsys.stderr,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 )
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 print(RED("Please use Python >=3D 3.10= instead"), file=3Dsys.stderr)
+REGEX_FOR_PCI_ADDRESS: str =3D "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-= fA-F]{2}.[0-9]{1}/"


=C2=A0def expand_range(range_str: str) -> list[int]:
@@ -67,7 +38,7 @@ def expand_range(range_str: str) -> list[int]:
=C2=A0 =C2=A0 =C2=A0return expanded_range


-def get_packet_summaries(packets: list[Packet]):
+def get_packet_summaries(packets: list[Packet]) -> str:
=C2=A0 =C2=A0 =C2=A0if len(packets) =3D=3D 1:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0packet_summaries =3D packets[0].summary()=
=C2=A0 =C2=A0 =C2=A0else:
@@ -77,8 +48,15 @@ def get_packet_summaries(packets: list[Packet]):
=C2=A0 =C2=A0 =C2=A0return f"Packet contents: \n{packet_summaries}&quo= t;


-def RED(text: str) -> str:
-=C2=A0 =C2=A0 return f"\u001B[31;1m{str(text)}\u001B[0m"
+class StrEnum(Enum):
+=C2=A0 =C2=A0 @staticmethod
+=C2=A0 =C2=A0 def _generate_next_value_(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 name: str, start: int, count: int, last_values= : object
+=C2=A0 =C2=A0 ) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return name
+
+=C2=A0 =C2=A0 def __str__(self) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self.name


=C2=A0class MesonArgs(object):
@@ -225,5 +203,5 @@ def _delete_tarball(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if self._tarball_path and os.path.exists(= self._tarball_path):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0os.remove(self._tarball_pat= h)

-=C2=A0 =C2=A0 def __fspath__(self):
+=C2=A0 =C2=A0 def __fspath__(self) -> str:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return str(self._tarball_path)
diff --git a/dts/main.py b/dts/main.py
index 43311fa847..5d4714b0c3 100755
--- a/dts/main.py
+++ b/dts/main.py
@@ -10,10 +10,17 @@

=C2=A0import logging

-from framework import dts
+from framework import settings


=C2=A0def main() -> None:
+=C2=A0 =C2=A0 """Set DTS settings, then run DTS.
+
+=C2=A0 =C2=A0 The DTS settings are taken from the command line arguments a= nd the environment variables.
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 settings.SETTINGS =3D settings.get_settings()
+=C2=A0 =C2=A0 from framework import dts
+
=C2=A0 =C2=A0 =C2=A0dts.run_all()


--
2.34.1

--000000000000bd18b0060a4b5f19--