summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-06-05 01:01:59 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-06-12 02:58:55 +0000
commit93440a018d098f34f0c8f6c1a4ad8c66a914c225 (patch)
treec6b90a1974f4a18aa84ffa90be36eb7e5f53c90e
parent5e958db73270a60c6ac04b19aec8f66b6ba474e2 (diff)
downloadmeta-virtualization-93440a018d098f34f0c8f6c1a4ad8c66a914c225.tar.gz
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 <bruce.ashfield@gmail.com>
-rw-r--r--tests/test_incus_runtime.py65
-rw-r--r--tests/test_k3s_runtime.py81
-rw-r--r--tests/test_xen_runtime.py62
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 @@
5Incus runtime tests - boot container-image-host with incus and verify 5Incus runtime tests - boot container-image-host with incus and verify
6system container management. 6system container management.
7 7
8Build prerequisites (in local.conf): 8The tests automatically build container-image-host with CONTAINER_PROFILE=incus
9 require conf/distro/include/meta-virt-host.conf 9before booting. No local.conf changes needed.
10 require conf/distro/include/container-host-incus.conf
11 MACHINE = "qemux86-64" # or qemuarm64
12
13 bitbake container-image-host
14 10
15Run: 11Run:
16 pytest tests/test_incus_runtime.py -v --machine qemux86-64 12 pytest tests/test_incus_runtime.py -v --poky-dir /opt/bruce/poky
17 13
18Options: 14Options:
19 --boot-timeout QEMU boot timeout (default: 120s) 15 --boot-timeout QEMU boot timeout (default: 120s)
@@ -22,8 +18,11 @@ Options:
22 18
23import os 19import os
24import re 20import re
21import subprocess
22import tempfile
25import time 23import time
26import pytest 24import pytest
25from pathlib import Path
27 26
28try: 27try:
29 import pexpect 28 import pexpect
@@ -38,19 +37,61 @@ pytestmark = [
38] 37]
39 38
40 39
40def _run_bitbake(build_dir, recipe, extra_vars=None, timeout=3600):
41 """Run bitbake with optional variable overrides via -R conf file."""
42 bb_cmd = "bitbake"
43 conf_file = None
44 if extra_vars:
45 conf_file = tempfile.NamedTemporaryFile(
46 mode='w', suffix='.conf', prefix='pytest-incus-',
47 dir=str(build_dir / "conf"), delete=False)
48 for var, val in extra_vars.items():
49 conf_file.write(f'{var} = "{val}"\n')
50 conf_file.close()
51 bb_cmd += f" -R {conf_file.name}"
52 bb_cmd += f" {recipe}"
53 poky_dir = build_dir.parent
54 full_cmd = f"bash -c 'cd {poky_dir} && source oe-init-build-env {build_dir} >/dev/null 2>&1 && {bb_cmd}'"
55 try:
56 return subprocess.run(full_cmd, shell=True, cwd=build_dir,
57 timeout=timeout, capture_output=True, text=True)
58 finally:
59 if conf_file:
60 os.unlink(conf_file.name)
61
62
63@pytest.fixture(scope="module")
64def incus_image(request):
65 """Build container-image-host with incus profile."""
66 poky_dir = Path(request.config.getoption("--poky-dir"))
67 bd = request.config.getoption("--build-dir")
68 build_dir = Path(bd) if bd else poky_dir / "build"
69 result = _run_bitbake(
70 build_dir, "container-image-host",
71 extra_vars={
72 "CONTAINER_PROFILE": "incus",
73 "DISTRO_FEATURES:append": " virtualization",
74 },
75 )
76 if result.returncode != 0:
77 pytest.fail(f"Incus image build failed: {result.stderr}")
78
79
41@pytest.fixture(scope="module") 80@pytest.fixture(scope="module")
42def incus_qemu(request): 81def incus_qemu(request, incus_image):
43 """Boot a QEMU VM with incus and return the pexpect session.""" 82 """Build incus image, boot a QEMU VM, and return the pexpect session."""
44 machine = request.config.getoption("--machine", default="qemux86-64") 83 machine = request.config.getoption("--machine", default="qemux86-64")
45 boot_timeout = int(request.config.getoption("--boot-timeout", default="120")) 84 boot_timeout = int(request.config.getoption("--boot-timeout", default="120"))
46 no_kvm = request.config.getoption("--no-kvm", default=False) 85 no_kvm = request.config.getoption("--no-kvm", default=False)
47 86
48 builddir = os.environ.get("BUILDDIR", os.path.expanduser("~/poky/build")) 87 poky_dir = Path(request.config.getoption("--poky-dir"))
88 bd = request.config.getoption("--build-dir")
89 builddir = str(Path(bd) if bd else poky_dir / "build")
49 90
50 kvm_opt = "" if no_kvm else "kvm" 91 kvm_opt = "" if no_kvm else "kvm"
51 cmd = f"runqemu {machine} nographic slirp {kvm_opt} qemuparams=\"-m 4096\"" 92 cmd = f"runqemu {machine} container-image-host ext4 nographic slirp {kvm_opt} qemuparams=\"-m 4096\""
52 93
53 child = pexpect.spawn(f"bash -c 'source {builddir}/oe-init-build-env {builddir} >/dev/null 2>&1 && {cmd}'", 94 child = pexpect.spawn(f"bash -c 'cd {poky_dir} && source oe-init-build-env {builddir} >/dev/null 2>&1 && {cmd}'",
54 timeout=boot_timeout, encoding="utf-8", logfile=None) 95 timeout=boot_timeout, encoding="utf-8", logfile=None)
55 96
56 # Wait for login prompt 97 # 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
8deployment. Multi-node tests use QEMU socket networking to connect two VMs 8deployment. Multi-node tests use QEMU socket networking to connect two VMs
9on a shared L2 segment and verify agent join + multi-node scheduling. 9on a shared L2 segment and verify agent join + multi-node scheduling.
10 10
11Build prerequisites (in local.conf): 11The tests automatically build container-image-host with CONTAINER_PROFILE=k3s-host
12 require conf/distro/include/meta-virt-host.conf 12before booting. No local.conf changes needed.
13 require conf/distro/include/container-host-k3s.conf
14 MACHINE = "qemux86-64" # or qemuarm64
15
16 bitbake container-image-host
17 13
18Run: 14Run:
19 # Single-node only 15 # Single-node only
20 pytest tests/test_k3s_runtime.py -v -k "not multinode" --machine qemux86-64 16 pytest tests/test_k3s_runtime.py -v -k "not multinode" --poky-dir /opt/bruce/poky
21 17
22 # Multi-node only 18 # Multi-node only
23 pytest tests/test_k3s_runtime.py -v -k "multinode" --machine qemux86-64 19 pytest tests/test_k3s_runtime.py -v -k "multinode" --poky-dir /opt/bruce/poky
24 20
25 # All tests 21 # All tests
26 pytest tests/test_k3s_runtime.py -v --machine qemux86-64 22 pytest tests/test_k3s_runtime.py -v --poky-dir /opt/bruce/poky
27 23
28Options: 24Options:
29 --k3s-timeout Overall k3s readiness timeout (default: 300s) 25 --k3s-timeout Overall k3s readiness timeout (default: 300s)
@@ -43,6 +39,8 @@ Notes:
43import os 39import os
44import re 40import re
45import shutil 41import shutil
42import subprocess
43import tempfile
46import time 44import time
47import pytest 45import pytest
48from pathlib import Path 46from pathlib import Path
@@ -290,6 +288,39 @@ class K3sRunner:
290# Fixtures 288# Fixtures
291# ============================================================================ 289# ============================================================================
292 290
291def run_bitbake(build_dir, recipe, extra_vars=None, timeout=3600):
292 """Run a bitbake command within the Yocto environment."""
293 import tempfile
294
295 bb_cmd = "bitbake"
296
297 conf_file = None
298 if extra_vars:
299 conf_file = tempfile.NamedTemporaryFile(
300 mode='w', suffix='.conf', prefix='pytest-k3s-',
301 dir=str(build_dir / "conf"), delete=False)
302 for var, val in extra_vars.items():
303 conf_file.write(f'{var} = "{val}"\n')
304 conf_file.close()
305 bb_cmd += f" -R {conf_file.name}"
306
307 bb_cmd += f" {recipe}"
308
309 poky_dir = build_dir.parent
310 full_cmd = f"bash -c 'cd {poky_dir} && source oe-init-build-env {build_dir} >/dev/null 2>&1 && {bb_cmd}'"
311
312 try:
313 result = subprocess.run(
314 full_cmd, shell=True, cwd=build_dir,
315 timeout=timeout, capture_output=True, text=True,
316 )
317 finally:
318 if conf_file:
319 os.unlink(conf_file.name)
320
321 return result
322
323
293@pytest.fixture(scope="module") 324@pytest.fixture(scope="module")
294def poky_dir(request): 325def poky_dir(request):
295 """Path to poky directory.""" 326 """Path to poky directory."""
@@ -325,20 +356,28 @@ def k3s_timeout(request):
325 356
326 357
327@pytest.fixture(scope="module") 358@pytest.fixture(scope="module")
328def k3s_session(request, poky_dir, build_dir, machine): 359def k3s_image(build_dir):
360 """Build container-image-host with k3s profile."""
361 result = run_bitbake(
362 build_dir, "container-image-host",
363 extra_vars={
364 "CONTAINER_PROFILE": "k3s-host",
365 "DISTRO_FEATURES:append": " k3s virtualization",
366 },
367 )
368 if result.returncode != 0:
369 pytest.fail(f"K3s image build failed: {result.stderr}")
370
371
372@pytest.fixture(scope="module")
373def k3s_session(request, poky_dir, build_dir, machine, k3s_image):
329 """ 374 """
330 Module-scoped fixture that boots container-image-host once for all 375 Module-scoped fixture that builds container-image-host with k3s profile,
331 single-node k3s tests. Uses runqemu for single-node tests. 376 boots it, and provides a session for all single-node k3s tests.
332 """ 377 """
333 if not PEXPECT_AVAILABLE: 378 if not PEXPECT_AVAILABLE:
334 pytest.skip("pexpect not installed. Run: pip install pexpect") 379 pytest.skip("pexpect not installed. Run: pip install pexpect")
335 380
336 deploy_dir = build_dir / "tmp" / "deploy" / "images" / machine
337 ext4_files = list(deploy_dir.glob("container-image-host-*.rootfs.ext4"))
338 if not ext4_files:
339 pytest.skip(
340 f"container-image-host ext4 image not found in {deploy_dir}")
341
342 timeout = request.config.getoption("--boot-timeout") 381 timeout = request.config.getoption("--boot-timeout")
343 use_kvm = not request.config.getoption("--no-kvm") 382 use_kvm = not request.config.getoption("--no-kvm")
344 383
@@ -356,10 +395,10 @@ def k3s_session(request, poky_dir, build_dir, machine):
356 395
357 396
358@pytest.fixture(scope="module") 397@pytest.fixture(scope="module")
359def k3s_multinode(request, poky_dir, build_dir, machine): 398def k3s_multinode(request, poky_dir, build_dir, machine, k3s_image):
360 """ 399 """
361 Module-scoped fixture that boots two VMs connected via QEMU socket 400 Module-scoped fixture that builds with k3s profile, then boots two VMs
362 networking for multi-node k3s testing. 401 connected via QEMU socket networking for multi-node k3s testing.
363 402
364 Uses direct QEMU launch (not runqemu) since runqemu can only run 403 Uses direct QEMU launch (not runqemu) since runqemu can only run
365 one VM at a time. Creates a copy of the rootfs for the agent VM. 404 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.
7These tests boot an actual Xen Dom0 image via runqemu, verify the 7These tests boot an actual Xen Dom0 image via runqemu, verify the
8hypervisor is functional, check guest bundling, and exercise vxn/containerd. 8hypervisor is functional, check guest bundling, and exercise vxn/containerd.
9 9
10Build prerequisites (minimum for Dom0 boot tests): 10The tests automatically build xen-image-minimal with the required
11 DISTRO_FEATURES:append = " xen systemd" 11DISTRO_FEATURES before booting. No local.conf changes needed.
12 MACHINE = "qemux86-64" # or qemuarm64
13 bitbake xen-image-minimal
14
15For guest bundling tests:
16 IMAGE_INSTALL:append:pn-xen-image-minimal = " alpine-xen-guest-bundle"
17
18For vxn/containerd tests:
19 DISTRO_FEATURES:append = " virtualization vcontainer vxn"
20 IMAGE_INSTALL:append:pn-xen-image-minimal = " vxn"
21 BBMULTICONFIG = "vruntime-aarch64 vruntime-x86-64"
22 12
23Run with: 13Run with:
24 pytest tests/test_xen_runtime.py -v --machine qemux86-64 14 pytest tests/test_xen_runtime.py -v --poky-dir /opt/bruce/poky
25 15
26Skip network-dependent tests: 16Skip network-dependent tests:
27 pytest tests/test_xen_runtime.py -v -m "boot and not network" 17 pytest tests/test_xen_runtime.py -v -m "boot and not network"
@@ -33,7 +23,10 @@ Custom paths and longer timeout:
33 --boot-timeout 180 23 --boot-timeout 180
34""" 24"""
35 25
26import os
36import re 27import re
28import subprocess
29import tempfile
37import time 30import time
38import pytest 31import pytest
39from pathlib import Path 32from pathlib import Path
@@ -50,6 +43,29 @@ except ImportError:
50# are defined in conftest.py to avoid conflicts with other test files. 43# are defined in conftest.py to avoid conflicts with other test files.
51 44
52 45
46def _run_bitbake(build_dir, recipe, extra_vars=None, timeout=3600):
47 """Run bitbake with optional variable overrides via -R conf file."""
48 bb_cmd = "bitbake"
49 conf_file = None
50 if extra_vars:
51 conf_file = tempfile.NamedTemporaryFile(
52 mode='w', suffix='.conf', prefix='pytest-xen-',
53 dir=str(build_dir / "conf"), delete=False)
54 for var, val in extra_vars.items():
55 conf_file.write(f'{var} = "{val}"\n')
56 conf_file.close()
57 bb_cmd += f" -R {conf_file.name}"
58 bb_cmd += f" {recipe}"
59 poky_dir = build_dir.parent
60 full_cmd = f"bash -c 'cd {poky_dir} && source oe-init-build-env {build_dir} >/dev/null 2>&1 && {bb_cmd}'"
61 try:
62 return subprocess.run(full_cmd, shell=True, cwd=build_dir,
63 timeout=timeout, capture_output=True, text=True)
64 finally:
65 if conf_file:
66 os.unlink(conf_file.name)
67
68
53class XenRunner: 69class XenRunner:
54 """ 70 """
55 Manages a runqemu session for Xen boot testing. 71 Manages a runqemu session for Xen boot testing.
@@ -218,11 +234,25 @@ def machine(request):
218 234
219 235
220@pytest.fixture(scope="module") 236@pytest.fixture(scope="module")
221def xen_session(request, poky_dir, build_dir, machine): 237def xen_image(build_dir):
238 """Build xen-image-minimal with required distro features."""
239 result = _run_bitbake(
240 build_dir, "xen-image-minimal",
241 extra_vars={
242 "DISTRO_FEATURES:append": " xen vxn virtualization vcontainer systemd",
243 },
244 )
245 if result.returncode != 0:
246 pytest.fail(f"Xen image build failed: {result.stderr}")
247
248
249@pytest.fixture(scope="module")
250def xen_session(request, poky_dir, build_dir, machine, xen_image):
222 """ 251 """
223 Module-scoped fixture that boots xen-image-minimal once for all tests. 252 Module-scoped fixture that builds xen-image-minimal and boots it
253 once for all tests.
224 254
225 Skips if pexpect is not available, image is not found, or boot fails. 255 Skips if pexpect is not available or boot fails.
226 """ 256 """
227 if not PEXPECT_AVAILABLE: 257 if not PEXPECT_AVAILABLE:
228 pytest.skip("pexpect not installed. Run: pip install pexpect") 258 pytest.skip("pexpect not installed. Run: pip install pexpect")