From 93440a018d098f34f0c8f6c1a4ad8c66a914c225 Mon Sep 17 00:00:00 2001 From: Bruce Ashfield Date: Fri, 5 Jun 2026 01:01:59 +0000 Subject: tests: make k3s, incus, and xen tests self-contained These boot tests previously required manual local.conf changes to set CONTAINER_PROFILE, DISTRO_FEATURES, and other variables before the image could be built and tested. This meant complete test coverage required editing local.conf between runs. Add build fixtures that use bitbake -R with a temporary conf file to inject the required variables automatically: - k3s: builds container-image-host with CONTAINER_PROFILE=k3s-host - incus: builds container-image-host with CONTAINER_PROFILE=incus - xen: builds xen-image-minimal with xen/vxn DISTRO_FEATURES Each test module now builds its own image before booting, using the same run_bitbake + extra_vars pattern established in test_container_cross_install.py. Signed-off-by: Bruce Ashfield --- tests/test_incus_runtime.py | 65 +++++++++++++++++++++++++++++------- tests/test_k3s_runtime.py | 81 +++++++++++++++++++++++++++++++++------------ tests/test_xen_runtime.py | 62 +++++++++++++++++++++++++--------- 3 files changed, 159 insertions(+), 49 deletions(-) diff --git a/tests/test_incus_runtime.py b/tests/test_incus_runtime.py index 6de4bd5c..ea5eb1fc 100644 --- a/tests/test_incus_runtime.py +++ b/tests/test_incus_runtime.py @@ -5,15 +5,11 @@ Incus runtime tests - boot container-image-host with incus and verify system container management. -Build prerequisites (in local.conf): - require conf/distro/include/meta-virt-host.conf - require conf/distro/include/container-host-incus.conf - MACHINE = "qemux86-64" # or qemuarm64 - - bitbake container-image-host +The tests automatically build container-image-host with CONTAINER_PROFILE=incus +before booting. No local.conf changes needed. Run: - pytest tests/test_incus_runtime.py -v --machine qemux86-64 + pytest tests/test_incus_runtime.py -v --poky-dir /opt/bruce/poky Options: --boot-timeout QEMU boot timeout (default: 120s) @@ -22,8 +18,11 @@ Options: import os import re +import subprocess +import tempfile import time import pytest +from pathlib import Path try: import pexpect @@ -38,19 +37,61 @@ pytestmark = [ ] +def _run_bitbake(build_dir, recipe, extra_vars=None, timeout=3600): + """Run bitbake with optional variable overrides via -R conf file.""" + bb_cmd = "bitbake" + conf_file = None + if extra_vars: + conf_file = tempfile.NamedTemporaryFile( + mode='w', suffix='.conf', prefix='pytest-incus-', + dir=str(build_dir / "conf"), delete=False) + for var, val in extra_vars.items(): + conf_file.write(f'{var} = "{val}"\n') + conf_file.close() + bb_cmd += f" -R {conf_file.name}" + bb_cmd += f" {recipe}" + poky_dir = build_dir.parent + full_cmd = f"bash -c 'cd {poky_dir} && source oe-init-build-env {build_dir} >/dev/null 2>&1 && {bb_cmd}'" + try: + return subprocess.run(full_cmd, shell=True, cwd=build_dir, + timeout=timeout, capture_output=True, text=True) + finally: + if conf_file: + os.unlink(conf_file.name) + + +@pytest.fixture(scope="module") +def incus_image(request): + """Build container-image-host with incus profile.""" + poky_dir = Path(request.config.getoption("--poky-dir")) + bd = request.config.getoption("--build-dir") + build_dir = Path(bd) if bd else poky_dir / "build" + result = _run_bitbake( + build_dir, "container-image-host", + extra_vars={ + "CONTAINER_PROFILE": "incus", + "DISTRO_FEATURES:append": " virtualization", + }, + ) + if result.returncode != 0: + pytest.fail(f"Incus image build failed: {result.stderr}") + + @pytest.fixture(scope="module") -def incus_qemu(request): - """Boot a QEMU VM with incus and return the pexpect session.""" +def incus_qemu(request, incus_image): + """Build incus image, boot a QEMU VM, and return the pexpect session.""" machine = request.config.getoption("--machine", default="qemux86-64") boot_timeout = int(request.config.getoption("--boot-timeout", default="120")) no_kvm = request.config.getoption("--no-kvm", default=False) - builddir = os.environ.get("BUILDDIR", os.path.expanduser("~/poky/build")) + poky_dir = Path(request.config.getoption("--poky-dir")) + bd = request.config.getoption("--build-dir") + builddir = str(Path(bd) if bd else poky_dir / "build") kvm_opt = "" if no_kvm else "kvm" - cmd = f"runqemu {machine} nographic slirp {kvm_opt} qemuparams=\"-m 4096\"" + cmd = f"runqemu {machine} container-image-host ext4 nographic slirp {kvm_opt} qemuparams=\"-m 4096\"" - child = pexpect.spawn(f"bash -c 'source {builddir}/oe-init-build-env {builddir} >/dev/null 2>&1 && {cmd}'", + child = pexpect.spawn(f"bash -c 'cd {poky_dir} && source oe-init-build-env {builddir} >/dev/null 2>&1 && {cmd}'", timeout=boot_timeout, encoding="utf-8", logfile=None) # Wait for login prompt diff --git a/tests/test_k3s_runtime.py b/tests/test_k3s_runtime.py index aa0d443f..662bdf4c 100644 --- a/tests/test_k3s_runtime.py +++ b/tests/test_k3s_runtime.py @@ -8,22 +8,18 @@ Single-node tests verify k3s server start, node readiness, and basic pod deployment. Multi-node tests use QEMU socket networking to connect two VMs on a shared L2 segment and verify agent join + multi-node scheduling. -Build prerequisites (in local.conf): - require conf/distro/include/meta-virt-host.conf - require conf/distro/include/container-host-k3s.conf - MACHINE = "qemux86-64" # or qemuarm64 - - bitbake container-image-host +The tests automatically build container-image-host with CONTAINER_PROFILE=k3s-host +before booting. No local.conf changes needed. Run: # Single-node only - pytest tests/test_k3s_runtime.py -v -k "not multinode" --machine qemux86-64 + pytest tests/test_k3s_runtime.py -v -k "not multinode" --poky-dir /opt/bruce/poky # Multi-node only - pytest tests/test_k3s_runtime.py -v -k "multinode" --machine qemux86-64 + pytest tests/test_k3s_runtime.py -v -k "multinode" --poky-dir /opt/bruce/poky # All tests - pytest tests/test_k3s_runtime.py -v --machine qemux86-64 + pytest tests/test_k3s_runtime.py -v --poky-dir /opt/bruce/poky Options: --k3s-timeout Overall k3s readiness timeout (default: 300s) @@ -43,6 +39,8 @@ Notes: import os import re import shutil +import subprocess +import tempfile import time import pytest from pathlib import Path @@ -290,6 +288,39 @@ class K3sRunner: # Fixtures # ============================================================================ +def run_bitbake(build_dir, recipe, extra_vars=None, timeout=3600): + """Run a bitbake command within the Yocto environment.""" + import tempfile + + bb_cmd = "bitbake" + + conf_file = None + if extra_vars: + conf_file = tempfile.NamedTemporaryFile( + mode='w', suffix='.conf', prefix='pytest-k3s-', + dir=str(build_dir / "conf"), delete=False) + for var, val in extra_vars.items(): + conf_file.write(f'{var} = "{val}"\n') + conf_file.close() + bb_cmd += f" -R {conf_file.name}" + + bb_cmd += f" {recipe}" + + poky_dir = build_dir.parent + full_cmd = f"bash -c 'cd {poky_dir} && source oe-init-build-env {build_dir} >/dev/null 2>&1 && {bb_cmd}'" + + try: + result = subprocess.run( + full_cmd, shell=True, cwd=build_dir, + timeout=timeout, capture_output=True, text=True, + ) + finally: + if conf_file: + os.unlink(conf_file.name) + + return result + + @pytest.fixture(scope="module") def poky_dir(request): """Path to poky directory.""" @@ -325,20 +356,28 @@ def k3s_timeout(request): @pytest.fixture(scope="module") -def k3s_session(request, poky_dir, build_dir, machine): +def k3s_image(build_dir): + """Build container-image-host with k3s profile.""" + result = run_bitbake( + build_dir, "container-image-host", + extra_vars={ + "CONTAINER_PROFILE": "k3s-host", + "DISTRO_FEATURES:append": " k3s virtualization", + }, + ) + if result.returncode != 0: + pytest.fail(f"K3s image build failed: {result.stderr}") + + +@pytest.fixture(scope="module") +def k3s_session(request, poky_dir, build_dir, machine, k3s_image): """ - Module-scoped fixture that boots container-image-host once for all - single-node k3s tests. Uses runqemu for single-node tests. + Module-scoped fixture that builds container-image-host with k3s profile, + boots it, and provides a session for all single-node k3s tests. """ if not PEXPECT_AVAILABLE: pytest.skip("pexpect not installed. Run: pip install pexpect") - deploy_dir = build_dir / "tmp" / "deploy" / "images" / machine - ext4_files = list(deploy_dir.glob("container-image-host-*.rootfs.ext4")) - if not ext4_files: - pytest.skip( - f"container-image-host ext4 image not found in {deploy_dir}") - timeout = request.config.getoption("--boot-timeout") use_kvm = not request.config.getoption("--no-kvm") @@ -356,10 +395,10 @@ def k3s_session(request, poky_dir, build_dir, machine): @pytest.fixture(scope="module") -def k3s_multinode(request, poky_dir, build_dir, machine): +def k3s_multinode(request, poky_dir, build_dir, machine, k3s_image): """ - Module-scoped fixture that boots two VMs connected via QEMU socket - networking for multi-node k3s testing. + Module-scoped fixture that builds with k3s profile, then boots two VMs + connected via QEMU socket networking for multi-node k3s testing. Uses direct QEMU launch (not runqemu) since runqemu can only run one VM at a time. Creates a copy of the rootfs for the agent VM. diff --git a/tests/test_xen_runtime.py b/tests/test_xen_runtime.py index 697c57ee..790a25bf 100644 --- a/tests/test_xen_runtime.py +++ b/tests/test_xen_runtime.py @@ -7,21 +7,11 @@ Xen runtime boot tests - boot xen-image-minimal and verify hypervisor. These tests boot an actual Xen Dom0 image via runqemu, verify the hypervisor is functional, check guest bundling, and exercise vxn/containerd. -Build prerequisites (minimum for Dom0 boot tests): - DISTRO_FEATURES:append = " xen systemd" - MACHINE = "qemux86-64" # or qemuarm64 - bitbake xen-image-minimal - -For guest bundling tests: - IMAGE_INSTALL:append:pn-xen-image-minimal = " alpine-xen-guest-bundle" - -For vxn/containerd tests: - DISTRO_FEATURES:append = " virtualization vcontainer vxn" - IMAGE_INSTALL:append:pn-xen-image-minimal = " vxn" - BBMULTICONFIG = "vruntime-aarch64 vruntime-x86-64" +The tests automatically build xen-image-minimal with the required +DISTRO_FEATURES before booting. No local.conf changes needed. Run with: - pytest tests/test_xen_runtime.py -v --machine qemux86-64 + pytest tests/test_xen_runtime.py -v --poky-dir /opt/bruce/poky Skip network-dependent tests: pytest tests/test_xen_runtime.py -v -m "boot and not network" @@ -33,7 +23,10 @@ Custom paths and longer timeout: --boot-timeout 180 """ +import os import re +import subprocess +import tempfile import time import pytest from pathlib import Path @@ -50,6 +43,29 @@ except ImportError: # are defined in conftest.py to avoid conflicts with other test files. +def _run_bitbake(build_dir, recipe, extra_vars=None, timeout=3600): + """Run bitbake with optional variable overrides via -R conf file.""" + bb_cmd = "bitbake" + conf_file = None + if extra_vars: + conf_file = tempfile.NamedTemporaryFile( + mode='w', suffix='.conf', prefix='pytest-xen-', + dir=str(build_dir / "conf"), delete=False) + for var, val in extra_vars.items(): + conf_file.write(f'{var} = "{val}"\n') + conf_file.close() + bb_cmd += f" -R {conf_file.name}" + bb_cmd += f" {recipe}" + poky_dir = build_dir.parent + full_cmd = f"bash -c 'cd {poky_dir} && source oe-init-build-env {build_dir} >/dev/null 2>&1 && {bb_cmd}'" + try: + return subprocess.run(full_cmd, shell=True, cwd=build_dir, + timeout=timeout, capture_output=True, text=True) + finally: + if conf_file: + os.unlink(conf_file.name) + + class XenRunner: """ Manages a runqemu session for Xen boot testing. @@ -218,11 +234,25 @@ def machine(request): @pytest.fixture(scope="module") -def xen_session(request, poky_dir, build_dir, machine): +def xen_image(build_dir): + """Build xen-image-minimal with required distro features.""" + result = _run_bitbake( + build_dir, "xen-image-minimal", + extra_vars={ + "DISTRO_FEATURES:append": " xen vxn virtualization vcontainer systemd", + }, + ) + if result.returncode != 0: + pytest.fail(f"Xen image build failed: {result.stderr}") + + +@pytest.fixture(scope="module") +def xen_session(request, poky_dir, build_dir, machine, xen_image): """ - Module-scoped fixture that boots xen-image-minimal once for all tests. + Module-scoped fixture that builds xen-image-minimal and boots it + once for all tests. - Skips if pexpect is not available, image is not found, or boot fails. + Skips if pexpect is not available or boot fails. """ if not PEXPECT_AVAILABLE: pytest.skip("pexpect not installed. Run: pip install pexpect") -- cgit v1.2.3-54-g00ecf