summaryrefslogtreecommitdiffstats
path: root/tests/test_incus_runtime.py
blob: 6de4bd5c8194c1d1158284f70e45da89ba8ca01e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# SPDX-FileCopyrightText: Copyright (C) 2026 Bruce Ashfield
#
# SPDX-License-Identifier: MIT
"""
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

Run:
    pytest tests/test_incus_runtime.py -v --machine qemux86-64

Options:
    --boot-timeout      QEMU boot timeout (default: 120s)
    --no-kvm            Disable KVM acceleration
"""

import os
import re
import time
import pytest

try:
    import pexpect
    PEXPECT_AVAILABLE = True
except ImportError:
    PEXPECT_AVAILABLE = False


pytestmark = [
    pytest.mark.skipif(not PEXPECT_AVAILABLE, reason="pexpect not installed"),
    pytest.mark.incus,
]


@pytest.fixture(scope="module")
def incus_qemu(request):
    """Boot a QEMU VM with incus 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"))

    kvm_opt = "" if no_kvm else "kvm"
    cmd = f"runqemu {machine} 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}'",
                          timeout=boot_timeout, encoding="utf-8", logfile=None)

    # Wait for login prompt
    child.expect(r"login:", timeout=boot_timeout)
    child.sendline("root")
    child.expect(r"root@.*[:~#]", timeout=30)

    # Suppress shell integration escape sequences
    child.sendline("export TERM=dumb")
    child.expect(r"root@.*[:~#]", timeout=10)

    yield child

    # Cleanup
    child.sendline("poweroff")
    try:
        child.expect(pexpect.EOF, timeout=30)
    except pexpect.TIMEOUT:
        child.terminate(force=True)


def run_cmd(child, cmd, timeout=60):
    """Run a command and return the output."""
    marker = f"__MARKER_{time.monotonic_ns()}__"
    child.sendline(f"{cmd}; echo {marker} $?")
    child.expect(marker + r" (\d+)", timeout=timeout)
    output = child.before.strip()
    rc = int(child.match.group(1))
    # consume prompt
    child.expect(r"root@.*[:~#]", timeout=10)
    return output, rc


class TestIncusDaemon:
    """Test that incusd starts and is functional."""

    def test_incusd_running(self, incus_qemu):
        """incusd should be running via systemd."""
        output, rc = run_cmd(incus_qemu, "systemctl is-active incus.service")
        assert "active" in output, f"incus.service not active: {output}"

    def test_incus_admin_group(self, incus_qemu):
        """incus-admin group should exist."""
        output, rc = run_cmd(incus_qemu, "getent group incus-admin")
        assert rc == 0, "incus-admin group not found"

    def test_incus_version(self, incus_qemu):
        """incus client should report a version."""
        output, rc = run_cmd(incus_qemu, "incus version")
        assert rc == 0, f"incus version failed: {output}"


class TestIncusInit:
    """Test incus initialization."""

    def test_incus_init_minimal(self, incus_qemu):
        """incus admin init --minimal should succeed."""
        output, rc = run_cmd(incus_qemu, "incus admin init --minimal", timeout=120)
        assert rc == 0, f"incus admin init --minimal failed: {output}"

    def test_incus_network_created(self, incus_qemu):
        """Default network bridge should exist after init."""
        output, rc = run_cmd(incus_qemu, "incus network list")
        assert rc == 0, f"incus network list failed: {output}"


class TestIncusContainer:
    """Test launching and managing a container."""

    def test_launch_alpine(self, incus_qemu):
        """Launch an Alpine container from the images: remote."""
        output, rc = run_cmd(incus_qemu, "incus launch images:alpine/edge incus-test1",
                             timeout=180)
        assert rc == 0, f"incus launch failed: {output}"

    def test_container_running(self, incus_qemu):
        """The launched container should be in RUNNING state."""
        output, rc = run_cmd(incus_qemu, "incus list --format csv -c n,s")
        assert rc == 0
        assert "incus-test1,RUNNING" in output.replace(" ", ""), \
            f"Container not running: {output}"

    def test_exec_in_container(self, incus_qemu):
        """Execute a command inside the container."""
        output, rc = run_cmd(incus_qemu, "incus exec incus-test1 -- cat /etc/os-release")
        assert rc == 0
        assert "Alpine" in output, f"Unexpected os-release: {output}"

    def test_stop_container(self, incus_qemu):
        """Stop the container."""
        output, rc = run_cmd(incus_qemu, "incus stop incus-test1", timeout=30)
        assert rc == 0, f"incus stop failed: {output}"

    def test_delete_container(self, incus_qemu):
        """Delete the stopped container."""
        output, rc = run_cmd(incus_qemu, "incus delete incus-test1", timeout=15)
        assert rc == 0, f"incus delete failed: {output}"

    def test_no_containers_remain(self, incus_qemu):
        """No containers should remain after cleanup."""
        output, rc = run_cmd(incus_qemu, "incus list --format csv")
        assert rc == 0
        assert "incus-test1" not in output