[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v3 2/6] automation/qtb: add the QTB machine for riscv64



The QTB (QEMU Test Bench) Python package drives a QEMU process over QMP and
qtest through QtbMachine, which leaves the QEMU binary (_resolve_binary())
and the machine part of its command line (_machine_args()) to a subclass.

Add RiscvTestMachine, the riscv64 implementation of QtbMachine, with:
  - HostConfig, the test-agnostic description of the host (Xen) to boot:
    for now, the pCPU count and the RAM size.
  - The QEMU command line that boots Xen on the virt machine in
    aia=aplic-imsic mode, using the distribution's qemu-system-riscv64,
    found in PATH, and the OpenSBI it bundles. Xen is resolved from the
    binaries directory before QEMU starts, so a missing one fails the
    test without launching QEMU for nothing.
  - expect_console() waits for a string on a console and turns a timeout or
    a closed console into an AssertionError, which pytest reports as a test
    failure.
  - Xen's console is mirrored to stdout, so a failing test shows where Xen
    stopped without fetching its log from the artifacts.

QtbEnv (qtb/env.py) holds the directories shared by every test, and
qtb/utils.py holds the generic path helpers.

Suggested-by: Stefano Stabellini <sstabellini@xxxxxxxxxx>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Baptiste Le Duc <baptiste.le-duc@xxxxxxxxxx>
---
The machine is unused on its own: the pytest fixture booting it and the
first test come in the next patch.
---
Changes in v3:
- New patch, split out of v2 "automation/qtb: add Python QTB framework
  with the console-test type".
- Mirror Xen's console to stdout, so pytest shows it in the report of a
  failing test.
- Map pexpect timeouts and EOF to AssertionError (a pytest failure)
  instead of a dedicated TestFailure exception.
- Keep the host at 2GB.
- Drop the Jinja2 host device tree.
---
 automation/scripts/qtb/__init__.py       |  2 +
 automation/scripts/qtb/env.py            | 15 ++++++
 automation/scripts/qtb/riscv/__init__.py |  2 +
 automation/scripts/qtb/riscv/machine.py  | 92 ++++++++++++++++++++++++++++++++
 automation/scripts/qtb/utils.py          | 15 ++++++
 5 files changed, 126 insertions(+)

diff --git a/automation/scripts/qtb/__init__.py 
b/automation/scripts/qtb/__init__.py
new file mode 100644
index 0000000000..a0e6e76cb2
--- /dev/null
+++ b/automation/scripts/qtb/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+"""QTB (QEMU Test Bench) test frameworks."""
diff --git a/automation/scripts/qtb/env.py b/automation/scripts/qtb/env.py
new file mode 100644
index 0000000000..7887d6af23
--- /dev/null
+++ b/automation/scripts/qtb/env.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+"""Run environment prepared by the launcher and shared by every test."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+
+
+@dataclass(frozen=True)
+class QtbEnv:
+    """Directories prepared by the launcher and shared by every test."""
+
+    binaries_dir: Path  # Build artifacts (xen, ...) the hosts boot.
+    log_dir: Path | None = None  # Logs, kept after the run.
diff --git a/automation/scripts/qtb/riscv/__init__.py 
b/automation/scripts/qtb/riscv/__init__.py
new file mode 100644
index 0000000000..7a0218c73c
--- /dev/null
+++ b/automation/scripts/qtb/riscv/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+"""QTB riscv64 test framework package."""
diff --git a/automation/scripts/qtb/riscv/machine.py 
b/automation/scripts/qtb/riscv/machine.py
new file mode 100644
index 0000000000..1c5e3b0d9e
--- /dev/null
+++ b/automation/scripts/qtb/riscv/machine.py
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: GPL-2.0-only
+"""QtbMachine for riscv64."""
+
+from __future__ import annotations
+
+import shutil
+from collections.abc import Sequence
+from dataclasses import dataclass
+
+import pexpect
+from qemu.qtb import GuestConsoleSession
+from qemu.qtb import QtbMachine
+
+from qtb.env import QtbEnv
+from qtb.utils import resolve_under
+
+
+@dataclass(frozen=True)
+class HostConfig:
+    """The test-agnostic description of what to boot."""
+
+    pcpu: int
+    memory: int = 2048  # MB
+
+
+class RiscvTestMachine(QtbMachine):
+    arch_name = "riscv64"
+    gdb_arch = "riscv:rv64"
+
+    qemu_bin = "qemu-system-riscv64"
+
+    def __init__(
+        self,
+        host_config: HostConfig,
+        *,
+        env: QtbEnv,
+    ) -> None:
+        self.host = host_config
+        self.env = env
+        # Resolved before QEMU starts, so a missing binary fails early.
+        self.xen = resolve_under("xen", env.binaries_dir)
+        super().__init__(
+            memory=self.host.memory,
+            cpus=self.host.pcpu,
+            mirror_console=True,
+            # Default expect timeout
+            timeout=30,
+            log_dir=str(env.log_dir) if env.log_dir else None,
+        )
+
+    # Implements QtbMachine._machine_args()
+    def _machine_args(self, memory: int, cpus: int) -> Sequence[str]:
+        machine_opt = "virt,aia=aplic-imsic"
+        cpu_opt = "rv64,svpbmt=on"
+        return [
+            "-M",
+            machine_opt,
+            "-cpu",
+            cpu_opt,
+            "-smp",
+            str(cpus),
+            "-m",
+            str(memory),
+            "-kernel",
+            str(self.xen),
+        ]
+
+    # Implements QtbMachine._resolve_binary()
+    @classmethod
+    def _resolve_binary(cls) -> str:
+        qemu = shutil.which(cls.qemu_bin)
+        if qemu is None:
+            raise FileNotFoundError(f"{cls.qemu_bin} not found in PATH")
+        return qemu
+
+    def get_host_console(self) -> GuestConsoleSession | None:
+        return self.console
+
+    @staticmethod
+    def expect_console(
+        console: GuestConsoleSession, expected: str, timeout: int
+    ) -> None:
+        """Wait for `expected` on console and fail if it does not show up
+        within `timeout`, which overrides the default one from QtbMachine."""
+        if console is None:
+            raise RuntimeError("Xen console not wired up, host not launched?")
+        try:
+            console.expect_exact(expected, timeout=timeout)
+        except pexpect.TIMEOUT:
+            raise AssertionError(f"{expected!r} not seen within {timeout}s") 
from None
+        except pexpect.EOF:
+            raise AssertionError(f"console closed before {expected!r}") from 
None
diff --git a/automation/scripts/qtb/utils.py b/automation/scripts/qtb/utils.py
new file mode 100644
index 0000000000..cce5936acb
--- /dev/null
+++ b/automation/scripts/qtb/utils.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+"""Generic helpers shared by the QTB test frameworks."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+
+def resolve_under(file_name: str, base: Path) -> Path:
+    """Resolve `file_name` against `base`, absolute paths pass through."""
+    p = Path(file_name)
+    path = p if p.is_absolute() else base / p
+    if not path.exists():
+        raise FileNotFoundError(f"cannot resolve {str(path)!r}: does not 
exist")
+    return path

-- 
2.56.0




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.