Files
buildroot/support/testing/tests/init/test_systemd.py
T
Yann E. MORIN a530492cd3 package/systemd: require merged-bin
Since version 256, systemd will taint the system if /usr/bin and
/usr/sbin are not merged, known as merged-bin:

    # systemctl --no-pager status
    ● buildroot
        State: running
        Units: 166 loaded (incl. loaded aliases)
         Jobs: 0 queued
       Failed: 0 units
        Since: Mon 2025-07-07 19:48:05 UTC; 19s ago
      systemd: 257.7
      Tainted: unmerged-bin
       CGroup: /
               ├─init.scope
               [...]

Although this is not yet an error, it will be in the future.

To be as ready as we can be when that happens, forcibly enable
merged-bin, like we did when we initially added merged-usr (except
this time we carry the select from systemd, even though it is not
yet strictly required, rather than from the init entry).

Extend the runtime test to catch any tainted flag.

Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Arnout Vandecappelle <arnout@mind.be>
Cc: Norbert Lange <nolange79@gmail.com>
Cc: Sen Hastings <sen@hastings.org>
Signed-off-by: Romain Naour <romain.naour@smile.fr>
2025-11-05 23:10:31 +01:00

428 lines
14 KiB
Python

import infra.basetest
import re
from tests.init.base import InitSystemBase as InitSystemBase
# In the following tests, the read-only cases use the default settings,
# which historically used both a factory to populate a tmpfs on /var,
# and pre-populated /var at buildtime. Since these are the default
# settings, and they proved to generate a system that ultimately boots,
# we still want to keep testing that. See later, below, for the
# specialised test cases.
class InitSystemSystemdBase(InitSystemBase):
config = \
"""
BR2_arm=y
BR2_cortex_a9=y
BR2_ARM_ENABLE_VFP=y
BR2_TOOLCHAIN_EXTERNAL=y
BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
BR2_INIT_SYSTEMD=y
BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0"
# BR2_TARGET_ROOTFS_TAR is not set
BR2_PER_PACKAGE_DIRECTORIES=y
"""
def check_systemd(self, fs):
if "BR2_LINUX_KERNEL=y" in self.config:
self.start_emulator(fs, "zImage", "vexpress-v2p-ca9")
else:
self.start_emulator(fs)
self.check_init("/lib/systemd/systemd")
# Test there is no tainted flag.
output, ret = self.emulator.run("systemctl --no-pager status")
self.assertEqual(ret, 0, f"'systemctl status' failed with exit code {ret}, with:\n{output}")
try:
# 'support-ended' tainted flag is only set based on the
# SUPPORT_END variable in /etc/os-release; as we don't set
# it in Buildroot, we can't get that flag in the runtime
# tests, even on our maintenance branches, so we don't need
# to filter it out.
tainted_flags = [
"".join(line.split(":")[1:]).strip()
for line in output
if line.strip().startswith("Tainted: ")
][0]
raise RuntimeError(f"Tainted flags: {tainted_flags}")
except IndexError:
# No tainted flag \o/
pass
# Test all units are OK
output, _ = self.emulator.run("systemctl --no-pager --failed --no-legend")
self.assertEqual(len(output), 0)
# Test we can reach the DBus daemon
self.assertRunOk("busctl --no-pager")
# Test we can read at least one line from the journal
output, _ = self.emulator.run("journalctl --no-pager --lines 1 --quiet")
self.assertEqual(len(output), 1)
# Check the network is up
self.check_network("eth0")
class TestInitSystemSystemdRoNetworkd(InitSystemSystemdBase):
config = InitSystemSystemdBase.config + \
"""
BR2_SYSTEM_DHCP="eth0"
# BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set
BR2_TARGET_ROOTFS_SQUASHFS=y
"""
def test_run(self):
self.check_systemd("squashfs")
class TestInitSystemSystemdRwNetworkd(InitSystemSystemdBase):
config = InitSystemSystemdBase.config + \
"""
BR2_SYSTEM_DHCP="eth0"
BR2_TARGET_ROOTFS_EXT2=y
"""
def test_run(self):
self.check_systemd("ext2")
class TestInitSystemSystemdRoIfupdown(InitSystemSystemdBase):
config = InitSystemSystemdBase.config + \
"""
BR2_SYSTEM_DHCP="eth0"
# BR2_PACKAGE_SYSTEMD_NETWORKD is not set
# BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set
BR2_TARGET_ROOTFS_SQUASHFS=y
"""
def test_run(self):
self.check_systemd("squashfs")
class TestInitSystemSystemdRoIfupdownDbusbroker(TestInitSystemSystemdRoIfupdown):
config = TestInitSystemSystemdRoIfupdown.config + \
"""
BR2_PACKAGE_DBUS_BROKER=y
"""
def test_run(self):
# Parent class' test_run() method does exactly that, no more:
self.check_systemd("squashfs")
# Check that the dbus-broker daemon is running as non-root
cmd = "find /proc/$(pidof dbus-broker) -maxdepth 1 -name exe -user dbus"
out, _ = self.emulator.run(cmd)
self.assertEqual(len(out), 1)
class TestInitSystemSystemdRoIfupdownDbusbrokerDbus(TestInitSystemSystemdRoIfupdownDbusbroker):
config = TestInitSystemSystemdRoIfupdownDbusbroker.config + \
"""
BR2_PACKAGE_DBUS=y
"""
class TestInitSystemSystemdRwIfupdown(InitSystemSystemdBase):
config = InitSystemSystemdBase.config + \
"""
BR2_SYSTEM_DHCP="eth0"
# BR2_PACKAGE_SYSTEMD_NETWORKD is not set
BR2_TARGET_ROOTFS_EXT2=y
"""
def test_run(self):
self.check_systemd("ext2")
class TestInitSystemSystemdRwIfupdownDbusbroker(TestInitSystemSystemdRwIfupdown):
config = TestInitSystemSystemdRwIfupdown.config + \
"""
BR2_PACKAGE_DBUS_BROKER=y
"""
def test_run(self):
# Parent class' test_run() method does exactly that, no more:
self.check_systemd("ext2")
# Check that the dbus-broker daemon is running as non-root
cmd = "find /proc/$(pidof dbus-broker) -maxdepth 1 -name exe -user dbus"
out, _ = self.emulator.run(cmd)
self.assertEqual(len(out), 1)
class TestInitSystemSystemdRwIfupdownDbusbrokerDbus(TestInitSystemSystemdRwIfupdownDbusbroker):
config = TestInitSystemSystemdRwIfupdownDbusbroker.config + \
"""
BR2_PACKAGE_DBUS=y
"""
class TestInitSystemSystemdRoFull(InitSystemSystemdBase):
config = InitSystemSystemdBase.config + \
"""
BR2_SYSTEM_DHCP="eth0"
# BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set
BR2_PACKAGE_SYSTEMD_JOURNAL_REMOTE=y
BR2_PACKAGE_SYSTEMD_BACKLIGHT=y
BR2_PACKAGE_SYSTEMD_BINFMT=y
BR2_PACKAGE_SYSTEMD_COREDUMP=y
BR2_PACKAGE_SYSTEMD_FIRSTBOOT=y
BR2_PACKAGE_SYSTEMD_HIBERNATE=y
BR2_PACKAGE_SYSTEMD_IMPORTD=y
BR2_PACKAGE_SYSTEMD_LOCALED=y
BR2_PACKAGE_SYSTEMD_LOGIND=y
BR2_PACKAGE_SYSTEMD_MACHINED=y
BR2_PACKAGE_SYSTEMD_POLKIT=y
BR2_PACKAGE_SYSTEMD_QUOTACHECK=y
BR2_PACKAGE_SYSTEMD_RANDOMSEED=y
BR2_PACKAGE_SYSTEMD_RFKILL=y
BR2_PACKAGE_SYSTEMD_SMACK_SUPPORT=y
BR2_PACKAGE_SYSTEMD_SYSUSERS=y
BR2_PACKAGE_SYSTEMD_VCONSOLE=y
BR2_TARGET_ROOTFS_SQUASHFS=y
"""
def test_run(self):
self.check_systemd("squashfs")
class TestInitSystemSystemdRwFull(InitSystemSystemdBase):
config = InitSystemSystemdBase.config + \
"""
BR2_SYSTEM_DHCP="eth0"
BR2_PACKAGE_SYSTEMD_JOURNAL_REMOTE=y
BR2_PACKAGE_SYSTEMD_BACKLIGHT=y
BR2_PACKAGE_SYSTEMD_BINFMT=y
BR2_PACKAGE_SYSTEMD_COREDUMP=y
BR2_PACKAGE_SYSTEMD_FIRSTBOOT=y
BR2_PACKAGE_SYSTEMD_HIBERNATE=y
BR2_PACKAGE_SYSTEMD_IMPORTD=y
BR2_PACKAGE_SYSTEMD_LOCALED=y
BR2_PACKAGE_SYSTEMD_LOGIND=y
BR2_PACKAGE_SYSTEMD_MACHINED=y
BR2_PACKAGE_SYSTEMD_POLKIT=y
BR2_PACKAGE_SYSTEMD_QUOTACHECK=y
BR2_PACKAGE_SYSTEMD_RANDOMSEED=y
BR2_PACKAGE_SYSTEMD_RFKILL=y
BR2_PACKAGE_SYSTEMD_SMACK_SUPPORT=y
BR2_PACKAGE_SYSTEMD_SYSUSERS=y
BR2_PACKAGE_SYSTEMD_VCONSOLE=y
BR2_TARGET_ROOTFS_EXT2=y
"""
def test_run(self):
self.check_systemd("ext2")
# The following tests are all about read-only rootfs, and exercise either
# using an un-populated factory for /var, or an overlaysfs on top of a
# pre-populated /var. They all specialise the TestInitSystemSystemdRo*
# test cases above.
# Helper class for factory-based tests
class InitSystemSystemdBaseFactory():
config = \
"""
# BR2_INIT_SYSTEMD_POPULATE_TMPFILES is not set
BR2_ROOTFS_OVERLAY="{}"
""".format(infra.filepath("tests/init/systemd-factory"))
def test_run(self):
super().test_run()
# This one must be executed on the target, to check that
# the factory feature works as expected
out, exit_code = self.emulator.run("cat /var/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "foobar")
# /var/foo/bar is from the /var factory
_, exit_code = self.emulator.run("test -e /usr/share/factory/var/foo/bar")
self.assertEqual(exit_code, 0)
# We can write in /var/foo/bar
_, exit_code = self.emulator.run("echo barfoo >/var/foo/bar")
self.assertEqual(exit_code, 0)
# ... and it contains the new content
out, exit_code = self.emulator.run("cat /var/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "barfoo")
# ... but the factory is umodified
out, exit_code = self.emulator.run("cat /usr/share/factory/var/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "foobar")
class TestInitSystemSystemdRoNetworkdFactory(
InitSystemSystemdBaseFactory,
TestInitSystemSystemdRoNetworkd,
):
config = InitSystemSystemdBaseFactory.config + \
TestInitSystemSystemdRoNetworkd.config
class TestInitSystemSystemdRoIfupdownFactory(
InitSystemSystemdBaseFactory,
TestInitSystemSystemdRoIfupdown,
):
config = InitSystemSystemdBaseFactory.config + \
TestInitSystemSystemdRoIfupdown.config
class TestInitSystemSystemdRoIfupdownDbusbrokerFactory(
InitSystemSystemdBaseFactory,
TestInitSystemSystemdRoIfupdownDbusbroker,
):
config = InitSystemSystemdBaseFactory.config + \
TestInitSystemSystemdRoIfupdownDbusbroker.config
class TestInitSystemSystemdRoIfupdownDbusbrokerDbusFactory(
InitSystemSystemdBaseFactory,
TestInitSystemSystemdRoIfupdownDbusbrokerDbus,
):
config = InitSystemSystemdBaseFactory.config + \
TestInitSystemSystemdRoIfupdownDbusbrokerDbus.config
class TestInitSystemSystemdRoFullFactory(
InitSystemSystemdBaseFactory,
TestInitSystemSystemdRoFull,
):
config = InitSystemSystemdBaseFactory.config + \
TestInitSystemSystemdRoFull.config
# Helper class for overlayfs-based tests
class InitSystemSystemdBaseOverlayfs():
config = \
"""
# BR2_INIT_SYSTEMD_VAR_FACTORY is not set
BR2_INIT_SYSTEMD_VAR_OVERLAYFS=y
BR2_ROOTFS_OVERLAY="{}"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="5.10.202"
BR2_LINUX_KERNEL_DEFCONFIG="vexpress"
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="{}"
BR2_LINUX_KERNEL_DTS_SUPPORT=y
BR2_LINUX_KERNEL_INTREE_DTS_NAME="vexpress-v2p-ca9"
""".format(infra.filepath("tests/init/systemd-factory"),
infra.filepath("conf/overlayfs-kernel-fragment.config"))
def test_run(self):
super().test_run()
# This one must be executed on the target, to check that
# the tmpfiles pre-populate works as expected
out, exit_code = self.emulator.run("cat /var/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "foobar")
# /var/foo/bar is from the pre-populated /var, so it should
# not be present in the upper of the overlay
_, exit_code = self.emulator.run("test -e /run/buildroot/mounts/var/upper/foo/bar")
self.assertNotEqual(exit_code, 0)
# We can write in /var/foo/bar
_, exit_code = self.emulator.run("echo barfoo >/var/foo/bar")
self.assertEqual(exit_code, 0)
# ... and it contains the new content
out, exit_code = self.emulator.run("cat /var/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "barfoo")
# ... and it to appears in the upper
_, exit_code = self.emulator.run("test -e /run/buildroot/mounts/var/upper/foo/bar")
self.assertEqual(exit_code, 0)
# ... with the new content
out, exit_code = self.emulator.run("cat /run/buildroot/mounts/var/upper/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "barfoo")
# ... while the lower still has the oldcontent
out, exit_code = self.emulator.run("cat /run/buildroot/mounts/var/lower/foo/bar")
self.assertEqual(exit_code, 0)
self.assertEqual(out[0], "foobar")
class TestInitSystemSystemdRoNetworkdOverlayfs(
InitSystemSystemdBaseOverlayfs,
TestInitSystemSystemdRoNetworkd,
):
config = InitSystemSystemdBaseOverlayfs.config + \
TestInitSystemSystemdRoNetworkd.config
class TestInitSystemSystemdRoIfupdownOverlayfs(
InitSystemSystemdBaseOverlayfs,
TestInitSystemSystemdRoIfupdown,
):
config = InitSystemSystemdBaseOverlayfs.config + \
TestInitSystemSystemdRoIfupdown.config
class TestInitSystemSystemdRoIfupdownDbusbrokerOverlayfs(
InitSystemSystemdBaseOverlayfs,
TestInitSystemSystemdRoIfupdownDbusbroker,
):
config = InitSystemSystemdBaseOverlayfs.config + \
TestInitSystemSystemdRoIfupdownDbusbroker.config
class TestInitSystemSystemdRoIfupdownDbusbrokerDbusOverlayfs(
InitSystemSystemdBaseOverlayfs,
TestInitSystemSystemdRoIfupdownDbusbrokerDbus,
):
config = InitSystemSystemdBaseOverlayfs.config + \
TestInitSystemSystemdRoIfupdownDbusbrokerDbus.config
class TestInitSystemSystemdRoFullOverlayfs(
InitSystemSystemdBaseOverlayfs,
TestInitSystemSystemdRoFull,
):
config = InitSystemSystemdBaseOverlayfs.config + \
TestInitSystemSystemdRoFull.config
class InitSystemSystemdBaseOverlayfsVarBacking(InitSystemBase):
@classmethod
def gen_config(cls, overlaydir: str) -> str:
return re.sub(
r'^\s*BR2_ROOTFS_OVERLAY="(.*)"$',
'BR2_ROOTFS_OVERLAY="\\1 {}"'.format(infra.filepath(overlaydir)),
TestInitSystemSystemdRoFullOverlayfs.config,
flags=re.MULTILINE,
)
def check_var_mounted(self):
self.assertRunOk("grep '^other-var-backing-store /run/buildroot/mounts/var tmpfs' /proc/mounts")
class TestInitSystemSystemdRoFullOverlayfsVarBackingMountUnit(
TestInitSystemSystemdRoFullOverlayfs,
InitSystemSystemdBaseOverlayfsVarBacking,
):
config = InitSystemSystemdBaseOverlayfsVarBacking.gen_config(
'tests/init/systemd-overlay-mount-unit',
)
def test_run(self):
super().test_run()
self.check_var_mounted()
class TestInitSystemSystemdRoFullOverlayfsVarBackingFstab(
TestInitSystemSystemdRoFullOverlayfs,
InitSystemSystemdBaseOverlayfsVarBacking,
):
config = InitSystemSystemdBaseOverlayfsVarBacking.gen_config(
'tests/init/systemd-overlay-fstab',
)
def test_run(self):
super().test_run()
self.check_var_mounted()