summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-12 18:31:00 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-26 01:05:01 +0000
commit0fe8c4444f3199b862a4ba52b2b62b5f9b2af85f (patch)
tree3ed29a52302003a532f674b20dd3466f4eae8da0 /tests
parent2f3dcd4e0a45d1b976d266f04912fe43c68586f5 (diff)
downloadmeta-virtualization-0fe8c4444f3199b862a4ba52b2b62b5f9b2af85f.tar.gz
xen: document guest import system and add tests
Add 3rd-party guest import section to README-xen.md covering import types, kernel modes, Alpine example, and how to add custom import handlers. Add test_xen_guest_bundle.py with 46 pytest tests covering bbclass structure, import handlers, kernel modes, license warning, Alpine recipe, and README content. Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/test_xen_guest_bundle.py361
1 files changed, 361 insertions, 0 deletions
diff --git a/tests/test_xen_guest_bundle.py b/tests/test_xen_guest_bundle.py
new file mode 100644
index 00000000..2035a5f9
--- /dev/null
+++ b/tests/test_xen_guest_bundle.py
@@ -0,0 +1,361 @@
1# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
2#
3# SPDX-License-Identifier: MIT
4"""
5Tests for xen-guest-bundle.bbclass - Xen guest bundling system.
6
7These tests verify:
8 - bbclass file structure and syntax
9 - Import handler definitions
10 - Parse-time logic (__anonymous)
11 - Alpine example recipe structure
12 - Build tests (slow, require configured build environment)
13
14Run with:
15 pytest tests/test_xen_guest_bundle.py -v
16
17Run build tests (requires configured Yocto build):
18 pytest tests/test_xen_guest_bundle.py -v -m slow --machine qemuarm64
19
20Environment variables:
21 POKY_DIR: Path to poky directory (default: /opt/bruce/poky)
22 BUILD_DIR: Path to build directory (default: $POKY_DIR/build)
23 MACHINE: Target machine (default: qemux86-64)
24"""
25
26import re
27import pytest
28from pathlib import Path
29
30
31# Note: Command line options (--poky-dir, --build-dir, --machine)
32# are defined in conftest.py
33
34
35@pytest.fixture(scope="module")
36def poky_dir(request):
37 """Path to poky directory."""
38 path = Path(request.config.getoption("--poky-dir"))
39 if not path.exists():
40 pytest.skip(f"Poky directory not found: {path}")
41 return path
42
43
44@pytest.fixture(scope="module")
45def meta_virt_dir(poky_dir):
46 """Path to meta-virtualization layer."""
47 path = poky_dir / "meta-virtualization"
48 if not path.exists():
49 pytest.skip(f"meta-virtualization not found: {path}")
50 return path
51
52
53@pytest.fixture(scope="module")
54def bbclass_content(meta_virt_dir):
55 """Content of xen-guest-bundle.bbclass."""
56 path = meta_virt_dir / "classes" / "xen-guest-bundle.bbclass"
57 if not path.exists():
58 pytest.skip(f"bbclass not found: {path}")
59 return path.read_text()
60
61
62@pytest.fixture(scope="module")
63def alpine_recipe_content(meta_virt_dir):
64 """Content of alpine-xen-guest-bundle recipe."""
65 recipes = list((meta_virt_dir / "recipes-extended" / "xen-guest-bundles").glob(
66 "alpine-xen-guest-bundle_*.bb"))
67 if not recipes:
68 pytest.skip("Alpine guest bundle recipe not found")
69 return recipes[0].read_text()
70
71
72# ============================================================================
73# bbclass structure tests
74# ============================================================================
75
76class TestXenGuestBundleClass:
77 """Test xen-guest-bundle.bbclass structure and syntax."""
78
79 def test_class_exists(self, meta_virt_dir):
80 """Test that the bbclass file exists."""
81 path = meta_virt_dir / "classes" / "xen-guest-bundle.bbclass"
82 assert path.exists(), f"bbclass not found: {path}"
83
84 def test_spdx_header(self, bbclass_content):
85 """Test SPDX license header is present."""
86 assert "SPDX-License-Identifier: MIT" in bbclass_content
87
88 def test_default_variables(self, bbclass_content):
89 """Test that expected default variables are defined."""
90 defaults = [
91 "XEN_GUEST_BUNDLES",
92 "XEN_GUEST_IMAGE_FSTYPE",
93 "XEN_GUEST_MEMORY_DEFAULT",
94 "XEN_GUEST_VCPUS_DEFAULT",
95 "XEN_GUEST_VIF_DEFAULT",
96 "XEN_GUEST_EXTRA_DEFAULT",
97 "XEN_GUEST_DISK_DEVICE_DEFAULT",
98 ]
99 for var in defaults:
100 assert var in bbclass_content, f"Default variable {var} not found"
101
102 def test_anonymous_function(self, bbclass_content):
103 """Test that __anonymous() is defined."""
104 assert "python __anonymous()" in bbclass_content
105
106 def test_do_compile_defined(self, bbclass_content):
107 """Test that do_compile is defined."""
108 assert "do_compile()" in bbclass_content
109
110 def test_do_install_defined(self, bbclass_content):
111 """Test that do_install is defined."""
112 assert "do_install()" in bbclass_content
113
114 def test_resolve_bundle_rootfs(self, bbclass_content):
115 """Test rootfs resolver function exists."""
116 assert "resolve_bundle_rootfs()" in bbclass_content
117
118 def test_resolve_bundle_kernel(self, bbclass_content):
119 """Test kernel resolver function exists."""
120 assert "resolve_bundle_kernel()" in bbclass_content
121
122 def test_generate_bundle_config(self, bbclass_content):
123 """Test config generator function exists."""
124 assert "generate_bundle_config()" in bbclass_content
125
126 def test_files_variable(self, bbclass_content):
127 """Test FILES variable is set."""
128 assert "FILES:${PN}" in bbclass_content
129 assert "xen-guest-bundles" in bbclass_content
130
131 def test_insane_skip(self, bbclass_content):
132 """Test INSANE_SKIP for binary images."""
133 assert "INSANE_SKIP" in bbclass_content
134 assert "buildpaths" in bbclass_content
135
136
137# ============================================================================
138# Import system tests
139# ============================================================================
140
141class TestImportSystem:
142 """Test import system for 3rd-party guests."""
143
144 def test_import_default_variables(self, bbclass_content):
145 """Test import-related default variables."""
146 assert "XEN_GUEST_IMAGE_SIZE_DEFAULT" in bbclass_content
147 assert "XEN_GUEST_IMPORT_DEPENDS_rootfs_dir" in bbclass_content
148 assert "XEN_GUEST_IMPORT_DEPENDS_qcow2" in bbclass_content
149 assert "XEN_GUEST_IMPORT_DEPENDS_ext4" in bbclass_content
150 assert "XEN_GUEST_IMPORT_DEPENDS_raw" in bbclass_content
151
152 def test_import_depends_rootfs_dir(self, bbclass_content):
153 """Test rootfs_dir depends on e2fsprogs-native."""
154 match = re.search(
155 r'XEN_GUEST_IMPORT_DEPENDS_rootfs_dir\s*=\s*"([^"]*)"',
156 bbclass_content)
157 assert match, "rootfs_dir depends not found"
158 assert "e2fsprogs-native" in match.group(1)
159
160 def test_import_depends_qcow2(self, bbclass_content):
161 """Test qcow2 depends on qemu-system-native."""
162 match = re.search(
163 r'XEN_GUEST_IMPORT_DEPENDS_qcow2\s*=\s*"([^"]*)"',
164 bbclass_content)
165 assert match, "qcow2 depends not found"
166 assert "qemu-system-native" in match.group(1)
167
168 def test_import_handler_rootfs_dir(self, bbclass_content):
169 """Test rootfs_dir import handler exists."""
170 assert "xen_guest_import_rootfs_dir()" in bbclass_content
171 assert "mkfs.ext4" in bbclass_content
172
173 def test_import_handler_qcow2(self, bbclass_content):
174 """Test qcow2 import handler exists."""
175 assert "xen_guest_import_qcow2()" in bbclass_content
176 assert "qemu-img convert" in bbclass_content
177
178 def test_import_handler_ext4(self, bbclass_content):
179 """Test ext4 import handler exists."""
180 assert "xen_guest_import_ext4()" in bbclass_content
181
182 def test_import_handler_raw(self, bbclass_content):
183 """Test raw import handler exists."""
184 assert "xen_guest_import_raw()" in bbclass_content
185
186 def test_resolve_import_source(self, bbclass_content):
187 """Test import source resolver exists."""
188 assert "resolve_import_source()" in bbclass_content
189 assert "_XEN_GUEST_IMPORT_MAP" in bbclass_content
190
191 def test_static_dispatch_in_do_compile(self, bbclass_content):
192 """Test that import dispatch uses static case statement."""
193 # BitBake needs static function references to include them
194 assert "case \"$import_type\" in" in bbclass_content
195 assert "xen_guest_import_rootfs_dir " in bbclass_content
196 assert "xen_guest_import_qcow2 " in bbclass_content
197
198 def test_fakeroot_for_rootfs_dir(self, bbclass_content):
199 """Test that rootfs_dir type triggers fakeroot."""
200 assert "fakeroot" in bbclass_content
201 assert "rootfs_dir" in bbclass_content
202
203
204# ============================================================================
205# Kernel mode tests
206# ============================================================================
207
208class TestKernelModes:
209 """Test three kernel modes: shared, custom, HVM/none."""
210
211 def test_hvm_mode_documented(self, bbclass_content):
212 """Test HVM mode (kernel=none) is supported."""
213 assert '"none"' in bbclass_content or "'none'" in bbclass_content
214 assert "HVM" in bbclass_content
215
216 def test_kernel_unpackdir_check(self, bbclass_content):
217 """Test kernel resolver checks UNPACKDIR."""
218 assert "UNPACKDIR" in bbclass_content
219
220 def test_config_omits_kernel_for_hvm(self, bbclass_content):
221 """Test generate_bundle_config omits kernel for HVM."""
222 # Should have conditional kernel output
223 assert 'if [ -n "$kernel_basename" ]' in bbclass_content
224
225 def test_shared_kernel_dependency(self, bbclass_content):
226 """Test virtual/kernel dependency for shared kernel."""
227 assert "virtual/kernel:do_deploy" in bbclass_content
228
229
230# ============================================================================
231# License warning tests
232# ============================================================================
233
234class TestLicenseWarning:
235 """Test external guest license warning."""
236
237 def test_external_names_variable(self, bbclass_content):
238 """Test _XEN_GUEST_EXTERNAL_NAMES is set for external guests."""
239 assert "_XEN_GUEST_EXTERNAL_NAMES" in bbclass_content
240
241 def test_license_warn_prefunc(self, bbclass_content):
242 """Test license warning is a prefunc on do_compile."""
243 assert "xen_guest_external_license_warn" in bbclass_content
244 assert "do_compile[prefuncs]" in bbclass_content
245
246 def test_license_warn_content(self, bbclass_content):
247 """Test license warning message content."""
248 assert "rights to redistribute" in bbclass_content
249 assert "license terms" in bbclass_content
250
251 def test_license_warn_is_python(self, bbclass_content):
252 """Test license warning is a python function (runs once at task time)."""
253 assert "python xen_guest_external_license_warn()" in bbclass_content
254
255
256# ============================================================================
257# Alpine recipe tests
258# ============================================================================
259
260class TestAlpineRecipe:
261 """Test alpine-xen-guest-bundle recipe structure."""
262
263 def test_recipe_exists(self, meta_virt_dir):
264 """Test that Alpine recipe exists."""
265 recipes = list((meta_virt_dir / "recipes-extended" / "xen-guest-bundles").glob(
266 "alpine-xen-guest-bundle_*.bb"))
267 assert len(recipes) > 0, "Alpine guest bundle recipe not found"
268
269 def test_inherits_xen_guest_bundle(self, alpine_recipe_content):
270 """Test recipe inherits xen-guest-bundle."""
271 assert "inherit xen-guest-bundle" in alpine_recipe_content
272
273 def test_license(self, alpine_recipe_content):
274 """Test recipe has license."""
275 assert 'LICENSE = "MIT"' in alpine_recipe_content
276 assert "LIC_FILES_CHKSUM" in alpine_recipe_content
277
278 def test_src_uri(self, alpine_recipe_content):
279 """Test SRC_URI fetches Alpine minirootfs."""
280 assert "dl-cdn.alpinelinux.org" in alpine_recipe_content
281 assert "alpine-minirootfs" in alpine_recipe_content
282 assert "subdir=alpine-rootfs" in alpine_recipe_content
283
284 def test_sha256sum(self, alpine_recipe_content):
285 """Test sha256sum is set (not placeholder)."""
286 match = re.search(r'SRC_URI\[sha256sum\]\s*=\s*"([^"]*)"',
287 alpine_recipe_content)
288 assert match, "sha256sum not found"
289 sha = match.group(1)
290 assert len(sha) == 64, f"sha256sum wrong length: {len(sha)}"
291 assert sha != "x" * 64, "sha256sum is still placeholder"
292
293 def test_guest_bundles(self, alpine_recipe_content):
294 """Test XEN_GUEST_BUNDLES is set."""
295 assert 'XEN_GUEST_BUNDLES = "alpine:autostart:external"' in alpine_recipe_content
296
297 def test_import_source_type(self, alpine_recipe_content):
298 """Test import source type is rootfs_dir."""
299 assert 'XEN_GUEST_SOURCE_TYPE[alpine] = "rootfs_dir"' in alpine_recipe_content
300
301 def test_import_source_file(self, alpine_recipe_content):
302 """Test import source file matches SRC_URI subdir."""
303 assert 'XEN_GUEST_SOURCE_FILE[alpine] = "alpine-rootfs"' in alpine_recipe_content
304
305 def test_image_size(self, alpine_recipe_content):
306 """Test image size is set."""
307 assert 'XEN_GUEST_IMAGE_SIZE[alpine]' in alpine_recipe_content
308
309 def test_guest_memory(self, alpine_recipe_content):
310 """Test guest memory is set."""
311 assert 'XEN_GUEST_MEMORY[alpine]' in alpine_recipe_content
312
313 def test_guest_extra(self, alpine_recipe_content):
314 """Test guest extra args include console."""
315 assert 'XEN_GUEST_EXTRA[alpine]' in alpine_recipe_content
316 assert "console=hvc0" in alpine_recipe_content
317
318 def test_multiarch_support(self, alpine_recipe_content):
319 """Test recipe supports multiple architectures."""
320 assert "ALPINE_ARCH" in alpine_recipe_content
321 assert "aarch64" in alpine_recipe_content
322 assert "x86_64" in alpine_recipe_content
323
324
325# ============================================================================
326# README tests
327# ============================================================================
328
329class TestReadme:
330 """Test README-xen.md documentation."""
331
332 @pytest.fixture(scope="class")
333 def readme_content(self, meta_virt_dir):
334 path = meta_virt_dir / "recipes-extended" / "images" / "README-xen.md"
335 if not path.exists():
336 pytest.skip("README-xen.md not found")
337 return path.read_text()
338
339 def test_import_section_exists(self, readme_content):
340 """Test 3rd-party import section exists."""
341 assert "3rd-party guest import" in readme_content
342
343 def test_import_types_documented(self, readme_content):
344 """Test import types are documented."""
345 assert "rootfs_dir" in readme_content
346 assert "qcow2" in readme_content
347
348 def test_kernel_modes_documented(self, readme_content):
349 """Test kernel modes are documented."""
350 assert "none" in readme_content
351 assert "Shared host kernel" in readme_content or "shared" in readme_content.lower()
352
353 def test_alpine_example(self, readme_content):
354 """Test Alpine example is in README."""
355 assert "alpine" in readme_content.lower()
356 assert "XEN_GUEST_SOURCE_TYPE" in readme_content
357
358 def test_custom_handler_docs(self, readme_content):
359 """Test custom handler instructions."""
360 assert "xen_guest_import_" in readme_content
361 assert "XEN_GUEST_IMPORT_DEPENDS_" in readme_content