<div dir="ltr">Acked-by: Jeremy Spweock <<a href="mailto:jspweock@iol.unh.edu" target="_blank">jspweock@iol.unh.edu</a>><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Apr 20, 2023 at 10:16 AM Juraj Linkeš <juraj.linkes@pantheon.tech> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Add additional convenience options for specifying what DPDK version to<br>
test.<br>
<br>
Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech><br>
---<br>
 dts/framework/config/__init__.py |  11 +--<br>
 dts/framework/settings.py        |  20 ++---<br>
 dts/framework/utils.py           | 140 +++++++++++++++++++++++++++++++<br>
 3 files changed, 152 insertions(+), 19 deletions(-)<br>
<br>
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py<br>
index ebb0823ff5..a4b73483e6 100644<br>
--- a/dts/framework/config/__init__.py<br>
+++ b/dts/framework/config/__init__.py<br>
@@ -11,21 +11,14 @@<br>
 import os.path<br>
 import pathlib<br>
 from dataclasses import dataclass<br>
-from enum import Enum, auto, unique<br>
+from enum import auto, unique<br>
 from typing import Any, TypedDict<br>
<br>
 import warlock  # type: ignore<br>
 import yaml<br>
<br>
 from framework.settings import SETTINGS<br>
-<br>
-<br>
-class StrEnum(Enum):<br>
-    @staticmethod<br>
-    def _generate_next_value_(<br>
-        name: str, start: int, count: int, last_values: object<br>
-    ) -> str:<br>
-        return name<br>
+from framework.utils import StrEnum<br>
<br>
<br>
 @unique<br>
diff --git a/dts/framework/settings.py b/dts/framework/settings.py<br>
index 71955f4581..cfa39d011b 100644<br>
--- a/dts/framework/settings.py<br>
+++ b/dts/framework/settings.py<br>
@@ -10,7 +10,7 @@<br>
 from pathlib import Path<br>
 from typing import Any, TypeVar<br>
<br>
-from .exception import ConfigurationError<br>
+from .utils import DPDKGitTarball<br>
<br>
 _T = TypeVar("_T")<br>
<br>
@@ -124,11 +124,13 @@ def _get_parser() -> argparse.ArgumentParser:<br>
     parser.add_argument(<br>
         "--tarball",<br>
         "--snapshot",<br>
+        "--git-ref",<br>
         action=_env_arg("DTS_DPDK_TARBALL"),<br>
         default="dpdk.tar.xz",<br>
         type=Path,<br>
-        help="[DTS_DPDK_TARBALL] Path to DPDK source code tarball "<br>
-        "which will be used in testing.",<br>
+        help="[DTS_DPDK_TARBALL] Path to DPDK source code tarball or a git commit ID, "<br>
+        "tag ID or tree ID to test. To test local changes, first commit them, "<br>
+        "then use the commit ID with this option.",<br>
     )<br>
<br>
     parser.add_argument(<br>
@@ -160,21 +162,19 @@ def _get_parser() -> argparse.ArgumentParser:<br>
     return parser<br>
<br>
<br>
-def _check_tarball_path(parsed_args: argparse.Namespace) -> None:<br>
-    if not os.path.exists(parsed_args.tarball):<br>
-        raise ConfigurationError(f"DPDK tarball '{parsed_args.tarball}' doesn't exist.")<br>
-<br>
-<br>
 def _get_settings() -> _Settings:<br>
     parsed_args = _get_parser().parse_args()<br>
-    _check_tarball_path(parsed_args)<br>
     return _Settings(<br>
         config_file_path=parsed_args.config_file,<br>
         output_dir=parsed_args.output_dir,<br>
         timeout=parsed_args.timeout,<br>
         verbose=(parsed_args.verbose == "Y"),<br>
         skip_setup=(parsed_args.skip_setup == "Y"),<br>
-        dpdk_tarball_path=parsed_args.tarball,<br>
+        dpdk_tarball_path=Path(<br>
+            DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir)<br>
+        )<br>
+        if not os.path.exists(parsed_args.tarball)<br>
+        else Path(parsed_args.tarball),<br>
         compile_timeout=parsed_args.compile_timeout,<br>
         test_cases=parsed_args.test_cases.split(",") if parsed_args.test_cases else [],<br>
         re_run=parsed_args.re_run,<br>
diff --git a/dts/framework/utils.py b/dts/framework/utils.py<br>
index 55e0b0ef0e..0623106b78 100644<br>
--- a/dts/framework/utils.py<br>
+++ b/dts/framework/utils.py<br>
@@ -3,7 +3,26 @@<br>
 # Copyright(c) 2022-2023 PANTHEON.tech s.r.o.<br>
 # Copyright(c) 2022-2023 University of New Hampshire<br>
<br>
+import atexit<br>
+import os<br>
+import subprocess<br>
 import sys<br>
+from enum import Enum<br>
+from pathlib import Path<br>
+from subprocess import SubprocessError<br>
+<br>
+from .exception import ConfigurationError<br>
+<br>
+<br>
+class StrEnum(Enum):<br>
+    @staticmethod<br>
+    def _generate_next_value_(<br>
+        name: str, start: int, count: int, last_values: object<br>
+    ) -> str:<br>
+        return name<br>
+<br>
+    def __str__(self) -> str:<br>
+        return <a href="http://self.name" rel="noreferrer" target="_blank">self.name</a><br>
<br>
<br>
 def check_dts_python_version() -> None:<br>
@@ -80,3 +99,124 @@ def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):<br>
<br>
     def __str__(self) -> str:<br>
         return " ".join(f"{self._default_library} {self._dpdk_args}".split())<br>
+<br>
+<br>
+class _TarCompressionFormat(StrEnum):<br>
+    """Compression formats that tar can use.<br>
+<br>
+    Enum names are the shell compression commands<br>
+    and Enum values are the associated file extensions.<br>
+    """<br>
+<br>
+    gzip = "gz"<br>
+    compress = "Z"<br>
+    bzip2 = "bz2"<br>
+    lzip = "lz"<br>
+    lzma = "lzma"<br>
+    lzop = "lzo"<br>
+    xz = "xz"<br>
+    zstd = "zst"<br>
+<br>
+<br>
+class DPDKGitTarball(object):<br>
+    """Create a compressed tarball of DPDK from the repository.<br>
+<br>
+    The DPDK version is specified with git object git_ref.<br>
+    The tarball will be compressed with _TarCompressionFormat,<br>
+    which must be supported by the DTS execution environment.<br>
+    The resulting tarball will be put into output_dir.<br>
+<br>
+    The class supports the os.PathLike protocol,<br>
+    which is used to get the Path of the tarball::<br>
+<br>
+        from pathlib import Path<br>
+        tarball = DPDKGitTarball("HEAD", "output")<br>
+        tarball_path = Path(tarball)<br>
+<br>
+    Arguments:<br>
+        git_ref: A git commit ID, tag ID or tree ID.<br>
+        output_dir: The directory where to put the resulting tarball.<br>
+        tar_compression_format: The compression format to use.<br>
+    """<br>
+<br>
+    _git_ref: str<br>
+    _tar_compression_format: _TarCompressionFormat<br>
+    _tarball_dir: Path<br>
+    _tarball_name: str<br>
+    _tarball_path: Path | None<br>
+<br>
+    def __init__(<br>
+        self,<br>
+        git_ref: str,<br>
+        output_dir: str,<br>
+        tar_compression_format: _TarCompressionFormat = _TarCompressionFormat.xz,<br>
+    ):<br>
+        self._git_ref = git_ref<br>
+        self._tar_compression_format = tar_compression_format<br>
+<br>
+        self._tarball_dir = Path(output_dir, "tarball")<br>
+<br>
+        self._get_commit_id()<br>
+        self._create_tarball_dir()<br>
+<br>
+        self._tarball_name = (<br>
+            f"dpdk-tarball-{self._git_ref}.tar.{self._tar_compression_format.value}"<br>
+        )<br>
+        self._tarball_path = self._check_tarball_path()<br>
+        if not self._tarball_path:<br>
+            self._create_tarball()<br>
+<br>
+    def _get_commit_id(self) -> None:<br>
+        result = subprocess.run(<br>
+            ["git", "rev-parse", "--verify", self._git_ref],<br>
+            text=True,<br>
+            capture_output=True,<br>
+        )<br>
+        if result.returncode != 0:<br>
+            raise ConfigurationError(<br>
+                f"{self._git_ref} is neither a path to an existing DPDK "<br>
+                "archive nor a valid git reference.\n"<br>
+                f"Command: {result.args}\n"<br>
+                f"Stdout: {result.stdout}\n"<br>
+                f"Stderr: {result.stderr}"<br>
+            )<br>
+        self._git_ref = result.stdout.strip()<br>
+<br>
+    def _create_tarball_dir(self) -> None:<br>
+        os.makedirs(self._tarball_dir, exist_ok=True)<br>
+<br>
+    def _check_tarball_path(self) -> Path | None:<br>
+        if self._tarball_name in os.listdir(self._tarball_dir):<br>
+            return Path(self._tarball_dir, self._tarball_name)<br>
+        return None<br>
+<br>
+    def _create_tarball(self) -> None:<br>
+        self._tarball_path = Path(self._tarball_dir, self._tarball_name)<br>
+<br>
+        atexit.register(self._delete_tarball)<br>
+<br>
+        result = subprocess.run(<br>
+            'git -C "$(git rev-parse --show-toplevel)" archive '<br>
+            f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | '<br>
+            f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}",<br>
+            shell=True,<br>
+            text=True,<br>
+            capture_output=True,<br>
+        )<br>
+<br>
+        if result.returncode != 0:<br>
+            raise SubprocessError(<br>
+                f"Git archive creation failed with exit code {result.returncode}.\n"<br>
+                f"Command: {result.args}\n"<br>
+                f"Stdout: {result.stdout}\n"<br>
+                f"Stderr: {result.stderr}"<br>
+            )<br>
+<br>
+        atexit.unregister(self._delete_tarball)<br>
+<br>
+    def _delete_tarball(self) -> None:<br>
+        if self._tarball_path and os.path.exists(self._tarball_path):<br>
+            os.remove(self._tarball_path)<br>
+<br>
+    def __fspath__(self):<br>
+        return str(self._tarball_path)<br>
-- <br>
2.30.2<br>
<br>
</blockquote></div>