# # Test for the build root # import pathlib import os import select import subprocess import sys import time from tempfile import TemporaryDirectory import pytest from osbuild.buildroot import BuildRoot from osbuild.monitor import LogMonitor, NullMonitor from osbuild.pipeline import detect_host_runner from ..test import TestBase @pytest.fixture(name="tempdir") def tempdir_fixture(): with TemporaryDirectory(prefix="lvm2-") as tmp: yield tmp @pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only") def test_basic(tempdir): runner = detect_host_runner() libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() monitor = NullMonitor(sys.stderr.fileno()) with BuildRoot("/", runner, libdir, var) as root: r = root.run(["/usr/bin/true"], monitor) assert r.returncode == 0 # Test we can use `.run` multiple times r = root.run(["/usr/bin/true"], monitor) assert r.returncode == 0 r = root.run(["/usr/bin/false"], monitor) assert r.returncode != 0 @pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only") def test_runner_fail(tempdir): runner = "org.osbuild.nonexistantrunner" libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() logfile = os.path.join(tempdir, "log.txt") with BuildRoot("/", runner, libdir, var) as root, \ open(logfile, "w") as log: monitor = LogMonitor(log.fileno()) r = root.run(["/usr/bin/true"], monitor) assert r.returncode == 1 with open(logfile) as f: log = f.read() assert log assert r.output assert log == r.output @pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only") def test_output(tempdir): runner = detect_host_runner() libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() data = "42. cats are superior to dogs" monitor = NullMonitor(sys.stderr.fileno()) with BuildRoot("/", runner, libdir, var) as root: r = root.run(["/usr/bin/echo", data], monitor) assert r.returncode == 0 assert data in r.output.strip() @pytest.mark.skipif(not TestBase.have_test_data(), reason="no test-data access") @pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only") def test_bind_mounts(tempdir): runner = detect_host_runner() libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() rw_data = pathlib.Path(tempdir, "data") rw_data.mkdir() scripts = os.path.join(TestBase.locate_test_data(), "scripts") monitor = NullMonitor(sys.stderr.fileno()) with BuildRoot("/", runner, libdir, var) as root: ro_binds = [f"{scripts}:/scripts"] cmd = ["/scripts/mount_flags.py", "/scripts", "ro"] r = root.run(cmd, monitor, readonly_binds=ro_binds) assert r.returncode == 0 cmd = ["/scripts/mount_flags.py", "/rw-data", "ro"] binds = [f"{rw_data}:/rw-data"] r = root.run(cmd, monitor, binds=binds, readonly_binds=ro_binds) assert r.returncode == 1 @pytest.mark.skipif(not TestBase.have_test_data(), reason="no test-data access") @pytest.mark.skipif(not os.path.exists("/sys/fs/selinux"), reason="no SELinux") def test_selinuxfs_ro(tempdir): # /sys/fs/selinux must never be writable in the container # because RPM and other tools must not assume the policy # of the host is the valid policy runner = detect_host_runner() libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() scripts = os.path.join(TestBase.locate_test_data(), "scripts") monitor = NullMonitor(sys.stderr.fileno()) with BuildRoot("/", runner, libdir, var) as root: ro_binds = [f"{scripts}:/scripts"] cmd = ["/scripts/mount_flags.py", "/sys/fs/selinux", "ro"] r = root.run(cmd, monitor, readonly_binds=ro_binds) assert r.returncode == 0 @pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only") def test_proc_overrides(tempdir): runner = detect_host_runner() libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() cmdline = "is-this-the-real-world" monitor = NullMonitor(sys.stderr.fileno()) with BuildRoot("/", runner, libdir, var) as root: root.proc.cmdline = cmdline r = root.run(["cat", "/proc/cmdline"], monitor) assert r.returncode == 0 assert cmdline in r.output.strip() @pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only") def test_timeout(tempdir): runner = detect_host_runner() libdir = os.path.abspath(os.curdir) var = pathlib.Path(tempdir, "var") var.mkdir() with BuildRoot("/", runner, libdir, var) as root: READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR poller = select.poll() proc = subprocess.Popen(['python3', libdir + '/stages/org.osbuild.test.timeout'], stdout = subprocess.PIPE) start = time.monotonic() timeout = 4 poller.register(proc.stdout.fileno(), READ_ONLY) with pytest.raises(TimeoutError): root.read_with_timeout(proc, poller, start, timeout) proc = subprocess.Popen(['python3', libdir + '/stages/org.osbuild.test.timeout'], stdout = subprocess.PIPE) start = time.monotonic() timeout = 0 poller.register(proc.stdout.fileno(), READ_ONLY) with pytest.raises(TimeoutError): root.read_with_timeout(proc, poller, start, timeout)