summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-04-09 03:35:47 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-04-09 03:35:47 +0000
commit68f0c8faf2e2b9024ee24ef97df1895bb117629c (patch)
treebc018aa36ce25df9c2e7b16b4e6330899046a4f3
parenta05c578640c1bef9b8704ffb060815e0f946d705 (diff)
downloadmeta-virtualization-68f0c8faf2e2b9024ee24ef97df1895bb117629c.tar.gz
incus: add runtime test suite
pexpect-based tests covering: - Daemon startup via systemd - incus-admin group creation - incus admin init --minimal - Alpine container launch, exec, stop, delete Run: pytest tests/test_incus_runtime.py -v --machine qemux86-64 Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
-rw-r--r--tests/pytest.ini2
-rw-r--r--tests/test_incus_runtime.py156
2 files changed, 158 insertions, 0 deletions
diff --git a/tests/pytest.ini b/tests/pytest.ini
index b0a51f7e..6d756a28 100644
--- a/tests/pytest.ini
+++ b/tests/pytest.ini
@@ -10,6 +10,8 @@ markers =
10 memres: marks tests that require memory resident mode 10 memres: marks tests that require memory resident mode
11 network: marks tests that require network access 11 network: marks tests that require network access
12 boot: marks tests that boot a full QEMU image (requires pexpect) 12 boot: marks tests that boot a full QEMU image (requires pexpect)
13 incus: marks incus runtime tests
14 k3s: marks k3s runtime tests
13 15
14# Default options - include junit xml for CI and detailed output 16# Default options - include junit xml for CI and detailed output
15addopts = -v --tb=short --junitxml=/tmp/pytest-results.xml 17addopts = -v --tb=short --junitxml=/tmp/pytest-results.xml
diff --git a/tests/test_incus_runtime.py b/tests/test_incus_runtime.py
new file mode 100644
index 00000000..6de4bd5c
--- /dev/null
+++ b/tests/test_incus_runtime.py
@@ -0,0 +1,156 @@
1# SPDX-FileCopyrightText: Copyright (C) 2026 Bruce Ashfield
2#
3# SPDX-License-Identifier: MIT
4"""
5Incus runtime tests - boot container-image-host with incus and verify
6system container management.
7
8Build prerequisites (in local.conf):
9 require conf/distro/include/meta-virt-host.conf
10 require conf/distro/include/container-host-incus.conf
11 MACHINE = "qemux86-64" # or qemuarm64
12
13 bitbake container-image-host
14
15Run:
16 pytest tests/test_incus_runtime.py -v --machine qemux86-64
17
18Options:
19 --boot-timeout QEMU boot timeout (default: 120s)
20 --no-kvm Disable KVM acceleration
21"""
22
23import os
24import re
25import time
26import pytest
27
28try:
29 import pexpect
30 PEXPECT_AVAILABLE = True
31except ImportError:
32 PEXPECT_AVAILABLE = False
33
34
35pytestmark = [
36 pytest.mark.skipif(not PEXPECT_AVAILABLE, reason="pexpect not installed"),
37 pytest.mark.incus,
38]
39
40
41@pytest.fixture(scope="module")
42def incus_qemu(request):
43 """Boot a QEMU VM with incus and return the pexpect session."""
44 machine = request.config.getoption("--machine", default="qemux86-64")
45 boot_timeout = int(request.config.getoption("--boot-timeout", default="120"))
46 no_kvm = request.config.getoption("--no-kvm", default=False)
47
48 builddir = os.environ.get("BUILDDIR", os.path.expanduser("~/poky/build"))
49
50 kvm_opt = "" if no_kvm else "kvm"
51 cmd = f"runqemu {machine} nographic slirp {kvm_opt} qemuparams=\"-m 4096\""
52
53 child = pexpect.spawn(f"bash -c 'source {builddir}/oe-init-build-env {builddir} >/dev/null 2>&1 && {cmd}'",
54 timeout=boot_timeout, encoding="utf-8", logfile=None)
55
56 # Wait for login prompt
57 child.expect(r"login:", timeout=boot_timeout)
58 child.sendline("root")
59 child.expect(r"root@.*[:~#]", timeout=30)
60
61 # Suppress shell integration escape sequences
62 child.sendline("export TERM=dumb")
63 child.expect(r"root@.*[:~#]", timeout=10)
64
65 yield child
66
67 # Cleanup
68 child.sendline("poweroff")
69 try:
70 child.expect(pexpect.EOF, timeout=30)
71 except pexpect.TIMEOUT:
72 child.terminate(force=True)
73
74
75def run_cmd(child, cmd, timeout=60):
76 """Run a command and return the output."""
77 marker = f"__MARKER_{time.monotonic_ns()}__"
78 child.sendline(f"{cmd}; echo {marker} $?")
79 child.expect(marker + r" (\d+)", timeout=timeout)
80 output = child.before.strip()
81 rc = int(child.match.group(1))
82 # consume prompt
83 child.expect(r"root@.*[:~#]", timeout=10)
84 return output, rc
85
86
87class TestIncusDaemon:
88 """Test that incusd starts and is functional."""
89
90 def test_incusd_running(self, incus_qemu):
91 """incusd should be running via systemd."""
92 output, rc = run_cmd(incus_qemu, "systemctl is-active incus.service")
93 assert "active" in output, f"incus.service not active: {output}"
94
95 def test_incus_admin_group(self, incus_qemu):
96 """incus-admin group should exist."""
97 output, rc = run_cmd(incus_qemu, "getent group incus-admin")
98 assert rc == 0, "incus-admin group not found"
99
100 def test_incus_version(self, incus_qemu):
101 """incus client should report a version."""
102 output, rc = run_cmd(incus_qemu, "incus version")
103 assert rc == 0, f"incus version failed: {output}"
104
105
106class TestIncusInit:
107 """Test incus initialization."""
108
109 def test_incus_init_minimal(self, incus_qemu):
110 """incus admin init --minimal should succeed."""
111 output, rc = run_cmd(incus_qemu, "incus admin init --minimal", timeout=120)
112 assert rc == 0, f"incus admin init --minimal failed: {output}"
113
114 def test_incus_network_created(self, incus_qemu):
115 """Default network bridge should exist after init."""
116 output, rc = run_cmd(incus_qemu, "incus network list")
117 assert rc == 0, f"incus network list failed: {output}"
118
119
120class TestIncusContainer:
121 """Test launching and managing a container."""
122
123 def test_launch_alpine(self, incus_qemu):
124 """Launch an Alpine container from the images: remote."""
125 output, rc = run_cmd(incus_qemu, "incus launch images:alpine/edge incus-test1",
126 timeout=180)
127 assert rc == 0, f"incus launch failed: {output}"
128
129 def test_container_running(self, incus_qemu):
130 """The launched container should be in RUNNING state."""
131 output, rc = run_cmd(incus_qemu, "incus list --format csv -c n,s")
132 assert rc == 0
133 assert "incus-test1,RUNNING" in output.replace(" ", ""), \
134 f"Container not running: {output}"
135
136 def test_exec_in_container(self, incus_qemu):
137 """Execute a command inside the container."""
138 output, rc = run_cmd(incus_qemu, "incus exec incus-test1 -- cat /etc/os-release")
139 assert rc == 0
140 assert "Alpine" in output, f"Unexpected os-release: {output}"
141
142 def test_stop_container(self, incus_qemu):
143 """Stop the container."""
144 output, rc = run_cmd(incus_qemu, "incus stop incus-test1", timeout=30)
145 assert rc == 0, f"incus stop failed: {output}"
146
147 def test_delete_container(self, incus_qemu):
148 """Delete the stopped container."""
149 output, rc = run_cmd(incus_qemu, "incus delete incus-test1", timeout=15)
150 assert rc == 0, f"incus delete failed: {output}"
151
152 def test_no_containers_remain(self, incus_qemu):
153 """No containers should remain after cleanup."""
154 output, rc = run_cmd(incus_qemu, "incus list --format csv")
155 assert rc == 0
156 assert "incus-test1" not in output