summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--recipes-extended/images/README-xen.md224
-rw-r--r--tests/test_xen_guest_bundle.py361
2 files changed, 490 insertions, 95 deletions
diff --git a/recipes-extended/images/README-xen.md b/recipes-extended/images/README-xen.md
index 82d72364..f16aa743 100644
--- a/recipes-extended/images/README-xen.md
+++ b/recipes-extended/images/README-xen.md
@@ -20,154 +20,188 @@ to differentiate).
20 20
21It creates tarballs, ext4 and qcow images for testing purposes. 21It creates tarballs, ext4 and qcow images for testing purposes.
22 22
23bundling 23Bundling
24-------- 24--------
25 25
26Guests can be bundled automatically through the following mechanisms: 26There are two ways to bundle Xen guests into a Dom0 host image:
27 27
28 - via the variable XEN_BUNDLED_GUESTS 28| Use Case | `BUNDLED_XEN_GUESTS` | Bundle Recipe |
29 - via a xen configuration file in the deploy directory of the format 29|---|---|---|
30 xen-guest-bundle-*.cfg 30| Simple: guests in one host image | recommended | overkill |
31| Reuse guests across multiple host images | repetitive | recommended |
32| Package versioning and dependencies | not supported | supported |
33| Distribute pre-built guest sets | not supported | supported |
31 34
32The guests can be built via OE, or be 3rd party guests. They just 35### Variable-driven (BUNDLED_XEN_GUESTS)
33must be in the deploy directory so they can be copied into the rootfs
34of the xen host image
35 36
36Type 1) XEN_BUNDLED_GUESTS 37Guests can be bundled into the host image automatically using
38`xen-guest-cross-install.bbclass` (inherited by xen-image-minimal).
37 39
38If XEN_BUNDLED_GUESTS is used, it is simply a colon separated list of 40Set `BUNDLED_XEN_GUESTS` in local.conf or the image recipe:
39rootfs:kernels. Normal variable rules apply, so it can be set in a
40local.conf, or in a bbappend to the image recipe.
41 41
42An example would be: 42 BUNDLED_XEN_GUESTS = "xen-guest-image-minimal:autostart"
43 43
44 XEN_BUNDLED_GUESTS = "xen-guest-image-minimal-qemuarm64.rootfs.ext4:Image" 44Each entry is a recipe name with optional tags:
45 45
46These point at symlinks created in the image deploy directory, or they 46 recipe-name[:autostart][:external]
47can be specific images/kernels without the symlink.
48 47
49Type 2) A Xen guest configuration file 48 - recipe-name: Yocto image recipe that produces the guest rootfs
49 - autostart: Creates symlink in /etc/xen/auto/ for xendomains
50 - external: Skip dependency generation (3rd-party guest)
50 51
51If xen guest configuration files are found in the deploy directories 52Examples:
52the kernel and disk information contained within them will be processed
53and modified for the xen host. The kernel and guest image will be
54copied to the appropriate location, and the config made to match.
55 53
56These files following the naming convention: xen-guest-bundle*.cfg 54 # Single guest with autostart (default recommendation)
55 BUNDLED_XEN_GUESTS = "xen-guest-image-minimal:autostart"
57 56
58Guests of type #1 generate a configuration file that is picked up as 57 # Guest without autostart
59type #2. 58 BUNDLED_XEN_GUESTS = "xen-guest-image-minimal"
60 59
61An example config file follows: 60 # External/3rd-party guest (no build dependency)
61 BUNDLED_XEN_GUESTS = "my-vendor-guest:external"
62 62
63 name = "xen-guest" 63Per-guest configuration via varflags:
64 memory = 512
65 vcpus = 1
66 disk = ['file:xen-guest-image-minimal-qemuarm64.rootfs.ext4,xvda,rw']
67 vif = ['bridge=xenbr0']
68 kernel = "Image"
69 extra = "root=/dev/xvda ro console=hvc0 ip=dhcp"
70 64
71It should also be noted that when a xen-guest-image-minimal is built 65 XEN_GUEST_MEMORY[xen-guest-image-minimal] = "1024"
72with the XEN_GUEST_AUTO_BUNDLE varaible set to True, a configuration 66 XEN_GUEST_VCPUS[xen-guest-image-minimal] = "2"
73file for type #2 will be generated and the guest bundled automatically 67 XEN_GUEST_VIF[xen-guest-image-minimal] = "bridge=xenbr0"
74when the host image is built. 68 XEN_GUEST_EXTRA[xen-guest-image-minimal] = "root=/dev/xvda ro console=hvc0 ip=dhcp"
69
70Custom config file (replaces auto-generation):
71
72 SRC_URI += "file://my-custom-guest.cfg"
73 BUNDLED_XEN_GUESTS = "xen-guest-image-minimal:autostart"
74 XEN_GUEST_CONFIG_FILE[xen-guest-image-minimal] = "${UNPACKDIR}/my-custom-guest.cfg"
75
76Explicit rootfs/kernel for external guests:
77
78 XEN_GUEST_ROOTFS[my-vendor-guest] = "vendor-rootfs.ext4"
79 XEN_GUEST_KERNEL[my-vendor-guest] = "vendor-kernel"
80
81### Package-based (xen-guest-bundle.bbclass)
82
83For reusable guest sets, create a bundle recipe that inherits
84`xen-guest-bundle`:
85
86 # recipes-extended/xen-guest-bundles/my-guests_1.0.bb
87 inherit xen-guest-bundle
88
89 XEN_GUEST_BUNDLES = "xen-guest-image-minimal:autostart"
90 XEN_GUEST_MEMORY[xen-guest-image-minimal] = "1024"
91
92Then install the bundle in the host image:
93
94 IMAGE_INSTALL:append:pn-xen-image-minimal = " my-guests"
95
96The bundle package includes rootfs, kernel, and config files. At
97image time, `merge_installed_xen_bundles()` deploys them to the
98same target locations as the variable-driven path.
99
100Custom config files work the same way via SRC_URI + varflag:
101
102 SRC_URI += "file://my-custom-guest.cfg"
103 XEN_GUEST_CONFIG_FILE[xen-guest-image-minimal] = "${UNPACKDIR}/my-custom-guest.cfg"
104
105See `example-xen-guest-bundle_1.0.bb` for a complete example.
106
107### 3rd-party guest import
108
109The import system converts fetched source formats (tarballs, qcow2 images,
110etc.) into Xen-ready disk images at build time. This is for guests that
111are not built by Yocto (e.g., Alpine minirootfs, Debian cloud images).
112
113Per-guest varflags control the import:
114
115 XEN_GUEST_SOURCE_TYPE[guest] = "rootfs_dir" # import handler type
116 XEN_GUEST_SOURCE_FILE[guest] = "alpine-rootfs" # file/dir in UNPACKDIR
117 XEN_GUEST_IMAGE_SIZE[guest] = "128" # target image size in MB
118
119Built-in import types:
120
121| Type | Input | Output | Tool |
122|---|---|---|---|
123| `rootfs_dir` | Extracted rootfs directory | ext4 image | `mkfs.ext4 -F -d` |
124| `qcow2` | QCOW2 disk image | raw image | `qemu-img convert` |
125| `ext4` | ext4 image file | ext4 (copy) | `cp` |
126| `raw` | raw disk image | raw (copy) | `cp` |
127
128Native tool dependencies are resolved automatically at parse time.
129
130Kernel modes (per-guest via `XEN_GUEST_KERNEL` varflag):
131
132 - (not set): Shared host kernel from DEPLOY_DIR_IMAGE
133 - `"path"`: Custom kernel from UNPACKDIR or DEPLOY_DIR_IMAGE
134 - `"none"`: HVM guest, no kernel (omits kernel= from config)
135
136Alpine example (`alpine-xen-guest-bundle_3.23.bb`):
137
138 inherit xen-guest-bundle
139
140 SRC_URI = "https://...alpine-minirootfs-${ALPINE_VERSION}-${ALPINE_ARCH}.tar.gz;subdir=alpine-rootfs"
141
142 XEN_GUEST_BUNDLES = "alpine:autostart:external"
143 XEN_GUEST_SOURCE_TYPE[alpine] = "rootfs_dir"
144 XEN_GUEST_SOURCE_FILE[alpine] = "alpine-rootfs"
145 XEN_GUEST_IMAGE_SIZE[alpine] = "128"
146 XEN_GUEST_MEMORY[alpine] = "256"
147 XEN_GUEST_EXTRA[alpine] = "root=/dev/xvda ro console=hvc0"
148
149Adding custom import types: define a shell function
150`xen_guest_import_<type>(source_path, output_path, size_mb)` in a
151bbclass, recipe, or bbappend and set the corresponding
152`XEN_GUEST_IMPORT_DEPENDS_<type>` variable for native tool dependencies.
153
154Target layout
155-------------
75 156
76kernel and rootfs are copied to the target in /var/lib/xen/images/ 157kernel and rootfs are copied to the target in /var/lib/xen/images/
77 158
78configuration files are copied to: /etc/xen 159configuration files are copied to: /etc/xen
79 160
80Guests can be launched after boot with: xl create -c /etc/xen/<config file> 161autostart symlinks are created in: /etc/xen/auto/
162
163Guests can be launched after boot with: xl create -c /etc/xen/<guest>.cfg
81 164
82Build and boot 165Build and boot
83-------------- 166--------------
84 167
85Using a reference qmeuarm64 MACHINE, the following are the commands 168Using a reference qemuarm64 MACHINE, the following are the commands
86to build and boot a guest. 169to build and boot a guest.
87 170
88local.conf contains: 171local.conf contains:
89 172
90 XEN_BUNDLED_GUESTS = "xen-guest-image-minimal-qemuarm64.rootfs.ext4:Image" 173 BUNDLED_XEN_GUESTS = "xen-guest-image-minimal:autostart"
91 174
92 % bitbake xen-guest-image-minimal 175 % bitbake xen-guest-image-minimal
93 % bitbake xen-image-minimal 176 % bitbake xen-image-minimal
94 177
95 % runqemu qemuarm64 nographic slirp qemuparams="-m 4096" tmp/deploy/images/qemuarm64/xen-image-minimal-qemuarm64.rootfs.ext4 178 % runqemu qemuarm64 nographic slirp qemuparams="-m 4096"
96 179
97Poky (Yocto Project Reference Distro) 5.1 qemuarm64 hvc0 180Poky (Yocto Project Reference Distro) 5.1 qemuarm64 hvc0
98 181
99qemuarm64 login: root 182qemuarm64 login: root
100 183
101WARNING: Poky is a reference Yocto Project distribution that should be used for
102testing and development purposes only. It is recommended that you create your
103own distribution for production use.
104
105 root@qemuarm64:~# uname -a
106Linux qemuarm64 6.10.11-yocto-standard #1 SMP PREEMPT Fri Sep 20 22:32:26 UTC 2024 aarch64 GNU/Linux
107root@qemuarm64:~# ls /etc/xen/ 184root@qemuarm64:~# ls /etc/xen/
108auto 185auto
109cpupool 186cpupool
110scripts 187scripts
111xen-guest-bundle-xen-guest-image-minimal-qemuarm64--20241112174803.cfg 188xen-guest-image-minimal.cfg
112xl.conf 189xl.conf
113root@qemuarm64:~# ls /var/lib/xen/images/ 190root@qemuarm64:~# ls /var/lib/xen/images/
114Image--6.10.11+git0+4bf82718cf_6c956b2ea6-r0-qemuarm64-20241018190311.bin 191Image--6.10.11+git0+4bf82718cf_6c956b2ea6-r0-qemuarm64-20241018190311.bin
115xen-guest-image-minimal-qemuarm64.rootfs-20241111222814.ext4 192xen-guest-image-minimal-qemuarm64-20241111222814.ext4
116
117 root@qemuarm64:~# ip a s
1181: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
119 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
120 inet 127.0.0.1/8 scope host lo
121 valid_lft forever preferred_lft forever
122 inet6 ::1/128 scope host noprefixroute
123 valid_lft forever preferred_lft forever
1242: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master xenbr0 qlen 1000
125 link/ether 52:54:00:12:35:02 brd ff:ff:ff:ff:ff:ff
1263: sit0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000
127 link/sit 0.0.0.0 brd 0.0.0.0
1284: xenbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue qlen 1000
129 link/ether ee:e4:a8:24:24:e7 brd ff:ff:ff:ff:ff:ff
130 inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic xenbr0
131 valid_lft 86354sec preferred_lft 86354sec
132 inet6 fec0::ece4:a8ff:fe24:24e7/64 scope site dynamic noprefixroute flags 100
133 valid_lft 86356sec preferred_lft 14356sec
134 inet6 fe80::ece4:a8ff:fe24:24e7/64 scope link
135 valid_lft forever preferred_lft forever
136
137 root@qemuarm64:~# xl create -c /etc/xen/xen-guest-bundle-xen-guest-image-minimal-qemuarm64--20241112174803.cfg
138 193
139qemuarm64 login: root 194 root@qemuarm64:~# xl create -c /etc/xen/xen-guest-image-minimal.cfg
140 195
141WARNING: Poky is a reference Yocto Project distribution that should be used for 196qemuarm64 login: root
142testing and development purposes only. It is recommended that you create your
143own distribution for production use.
144 197
145root@qemuarm64:~# uname -a 198root@qemuarm64:~# uname -a
146Linux qemuarm64 6.10.11-yocto-standard #1 SMP PREEMPT Fri Sep 20 22:32:26 UTC 2024 aarch64 GNU/Linux 199Linux qemuarm64 6.10.11-yocto-standard #1 SMP PREEMPT Fri Sep 20 22:32:26 UTC 2024 aarch64 GNU/Linux
147 200
148root@qemuarm64:~# wget example.com
149Connecting to example.com (93.184.215.14:80)
150wget: can't open 'index.html': File exists
151root@qemuarm64:~# rm index.html
152root@qemuarm64:~# wget example.com
153Connecting to example.com (93.184.215.14:80)
154saving to 'index.html'
155index.html 100% |********************************| 1256 0:00:00 ETA
156'index.html' saved
157
158From the host: 201From the host:
159 202
160Connection to 127.0.0.1 closed.
161build4 [/home/bruc.../qemuarm64]> ssh -p 2222 root@127.0.0.1
162Last login: Tue Nov 12 20:42:57 2024 from 10.0.2.2
163
164WARNING: Poky is a reference Yocto Project distribution that should be used for
165testing and development purposes only. It is recommended that you create your
166own distribution for production use.
167
168root@qemuarm64:~# xl list 203root@qemuarm64:~# xl list
169Name ID Mem VCPUs State Time(s) 204Name ID Mem VCPUs State Time(s)
170Domain-0 0 192 4 r----- 696.2 205Domain-0 0 192 4 r----- 696.2
171xen-guest 1 512 1 -b---- 153.0 206xen-guest-image-minimal 1 512 1 -b---- 153.0
172root@qemuarm64:~# xl destroy xen-guest 207root@qemuarm64:~# xl destroy xen-guest-image-minimal
173
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