|
[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
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |