fix: make RPM runtime portable on Tumbleweed

This commit is contained in:
xavierk
2026-09-04 11:46:58 +05:30
parent bdcd321f4c
commit b593a2742e
16 changed files with 146 additions and 71 deletions
+15
View File
@@ -52,6 +52,21 @@ class TestIsRoot:
class TestEnableIdempotentMatrix:
"""§8.6: Period-row idempotent matrix."""
def test_first_enable_creates_missing_store(self, store_path):
"""A fresh package install has a store directory but no database yet."""
args = MagicMock(now=False, store_path=store_path)
with patch("fenris.monitor.subprocess") as mock_sub:
mock_sub.run.return_value = MagicMock(returncode=0)
cmd_enable(args)
conn = init_store(store_path)
row = conn.execute(
"SELECT ended_at FROM monitoring_periods WHERE ended_at IS NULL"
).fetchone()
conn.close()
assert row is not None
def test_first_opens_period(self, store_path):
"""First-ever enable opens a period at the enable moment."""
# Initialize store
+27 -17
View File
@@ -78,6 +78,7 @@ DEB_TARGETS = [
RPM_TARGETS = [
("fedora:40", "rpm"),
("opensuse/tumbleweed", "rpm"),
]
ALL_TARGETS = DEB_TARGETS + RPM_TARGETS
@@ -101,11 +102,14 @@ def _build_deb_dockerfile(image: str, pkg_name: str) -> str:
def _build_rpm_dockerfile(image: str, pkg_name: str) -> str:
"""Dockerfile for testing rpm installation."""
install_command = (
"zypper --non-interactive install --no-recommends python3 smartmontools systemd dbus-1 && zypper clean --all"
if image.startswith("opensuse/")
else "dnf install -y --setopt=install_weak_deps=False python3 smartmontools systemd dbus && dnf clean all"
)
return textwrap.dedent(f"""\
FROM {image}
RUN dnf install -y --setopt=install_weak_deps=False \
python3 smartmontools systemd dbus && \
dnf clean all
RUN {install_command}
COPY dist/{pkg_name} /pkg/{pkg_name}
RUN rpm -ivh /pkg/{pkg_name}
""")
@@ -132,13 +136,16 @@ def skip_no_docker():
def _assert_dormant_layout(container: str, fmt: str, version: str) -> None:
"""Assert the dormant-install contract (spec §6, §7)."""
# Venv directory exists with expected structure
# Version-neutral vendored runtime exists. It must not contain a copied
# Python binary tied to the package build host.
rc, out = _container_exec(container, "test -d /opt/fenris && echo OK")
assert "OK" in out, "Bundled venv not found at /opt/fenris"
assert "OK" in out, "Bundled runtime not found at /opt/fenris"
# Venv Python binary exists (may not execute if shared libs differ)
rc, _ = _container_exec(container, "test -f /opt/fenris/bin/python3")
assert rc == 0, "Venv Python binary not found"
rc, _ = _container_exec(container, "test -f /opt/fenris/vendor/fenris/__init__.py")
assert rc == 0, "Fenris runtime package not found"
rc, _ = _container_exec(container, "test ! -e /opt/fenris/bin/python3")
assert rc == 0, "Package must not ship a copied Python interpreter"
# Wrapper on PATH
rc, out = _container_exec(container, "command -v fenris")
@@ -152,6 +159,11 @@ def _assert_dormant_layout(container: str, fmt: str, version: str) -> None:
rc, out = _container_exec(container, f"grep -q '{version}' /usr/bin/fenris && echo OK")
assert "OK" in out, f"Version {version} not found in wrapper script"
# This catches launcher import-path errors and copied-interpreter ABI
# breakage. Status is read-only and succeeds before monitoring setup.
rc, out = _container_exec(container, "fenris status")
assert rc == 0, f"Installed CLI cannot run: {out}"
# Helpers in /usr/libexec/fenris
for helper in ("fenris-monitor", "fenris-collect"):
rc, _ = _container_exec(container, f"test -x /usr/libexec/fenris/{helper}")
@@ -284,8 +296,7 @@ def _assert_upgrade_semantics(container: str, fmt: str, pkg_name: str, version:
4. Removal scripts are no-ops during upgrade
5. Downgrade refusal via forward-only version check (tested at Python level)
"""
# Create a fake observation store using system Python
# (venv Python may not execute if host shared libs differ)
# Create a fake observation store using the target system Python.
_container_exec(
container,
"mkdir -p /var/lib/fenris && "
@@ -391,7 +402,7 @@ def _assert_rpm_upgrade_full(container: str, pkg_name: str) -> None:
# Modify the config file (simulate hand-edited device selector)
_container_exec(
container,
"echo 'devices = /dev/nvme0n1' > /etc/fenris/fenris.conf",
"echo 'device = /dev/nvme0n1' > /etc/fenris/fenris.conf",
)
# Record original config hash
rc, original_hash = _container_exec(
@@ -447,8 +458,8 @@ def _assert_package_files_removed(container: str) -> None:
)
assert rc != 0, "Helper should be removed"
rc, _ = _container_exec(container, "test -f /opt/fenris/bin/python3")
assert rc != 0, "Venv Python should be removed"
rc, _ = _container_exec(container, "test -d /opt/fenris/vendor")
assert rc != 0, "Vendored runtime packages should be removed"
def _assert_deb_remove_preserves(container: str) -> None:
@@ -587,10 +598,9 @@ def test_python_floor(skip_no_docker, version):
assert "python3" in out, f"python3 dependency not declared: {out}"
assert "3.10" in out, f"Python 3.10 floor not in dependency: {out}"
# Verify the bundled venv exists (binary may not execute on the host
# interpreter — that's tested separately by the dormant-install test)
rc, _ = _container_exec(container, "test -f /opt/fenris/bin/python3")
assert rc == 0, "Bundled venv Python binary not found"
# Verify the version-neutral bundled runtime exists.
rc, _ = _container_exec(container, "test -f /opt/fenris/vendor/fenris/__init__.py")
assert rc == 0, "Bundled runtime package not found"
# fenris on PATH
rc, _ = _container_exec(container, "command -v fenris")