From: "Juraj Linkeš" <juraj.linkes@pantheon.tech>
To: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com,
lijuan.tu@intel.com, bruce.richardson@intel.com,
probb@iol.unh.edu
Cc: dev@dpdk.org, "Juraj Linkeš" <juraj.linkes@pantheon.tech>
Subject: [PATCH v6 01/10] dts: add node and os abstractions
Date: Fri, 3 Mar 2023 11:24:58 +0100 [thread overview]
Message-ID: <20230303102507.527790-2-juraj.linkes@pantheon.tech> (raw)
In-Reply-To: <20230303102507.527790-1-juraj.linkes@pantheon.tech>
The abstraction model in DTS is as follows:
Node, defining and implementing methods common to and the base of SUT
(system under test) Node and TG (traffic generator) Node.
Remote Session, defining and implementing methods common to any remote
session implementation, such as SSH Session.
OSSession, defining and implementing methods common to any operating
system/distribution, such as Linux.
OSSession uses a derived Remote Session and Node in turn uses a derived
OSSession. This split delegates OS-specific and connection-specific code
to specialized classes designed to handle the differences.
The base classes implement the methods or parts of methods that are
common to all implementations and defines abstract methods that must be
implemented by derived classes.
Part of the abstractions is the DTS test execution skeleton:
execution setup, build setup and then test execution.
Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
---
dts/conf.yaml | 11 +-
dts/framework/config/__init__.py | 73 +++++++-
dts/framework/config/conf_yaml_schema.json | 76 +++++++-
dts/framework/dts.py | 162 ++++++++++++++----
dts/framework/exception.py | 46 ++++-
dts/framework/logger.py | 24 +--
dts/framework/remote_session/__init__.py | 30 +++-
dts/framework/remote_session/linux_session.py | 11 ++
dts/framework/remote_session/os_session.py | 46 +++++
dts/framework/remote_session/posix_session.py | 12 ++
.../remote_session/remote/__init__.py | 16 ++
.../{ => remote}/remote_session.py | 41 +++--
.../{ => remote}/ssh_session.py | 20 +--
dts/framework/settings.py | 15 +-
dts/framework/testbed_model/__init__.py | 10 +-
dts/framework/testbed_model/node.py | 109 +++++++++---
dts/framework/testbed_model/sut_node.py | 13 ++
17 files changed, 594 insertions(+), 121 deletions(-)
create mode 100644 dts/framework/remote_session/linux_session.py
create mode 100644 dts/framework/remote_session/os_session.py
create mode 100644 dts/framework/remote_session/posix_session.py
create mode 100644 dts/framework/remote_session/remote/__init__.py
rename dts/framework/remote_session/{ => remote}/remote_session.py (61%)
rename dts/framework/remote_session/{ => remote}/ssh_session.py (91%)
create mode 100644 dts/framework/testbed_model/sut_node.py
diff --git a/dts/conf.yaml b/dts/conf.yaml
index 1aaa593612..03696d2bab 100644
--- a/dts/conf.yaml
+++ b/dts/conf.yaml
@@ -1,9 +1,16 @@
# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2022 The DPDK contributors
+# Copyright 2022-2023 The DPDK contributors
executions:
- - system_under_test: "SUT 1"
+ - build_targets:
+ - arch: x86_64
+ os: linux
+ cpu: native
+ compiler: gcc
+ compiler_wrapper: ccache
+ system_under_test: "SUT 1"
nodes:
- name: "SUT 1"
hostname: sut1.change.me.localhost
user: root
+ os: linux
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index 214be8e7f4..e3e2d74eac 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -1,15 +1,17 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2021 Intel Corporation
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 University of New Hampshire
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
"""
-Generic port and topology nodes configuration file load function
+Yaml config parsing methods
"""
import json
import os.path
import pathlib
from dataclasses import dataclass
+from enum import Enum, auto, unique
from typing import Any
import warlock # type: ignore
@@ -18,6 +20,47 @@
from framework.settings import SETTINGS
+class StrEnum(Enum):
+ @staticmethod
+ def _generate_next_value_(
+ name: str, start: int, count: int, last_values: object
+ ) -> str:
+ return name
+
+
+@unique
+class Architecture(StrEnum):
+ i686 = auto()
+ x86_64 = auto()
+ x86_32 = auto()
+ arm64 = auto()
+ ppc64le = auto()
+
+
+@unique
+class OS(StrEnum):
+ linux = auto()
+ freebsd = auto()
+ windows = auto()
+
+
+@unique
+class CPUType(StrEnum):
+ native = auto()
+ armv8a = auto()
+ dpaa2 = auto()
+ thunderx = auto()
+ xgene1 = auto()
+
+
+@unique
+class Compiler(StrEnum):
+ gcc = auto()
+ clang = auto()
+ icc = auto()
+ msvc = auto()
+
+
# Slots enables some optimizations, by pre-allocating space for the defined
# attributes in the underlying data structure.
#
@@ -29,6 +72,7 @@ class NodeConfiguration:
hostname: str
user: str
password: str | None
+ os: OS
@staticmethod
def from_dict(d: dict) -> "NodeConfiguration":
@@ -37,19 +81,44 @@ def from_dict(d: dict) -> "NodeConfiguration":
hostname=d["hostname"],
user=d["user"],
password=d.get("password"),
+ os=OS(d["os"]),
+ )
+
+
+@dataclass(slots=True, frozen=True)
+class BuildTargetConfiguration:
+ arch: Architecture
+ os: OS
+ cpu: CPUType
+ compiler: Compiler
+ name: str
+
+ @staticmethod
+ def from_dict(d: dict) -> "BuildTargetConfiguration":
+ return BuildTargetConfiguration(
+ arch=Architecture(d["arch"]),
+ os=OS(d["os"]),
+ cpu=CPUType(d["cpu"]),
+ compiler=Compiler(d["compiler"]),
+ name=f"{d['arch']}-{d['os']}-{d['cpu']}-{d['compiler']}",
)
@dataclass(slots=True, frozen=True)
class ExecutionConfiguration:
+ build_targets: list[BuildTargetConfiguration]
system_under_test: NodeConfiguration
@staticmethod
def from_dict(d: dict, node_map: dict) -> "ExecutionConfiguration":
+ build_targets: list[BuildTargetConfiguration] = list(
+ map(BuildTargetConfiguration.from_dict, d["build_targets"])
+ )
sut_name = d["system_under_test"]
assert sut_name in node_map, f"Unknown SUT {sut_name} in execution {d}"
return ExecutionConfiguration(
+ build_targets=build_targets,
system_under_test=node_map[sut_name],
)
diff --git a/dts/framework/config/conf_yaml_schema.json b/dts/framework/config/conf_yaml_schema.json
index 6b8d6ccd05..9170307fbe 100644
--- a/dts/framework/config/conf_yaml_schema.json
+++ b/dts/framework/config/conf_yaml_schema.json
@@ -5,6 +5,68 @@
"node_name": {
"type": "string",
"description": "A unique identifier for a node"
+ },
+ "OS": {
+ "type": "string",
+ "enum": [
+ "linux"
+ ]
+ },
+ "cpu": {
+ "type": "string",
+ "description": "Native should be the default on x86",
+ "enum": [
+ "native",
+ "armv8a",
+ "dpaa2",
+ "thunderx",
+ "xgene1"
+ ]
+ },
+ "compiler": {
+ "type": "string",
+ "enum": [
+ "gcc",
+ "clang",
+ "icc",
+ "mscv"
+ ]
+ },
+ "build_target": {
+ "type": "object",
+ "description": "Targets supported by DTS",
+ "properties": {
+ "arch": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "x86_64",
+ "arm64",
+ "ppc64le",
+ "other"
+ ]
+ },
+ "os": {
+ "$ref": "#/definitions/OS"
+ },
+ "cpu": {
+ "$ref": "#/definitions/cpu"
+ },
+ "compiler": {
+ "$ref": "#/definitions/compiler"
+ },
+ "compiler_wrapper": {
+ "type": "string",
+ "description": "This will be added before compiler to the CC variable when building DPDK. Optional."
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "arch",
+ "os",
+ "cpu",
+ "compiler"
+ ]
}
},
"type": "object",
@@ -29,13 +91,17 @@
"password": {
"type": "string",
"description": "The password to use on this node. Use only as a last resort. SSH keys are STRONGLY preferred."
+ },
+ "os": {
+ "$ref": "#/definitions/OS"
}
},
"additionalProperties": false,
"required": [
"name",
"hostname",
- "user"
+ "user",
+ "os"
]
},
"minimum": 1
@@ -45,12 +111,20 @@
"items": {
"type": "object",
"properties": {
+ "build_targets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/build_target"
+ },
+ "minimum": 1
+ },
"system_under_test": {
"$ref": "#/definitions/node_name"
}
},
"additionalProperties": false,
"required": [
+ "build_targets",
"system_under_test"
]
},
diff --git a/dts/framework/dts.py b/dts/framework/dts.py
index d23cfc4526..3d4170d10f 100644
--- a/dts/framework/dts.py
+++ b/dts/framework/dts.py
@@ -1,67 +1,157 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2019 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
import sys
-import traceback
-from collections.abc import Iterable
-from framework.testbed_model.node import Node
-
-from .config import CONFIGURATION
+from .config import CONFIGURATION, BuildTargetConfiguration, ExecutionConfiguration
+from .exception import DTSError, ErrorSeverity
from .logger import DTSLOG, getLogger
+from .testbed_model import SutNode
from .utils import check_dts_python_version
-dts_logger: DTSLOG | None = None
+dts_logger: DTSLOG = getLogger("DTSRunner")
+errors = []
def run_all() -> None:
"""
- Main process of DTS, it will run all test suites in the config file.
+ The main process of DTS. Runs all build targets in all executions from the main
+ config file.
"""
-
global dts_logger
+ global errors
# check the python version of the server that run dts
check_dts_python_version()
- dts_logger = getLogger("dts")
-
- nodes = {}
- # This try/finally block means "Run the try block, if there is an exception,
- # run the finally block before passing it upward. If there is not an exception,
- # run the finally block after the try block is finished." This helps avoid the
- # problem of python's interpreter exit context, which essentially prevents you
- # from making certain system calls. This makes cleaning up resources difficult,
- # since most of the resources in DTS are network-based, which is restricted.
+ nodes: dict[str, SutNode] = {}
try:
# for all Execution sections
for execution in CONFIGURATION.executions:
- sut_config = execution.system_under_test
- if sut_config.name not in nodes:
- node = Node(sut_config)
- nodes[sut_config.name] = node
- node.send_command("echo Hello World")
+ sut_node = None
+ if execution.system_under_test.name in nodes:
+ # a Node with the same name already exists
+ sut_node = nodes[execution.system_under_test.name]
+ else:
+ # the SUT has not been initialized yet
+ try:
+ sut_node = SutNode(execution.system_under_test)
+ except Exception as e:
+ dts_logger.exception(
+ f"Connection to node {execution.system_under_test} failed."
+ )
+ errors.append(e)
+ else:
+ nodes[sut_node.name] = sut_node
+
+ if sut_node:
+ _run_execution(sut_node, execution)
+
+ except Exception as e:
+ dts_logger.exception("An unexpected error has occurred.")
+ errors.append(e)
+ raise
+
+ finally:
+ try:
+ for node in nodes.values():
+ node.close()
+ except Exception as e:
+ dts_logger.exception("Final cleanup of nodes failed.")
+ errors.append(e)
+ # we need to put the sys.exit call outside the finally clause to make sure
+ # that unexpected exceptions will propagate
+ # in that case, the error that should be reported is the uncaught exception as
+ # that is a severe error originating from the framework
+ # at that point, we'll only have partial results which could be impacted by the
+ # error causing the uncaught exception, making them uninterpretable
+ _exit_dts()
+
+
+def _run_execution(sut_node: SutNode, execution: ExecutionConfiguration) -> None:
+ """
+ Run the given execution. This involves running the execution setup as well as
+ running all build targets in the given execution.
+ """
+ dts_logger.info(f"Running execution with SUT '{execution.system_under_test.name}'.")
+
+ try:
+ sut_node.set_up_execution(execution)
except Exception as e:
- # sys.exit() doesn't produce a stack trace, need to print it explicitly
- traceback.print_exc()
- raise e
+ dts_logger.exception("Execution setup failed.")
+ errors.append(e)
+
+ else:
+ for build_target in execution.build_targets:
+ _run_build_target(sut_node, build_target, execution)
finally:
- quit_execution(nodes.values())
+ try:
+ sut_node.tear_down_execution()
+ except Exception as e:
+ dts_logger.exception("Execution teardown failed.")
+ errors.append(e)
-def quit_execution(sut_nodes: Iterable[Node]) -> None:
+def _run_build_target(
+ sut_node: SutNode,
+ build_target: BuildTargetConfiguration,
+ execution: ExecutionConfiguration,
+) -> None:
"""
- Close session to SUT and TG before quit.
- Return exit status when failure occurred.
+ Run the given build target.
"""
- for sut_node in sut_nodes:
- # close all session
- sut_node.node_exit()
+ dts_logger.info(f"Running build target '{build_target.name}'.")
+
+ try:
+ sut_node.set_up_build_target(build_target)
+ except Exception as e:
+ dts_logger.exception("Build target setup failed.")
+ errors.append(e)
+
+ else:
+ _run_suites(sut_node, execution)
+
+ finally:
+ try:
+ sut_node.tear_down_build_target()
+ except Exception as e:
+ dts_logger.exception("Build target teardown failed.")
+ errors.append(e)
+
+
+def _run_suites(
+ sut_node: SutNode,
+ execution: ExecutionConfiguration,
+) -> None:
+ """
+ Use the given build_target to run execution's test suites
+ with possibly only a subset of test cases.
+ If no subset is specified, run all test cases.
+ """
+
+
+def _exit_dts() -> None:
+ """
+ Process all errors and exit with the proper exit code.
+ """
+ if errors and dts_logger:
+ dts_logger.debug("Summary of errors:")
+ for error in errors:
+ dts_logger.debug(repr(error))
+
+ return_code = ErrorSeverity.NO_ERR
+ for error in errors:
+ error_return_code = ErrorSeverity.GENERIC_ERR
+ if isinstance(error, DTSError):
+ error_return_code = error.severity
+
+ if error_return_code > return_code:
+ return_code = error_return_code
- if dts_logger is not None:
+ if dts_logger:
dts_logger.info("DTS execution has ended.")
- sys.exit(0)
+ sys.exit(return_code)
diff --git a/dts/framework/exception.py b/dts/framework/exception.py
index 8b2f08a8f0..121a0f7296 100644
--- a/dts/framework/exception.py
+++ b/dts/framework/exception.py
@@ -1,20 +1,46 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
"""
User-defined exceptions used across the framework.
"""
+from enum import IntEnum, unique
+from typing import ClassVar
-class SSHTimeoutError(Exception):
+
+@unique
+class ErrorSeverity(IntEnum):
+ """
+ The severity of errors that occur during DTS execution.
+ All exceptions are caught and the most severe error is used as return code.
+ """
+
+ NO_ERR = 0
+ GENERIC_ERR = 1
+ CONFIG_ERR = 2
+ SSH_ERR = 3
+
+
+class DTSError(Exception):
+ """
+ The base exception from which all DTS exceptions are derived.
+ Stores error severity.
+ """
+
+ severity: ClassVar[ErrorSeverity] = ErrorSeverity.GENERIC_ERR
+
+
+class SSHTimeoutError(DTSError):
"""
Command execution timeout.
"""
command: str
output: str
+ severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR
def __init__(self, command: str, output: str):
self.command = command
@@ -27,12 +53,13 @@ def get_output(self) -> str:
return self.output
-class SSHConnectionError(Exception):
+class SSHConnectionError(DTSError):
"""
SSH connection error.
"""
host: str
+ severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR
def __init__(self, host: str):
self.host = host
@@ -41,16 +68,25 @@ def __str__(self) -> str:
return f"Error trying to connect with {self.host}"
-class SSHSessionDeadError(Exception):
+class SSHSessionDeadError(DTSError):
"""
SSH session is not alive.
It can no longer be used.
"""
host: str
+ severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR
def __init__(self, host: str):
self.host = host
def __str__(self) -> str:
return f"SSH session with {self.host} has died"
+
+
+class ConfigurationError(DTSError):
+ """
+ Raised when an invalid configuration is encountered.
+ """
+
+ severity: ClassVar[ErrorSeverity] = ErrorSeverity.CONFIG_ERR
diff --git a/dts/framework/logger.py b/dts/framework/logger.py
index a31fcc8242..bb2991e994 100644
--- a/dts/framework/logger.py
+++ b/dts/framework/logger.py
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
"""
DTS logger module with several log level. DTS framework and TestSuite logs
@@ -33,17 +33,17 @@ class DTSLOG(logging.LoggerAdapter):
DTS log class for framework and testsuite.
"""
- logger: logging.Logger
+ _logger: logging.Logger
node: str
sh: logging.StreamHandler
fh: logging.FileHandler
verbose_fh: logging.FileHandler
def __init__(self, logger: logging.Logger, node: str = "suite"):
- self.logger = logger
+ self._logger = logger
# 1 means log everything, this will be used by file handlers if their level
# is not set
- self.logger.setLevel(1)
+ self._logger.setLevel(1)
self.node = node
@@ -55,9 +55,13 @@ def __init__(self, logger: logging.Logger, node: str = "suite"):
if SETTINGS.verbose is True:
sh.setLevel(logging.DEBUG)
- self.logger.addHandler(sh)
+ self._logger.addHandler(sh)
self.sh = sh
+ # prepare the output folder
+ if not os.path.exists(SETTINGS.output_dir):
+ os.mkdir(SETTINGS.output_dir)
+
logging_path_prefix = os.path.join(SETTINGS.output_dir, node)
fh = logging.FileHandler(f"{logging_path_prefix}.log")
@@ -68,7 +72,7 @@ def __init__(self, logger: logging.Logger, node: str = "suite"):
)
)
- self.logger.addHandler(fh)
+ self._logger.addHandler(fh)
self.fh = fh
# This outputs EVERYTHING, intended for post-mortem debugging
@@ -82,10 +86,10 @@ def __init__(self, logger: logging.Logger, node: str = "suite"):
)
)
- self.logger.addHandler(verbose_fh)
+ self._logger.addHandler(verbose_fh)
self.verbose_fh = verbose_fh
- super(DTSLOG, self).__init__(self.logger, dict(node=self.node))
+ super(DTSLOG, self).__init__(self._logger, dict(node=self.node))
def logger_exit(self) -> None:
"""
@@ -93,7 +97,7 @@ def logger_exit(self) -> None:
"""
for handler in (self.sh, self.fh, self.verbose_fh):
handler.flush()
- self.logger.removeHandler(handler)
+ self._logger.removeHandler(handler)
def getLogger(name: str, node: str = "suite") -> DTSLOG:
diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remote_session/__init__.py
index a227d8db22..747316c78a 100644
--- a/dts/framework/remote_session/__init__.py
+++ b/dts/framework/remote_session/__init__.py
@@ -1,14 +1,30 @@
# SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
-from framework.config import NodeConfiguration
+"""
+The package provides modules for managing remote connections to a remote host (node),
+differentiated by OS.
+The package provides a factory function, create_session, that returns the appropriate
+remote connection based on the passed configuration. The differences are in the
+underlying transport protocol (e.g. SSH) and remote OS (e.g. Linux).
+"""
+
+# pylama:ignore=W0611
+
+from framework.config import OS, NodeConfiguration
+from framework.exception import ConfigurationError
from framework.logger import DTSLOG
-from .remote_session import RemoteSession
-from .ssh_session import SSHSession
+from .linux_session import LinuxSession
+from .os_session import OSSession
+from .remote import RemoteSession, SSHSession
-def create_remote_session(
+def create_session(
node_config: NodeConfiguration, name: str, logger: DTSLOG
-) -> RemoteSession:
- return SSHSession(node_config, name, logger)
+) -> 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/linux_session.py b/dts/framework/remote_session/linux_session.py
new file mode 100644
index 0000000000..9d14166077
--- /dev/null
+++ b/dts/framework/remote_session/linux_session.py
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 University of New Hampshire
+
+from .posix_session import PosixSession
+
+
+class LinuxSession(PosixSession):
+ """
+ The implementation of non-Posix compliant parts of Linux remote sessions.
+ """
diff --git a/dts/framework/remote_session/os_session.py b/dts/framework/remote_session/os_session.py
new file mode 100644
index 0000000000..7a4cc5e669
--- /dev/null
+++ b/dts/framework/remote_session/os_session.py
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 University of New Hampshire
+
+from abc import ABC
+
+from framework.config import NodeConfiguration
+from framework.logger import DTSLOG
+
+from .remote import RemoteSession, create_remote_session
+
+
+class OSSession(ABC):
+ """
+ The OS classes create a DTS node remote session and implement OS specific
+ behavior. There a few control methods implemented by the base class, the rest need
+ to be implemented by derived classes.
+ """
+
+ _config: NodeConfiguration
+ name: str
+ _logger: DTSLOG
+ remote_session: RemoteSession
+
+ def __init__(
+ self,
+ node_config: NodeConfiguration,
+ name: str,
+ logger: DTSLOG,
+ ):
+ self._config = node_config
+ self.name = name
+ self._logger = logger
+ self.remote_session = create_remote_session(node_config, name, logger)
+
+ def close(self, force: bool = False) -> None:
+ """
+ Close the remote session.
+ """
+ self.remote_session.close(force)
+
+ def is_alive(self) -> bool:
+ """
+ Check whether the remote session is still responding.
+ """
+ return self.remote_session.is_alive()
diff --git a/dts/framework/remote_session/posix_session.py b/dts/framework/remote_session/posix_session.py
new file mode 100644
index 0000000000..110b6a4804
--- /dev/null
+++ b/dts/framework/remote_session/posix_session.py
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 University of New Hampshire
+
+from .os_session import OSSession
+
+
+class PosixSession(OSSession):
+ """
+ An intermediary class implementing the Posix compliant parts of
+ Linux and other OS remote sessions.
+ """
diff --git a/dts/framework/remote_session/remote/__init__.py b/dts/framework/remote_session/remote/__init__.py
new file mode 100644
index 0000000000..f3092f8bbe
--- /dev/null
+++ b/dts/framework/remote_session/remote/__init__.py
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+# pylama:ignore=W0611
+
+from framework.config import NodeConfiguration
+from framework.logger import DTSLOG
+
+from .remote_session import RemoteSession
+from .ssh_session import SSHSession
+
+
+def create_remote_session(
+ node_config: NodeConfiguration, name: str, logger: DTSLOG
+) -> RemoteSession:
+ return SSHSession(node_config, name, logger)
diff --git a/dts/framework/remote_session/remote_session.py b/dts/framework/remote_session/remote/remote_session.py
similarity index 61%
rename from dts/framework/remote_session/remote_session.py
rename to dts/framework/remote_session/remote/remote_session.py
index 33047d9d0a..7c7b30225f 100644
--- a/dts/framework/remote_session/remote_session.py
+++ b/dts/framework/remote_session/remote/remote_session.py
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
import dataclasses
from abc import ABC, abstractmethod
@@ -19,14 +19,23 @@ class HistoryRecord:
class RemoteSession(ABC):
+ """
+ The base class for defining which methods must be implemented in order to connect
+ to a remote host (node) and maintain a remote session. The derived classes are
+ supposed to implement/use some underlying transport protocol (e.g. SSH) to
+ implement the methods. On top of that, it provides some basic services common to
+ all derived classes, such as keeping history and logging what's being executed
+ on the remote node.
+ """
+
name: str
hostname: str
ip: str
port: int | None
username: str
password: str
- logger: DTSLOG
history: list[HistoryRecord]
+ _logger: DTSLOG
_node_config: NodeConfiguration
def __init__(
@@ -46,31 +55,34 @@ def __init__(
self.port = int(port)
self.username = node_config.user
self.password = node_config.password or ""
- self.logger = logger
self.history = []
- self.logger.info(f"Connecting to {self.username}@{self.hostname}.")
+ self._logger = logger
+ self._logger.info(f"Connecting to {self.username}@{self.hostname}.")
self._connect()
- self.logger.info(f"Connection to {self.username}@{self.hostname} successful.")
+ self._logger.info(f"Connection to {self.username}@{self.hostname} successful.")
@abstractmethod
def _connect(self) -> None:
"""
Create connection to assigned node.
"""
- pass
def send_command(self, command: str, timeout: float = SETTINGS.timeout) -> str:
- self.logger.info(f"Sending: {command}")
+ """
+ Send a command and return the output.
+ """
+ self._logger.info(f"Sending: {command}")
out = self._send_command(command, timeout)
- self.logger.debug(f"Received from {command}: {out}")
+ self._logger.debug(f"Received from {command}: {out}")
self._history_add(command=command, output=out)
return out
@abstractmethod
def _send_command(self, command: str, timeout: float) -> str:
"""
- Send a command and return the output.
+ Use the underlying protocol to execute the command and return the output
+ of the command.
"""
def _history_add(self, command: str, output: str) -> None:
@@ -79,17 +91,20 @@ def _history_add(self, command: str, output: str) -> None:
)
def close(self, force: bool = False) -> None:
- self.logger.logger_exit()
+ """
+ Close the remote session and free all used resources.
+ """
+ self._logger.logger_exit()
self._close(force)
@abstractmethod
def _close(self, force: bool = False) -> None:
"""
- Close the remote session, freeing all used resources.
+ Execute protocol specific steps needed to close the session properly.
"""
@abstractmethod
def is_alive(self) -> bool:
"""
- Check whether the session is still responding.
+ Check whether the remote session is still responding.
"""
diff --git a/dts/framework/remote_session/ssh_session.py b/dts/framework/remote_session/remote/ssh_session.py
similarity index 91%
rename from dts/framework/remote_session/ssh_session.py
rename to dts/framework/remote_session/remote/ssh_session.py
index 7ec327054d..96175f5284 100644
--- a/dts/framework/remote_session/ssh_session.py
+++ b/dts/framework/remote_session/remote/ssh_session.py
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
import time
@@ -17,7 +17,7 @@
class SSHSession(RemoteSession):
"""
- Module for creating Pexpect SSH sessions to a node.
+ Module for creating Pexpect SSH remote sessions.
"""
session: pxssh.pxssh
@@ -56,9 +56,9 @@ def _connect(self) -> None:
)
break
except Exception as e:
- self.logger.warning(e)
+ self._logger.warning(e)
time.sleep(2)
- self.logger.info(
+ self._logger.info(
f"Retrying connection: retry number {retry_attempt + 1}."
)
else:
@@ -67,13 +67,13 @@ def _connect(self) -> None:
self.send_expect("stty -echo", "#")
self.send_expect("stty columns 1000", "#")
except Exception as e:
- self.logger.error(RED(str(e)))
+ self._logger.error(RED(str(e)))
if getattr(self, "port", None):
suggestion = (
f"\nSuggestion: Check if the firewall on {self.hostname} is "
f"stopped.\n"
)
- self.logger.info(GREEN(suggestion))
+ self._logger.info(GREEN(suggestion))
raise SSHConnectionError(self.hostname)
@@ -87,8 +87,8 @@ def send_expect(
try:
retval = int(ret_status)
if retval:
- self.logger.error(f"Command: {command} failure!")
- self.logger.error(ret)
+ self._logger.error(f"Command: {command} failure!")
+ self._logger.error(ret)
return retval
else:
return ret
@@ -97,7 +97,7 @@ def send_expect(
else:
return ret
except Exception as e:
- self.logger.error(
+ self._logger.error(
f"Exception happened in [{command}] and output is "
f"[{self._get_output()}]"
)
diff --git a/dts/framework/settings.py b/dts/framework/settings.py
index 800f2c7b7f..6422b23499 100644
--- a/dts/framework/settings.py
+++ b/dts/framework/settings.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2021 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
# Copyright(c) 2022 University of New Hampshire
import argparse
@@ -23,7 +23,7 @@ def __init__(
default: str = None,
type: Callable[[str], _T | argparse.FileType | None] = None,
choices: Iterable[_T] | None = None,
- required: bool = True,
+ required: bool = False,
help: str | None = None,
metavar: str | tuple[str, ...] | None = None,
) -> None:
@@ -63,13 +63,17 @@ class _Settings:
def _get_parser() -> argparse.ArgumentParser:
- parser = argparse.ArgumentParser(description="DPDK test framework.")
+ parser = argparse.ArgumentParser(
+ description="Run DPDK test suites. All options may be specified with "
+ "the environment variables provided in brackets. "
+ "Command line arguments have higher priority.",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ )
parser.add_argument(
"--config-file",
action=_env_arg("DTS_CFG_FILE"),
default="conf.yaml",
- required=False,
help="[DTS_CFG_FILE] configuration file that describes the test cases, SUTs "
"and targets.",
)
@@ -79,7 +83,6 @@ def _get_parser() -> argparse.ArgumentParser:
"--output",
action=_env_arg("DTS_OUTPUT_DIR"),
default="output",
- required=False,
help="[DTS_OUTPUT_DIR] Output directory where dts logs and results are saved.",
)
@@ -88,7 +91,6 @@ def _get_parser() -> argparse.ArgumentParser:
"--timeout",
action=_env_arg("DTS_TIMEOUT"),
default=15,
- required=False,
help="[DTS_TIMEOUT] The default timeout for all DTS operations except for "
"compiling DPDK.",
)
@@ -98,7 +100,6 @@ def _get_parser() -> argparse.ArgumentParser:
"--verbose",
action=_env_arg("DTS_VERBOSE"),
default="N",
- required=False,
help="[DTS_VERBOSE] Set to 'Y' to enable verbose output, logging all messages "
"to the console.",
)
diff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbed_model/__init__.py
index c5512e5812..8ead9db482 100644
--- a/dts/framework/testbed_model/__init__.py
+++ b/dts/framework/testbed_model/__init__.py
@@ -1,7 +1,13 @@
# SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 University of New Hampshire
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
"""
-This module contains the classes used to model the physical traffic generator,
+This package contains the classes used to model the physical traffic generator,
system under test and any other components that need to be interacted with.
"""
+
+# pylama:ignore=W0611
+
+from .node import Node
+from .sut_node import SutNode
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py
index 8437975416..e1f06bc389 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -1,62 +1,119 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
"""
A node is a generic host that DTS connects to and manages.
"""
-from framework.config import NodeConfiguration
+from framework.config import (
+ BuildTargetConfiguration,
+ ExecutionConfiguration,
+ NodeConfiguration,
+)
from framework.logger import DTSLOG, getLogger
-from framework.remote_session import RemoteSession, create_remote_session
-from framework.settings import SETTINGS
+from framework.remote_session import OSSession, create_session
class Node(object):
"""
- Basic module for node management. This module implements methods that
+ Basic class for node management. This class implements methods that
manage a node, such as information gathering (of CPU/PCI/NIC) and
environment setup.
"""
+ main_session: OSSession
+ config: NodeConfiguration
name: str
- main_session: RemoteSession
- logger: DTSLOG
- _config: NodeConfiguration
- _other_sessions: list[RemoteSession]
+ _logger: DTSLOG
+ _other_sessions: list[OSSession]
def __init__(self, node_config: NodeConfiguration):
- self._config = node_config
+ self.config = node_config
+ self.name = node_config.name
+ self._logger = getLogger(self.name)
+ self.main_session = create_session(self.config, self.name, self._logger)
+
self._other_sessions = []
- self.name = node_config.name
- self.logger = getLogger(self.name)
- self.logger.info(f"Created node: {self.name}")
- self.main_session = create_remote_session(self._config, self.name, self.logger)
+ self._logger.info(f"Created node: {self.name}")
- def send_command(self, cmds: str, timeout: float = SETTINGS.timeout) -> str:
+ def set_up_execution(self, execution_config: ExecutionConfiguration) -> None:
"""
- Send commands to node and return string before timeout.
+ Perform the execution setup that will be done for each execution
+ this node is part of.
"""
+ self._set_up_execution(execution_config)
- return self.main_session.send_command(cmds, timeout)
+ def _set_up_execution(self, execution_config: ExecutionConfiguration) -> None:
+ """
+ This method exists to be optionally overwritten by derived classes and
+ is not decorated so that the derived class doesn't have to use the decorator.
+ """
- def create_session(self, name: str) -> RemoteSession:
- connection = create_remote_session(
- self._config,
- name,
- getLogger(name, node=self.name),
+ def tear_down_execution(self) -> None:
+ """
+ Perform the execution teardown that will be done after each execution
+ this node is part of concludes.
+ """
+ self._tear_down_execution()
+
+ def _tear_down_execution(self) -> None:
+ """
+ This method exists to be optionally overwritten by derived classes and
+ is not decorated so that the derived class doesn't have to use the decorator.
+ """
+
+ def set_up_build_target(
+ self, build_target_config: BuildTargetConfiguration
+ ) -> None:
+ """
+ Perform the build target setup that will be done for each build target
+ tested on this node.
+ """
+ self._set_up_build_target(build_target_config)
+
+ def _set_up_build_target(
+ self, build_target_config: BuildTargetConfiguration
+ ) -> None:
+ """
+ This method exists to be optionally overwritten by derived classes and
+ is not decorated so that the derived class doesn't have to use the decorator.
+ """
+
+ def tear_down_build_target(self) -> None:
+ """
+ Perform the build target teardown that will be done after each build target
+ tested on this node.
+ """
+ self._tear_down_build_target()
+
+ def _tear_down_build_target(self) -> None:
+ """
+ This method exists to be optionally overwritten by derived classes and
+ is not decorated so that the derived class doesn't have to use the decorator.
+ """
+
+ def create_session(self, name: str) -> OSSession:
+ """
+ Create and return a new OSSession tailored to the remote OS.
+ """
+ session_name = f"{self.name} {name}"
+ connection = create_session(
+ self.config,
+ session_name,
+ getLogger(session_name, node=self.name),
)
self._other_sessions.append(connection)
return connection
- def node_exit(self) -> None:
+ def close(self) -> None:
"""
- Recover all resource before node exit
+ Close all connections and free other resources.
"""
if self.main_session:
self.main_session.close()
for session in self._other_sessions:
session.close()
- self.logger.logger_exit()
+ self._logger.logger_exit()
diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbed_model/sut_node.py
new file mode 100644
index 0000000000..42acb6f9b2
--- /dev/null
+++ b/dts/framework/testbed_model/sut_node.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2010-2014 Intel Corporation
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+from .node import Node
+
+
+class SutNode(Node):
+ """
+ A class for managing connections to the System under Test, providing
+ methods that retrieve the necessary information about the node (such as
+ CPU, memory and NIC details) and configuration capabilities.
+ """
--
2.30.2
next prev parent reply other threads:[~2023-03-03 10:25 UTC|newest]
Thread overview: 97+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-24 16:24 [RFC PATCH v1 00/10] dts: add hello world testcase Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 01/10] dts: hello world config options Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 02/10] dts: hello world cli parameters and env vars Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 03/10] dts: ssh connection additions for hello world Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 04/10] dts: add basic node management methods Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 05/10] dts: add system under test node Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 06/10] dts: add traffic generator node Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 07/10] dts: add testcase and basic test results Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 08/10] dts: add test runner and statistics collector Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 09/10] dts: add hello world testplan Juraj Linkeš
2022-08-24 16:24 ` [RFC PATCH v1 10/10] dts: add hello world testsuite Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 00/10] dts: add hello world testcase Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 01/10] dts: add node and os abstractions Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 02/10] dts: add ssh command verification Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 03/10] dts: add dpdk build on sut Juraj Linkeš
2022-11-16 13:15 ` Owen Hilyard
[not found] ` <30ad4f7d087d4932845b6ca13934b1d2@pantheon.tech>
[not found] ` <CAHx6DYDOFMuEm4xc65OTrtUmGBtk8Z6UtSgS2grnR_RBY5HcjQ@mail.gmail.com>
2022-11-23 12:37 ` Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 04/10] dts: add dpdk execution handling Juraj Linkeš
2022-11-16 13:28 ` Owen Hilyard
[not found] ` <df13ee41efb64e7bb37791f21ae5bac1@pantheon.tech>
[not found] ` <CAHx6DYCEYxZ0Osm6fKhp3Jx8n7s=r7qVh8R41c6nCan8Or-dpA@mail.gmail.com>
2022-11-23 13:03 ` Juraj Linkeš
2022-11-28 13:05 ` Owen Hilyard
2022-11-14 16:54 ` [RFC PATCH v2 05/10] dts: add node memory setup Juraj Linkeš
2022-11-16 13:47 ` Owen Hilyard
2022-11-23 13:58 ` Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 06/10] dts: add test results module Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 07/10] dts: add simple stats report Juraj Linkeš
2022-11-16 13:57 ` Owen Hilyard
2022-11-14 16:54 ` [RFC PATCH v2 08/10] dts: add testsuite class Juraj Linkeš
2022-11-16 15:15 ` Owen Hilyard
2022-11-14 16:54 ` [RFC PATCH v2 09/10] dts: add hello world testplan Juraj Linkeš
2022-11-14 16:54 ` [RFC PATCH v2 10/10] dts: add hello world testsuite Juraj Linkeš
2023-01-17 15:48 ` [PATCH v3 00/10] dts: add hello world testcase Juraj Linkeš
2023-01-17 15:48 ` [PATCH v3 01/10] dts: add node and os abstractions Juraj Linkeš
2023-01-17 15:48 ` [PATCH v3 02/10] dts: add ssh command verification Juraj Linkeš
2023-01-17 15:48 ` [PATCH v3 03/10] dts: add dpdk build on sut Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 04/10] dts: add dpdk execution handling Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 05/10] dts: add node memory setup Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 06/10] dts: add test suite module Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 07/10] dts: add hello world testplan Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 08/10] dts: add hello world testsuite Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 09/10] dts: add test suite config and runner Juraj Linkeš
2023-01-17 15:49 ` [PATCH v3 10/10] dts: add test results module Juraj Linkeš
2023-01-19 16:16 ` [PATCH v3 00/10] dts: add hello world testcase Owen Hilyard
2023-02-09 16:47 ` Patrick Robb
2023-02-13 15:28 ` [PATCH v4 " Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 01/10] dts: add node and os abstractions Juraj Linkeš
2023-02-17 17:44 ` Bruce Richardson
2023-02-20 13:24 ` Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 02/10] dts: add ssh command verification Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 03/10] dts: add dpdk build on sut Juraj Linkeš
2023-02-22 16:44 ` Bruce Richardson
2023-02-13 15:28 ` [PATCH v4 04/10] dts: add dpdk execution handling Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 05/10] dts: add node memory setup Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 06/10] dts: add test suite module Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 07/10] dts: add hello world testsuite Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 08/10] dts: add test suite config and runner Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 09/10] dts: add test results module Juraj Linkeš
2023-02-13 15:28 ` [PATCH v4 10/10] doc: update DTS setup and test suite cookbook Juraj Linkeš
2023-02-17 17:26 ` [PATCH v4 00/10] dts: add hello world testcase Bruce Richardson
2023-02-20 10:13 ` Juraj Linkeš
2023-02-20 11:56 ` Bruce Richardson
2023-02-22 16:39 ` Bruce Richardson
2023-02-23 8:27 ` Juraj Linkeš
2023-02-23 9:17 ` Bruce Richardson
2023-02-23 15:28 ` [PATCH v5 " Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 01/10] dts: add node and os abstractions Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 02/10] dts: add ssh command verification Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 03/10] dts: add dpdk build on sut Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 04/10] dts: add dpdk execution handling Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 05/10] dts: add node memory setup Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 06/10] dts: add test suite module Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 07/10] dts: add hello world testsuite Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 08/10] dts: add test suite config and runner Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 09/10] dts: add test results module Juraj Linkeš
2023-02-23 15:28 ` [PATCH v5 10/10] doc: update DTS setup and test suite cookbook Juraj Linkeš
2023-03-03 8:31 ` Huang, ChenyuX
2023-02-23 16:13 ` [PATCH v5 00/10] dts: add hello world testcase Bruce Richardson
2023-02-26 19:11 ` Wathsala Wathawana Vithanage
2023-02-27 8:28 ` Juraj Linkeš
2023-02-28 15:27 ` Wathsala Wathawana Vithanage
2023-03-01 8:35 ` Juraj Linkeš
2023-03-03 10:24 ` [PATCH v6 00/10] dts: add hello world test case Juraj Linkeš
2023-03-03 10:24 ` Juraj Linkeš [this message]
2023-03-03 10:24 ` [PATCH v6 02/10] dts: add ssh command verification Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 03/10] dts: add dpdk build on sut Juraj Linkeš
2023-03-20 8:30 ` David Marchand
2023-03-20 13:12 ` Juraj Linkeš
2023-03-20 13:22 ` David Marchand
2023-03-03 10:25 ` [PATCH v6 04/10] dts: add dpdk execution handling Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 05/10] dts: add node memory setup Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 06/10] dts: add test suite module Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 07/10] dts: add hello world testsuite Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 08/10] dts: add test suite config and runner Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 09/10] dts: add test results module Juraj Linkeš
2023-03-03 10:25 ` [PATCH v6 10/10] doc: update dts setup and test suite cookbook Juraj Linkeš
2023-03-09 21:47 ` Patrick Robb
2023-03-19 15:26 ` [PATCH v6 00/10] dts: add hello world test case Thomas Monjalon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230303102507.527790-2-juraj.linkes@pantheon.tech \
--to=juraj.linkes@pantheon.tech \
--cc=Honnappa.Nagarahalli@arm.com \
--cc=bruce.richardson@intel.com \
--cc=dev@dpdk.org \
--cc=lijuan.tu@intel.com \
--cc=probb@iol.unh.edu \
--cc=thomas@monjalon.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).