fix(testing): add Python 3.10 floor test and fix Makefile version gate for #47
Add test_python_floor to the containerized packaging matrix: - Sub-check 1: Ubuntu 22.04 (Python 3.10) installs successfully, confirming the floor is met on the oldest supported deb target. - Sub-check 2: Debian 11 (Python 3.9) fails to configure due to unmet python3 (>= 3.10) dependency, verifying clean failure below floor. Fix Makefile check-python gate to enforce Python >= 3.10, matching the nfpm depends declaration. Full compatibility matrix is now green: 17 packaging tests (dormant install, migration guard, upgrade semantics, removal semantics, and Python floor) pass across all four targets (Debian 12, Ubuntu 22.04, Ubuntu 24.04, Fedora 40). Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
co-authored by
CommandCodeBot
parent
b2243a85f7
commit
e9d6881e38
@@ -40,8 +40,8 @@ help:
|
|||||||
# ─── Pre-install gates ──────────────────────────────────────────────────────
|
# ─── Pre-install gates ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
check-python:
|
check-python:
|
||||||
@echo "=== Verifying Python ≥ 3.9 ==="
|
@echo "=== Verifying Python ≥ 3.10 ==="
|
||||||
@$(PYTHON) -c "import sys; v=sys.version_info; exit(0 if (v>=(3,9)) else 1)" || { echo "Error: Python 3.9+ required (found $$($(PYTHON) --version 2>&1))"; exit 1; }
|
@$(PYTHON) -c "import sys; v=sys.version_info; exit(0 if (v>=(3,10)) else 1)" || { echo "Error: Python 3.10+ required (found $$($(PYTHON) --version 2>&1))"; exit 1; }
|
||||||
|
|
||||||
check-smartctl:
|
check-smartctl:
|
||||||
@echo "=== Verifying smartctl ==="
|
@echo "=== Verifying smartctl ==="
|
||||||
|
|||||||
@@ -359,6 +359,151 @@ def _assert_removal_semantics(container: str, fmt: str) -> None:
|
|||||||
assert rc != 0, "Venv Python should be removed after uninstall"
|
assert rc != 0, "Venv Python should be removed after uninstall"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tests — Python 3.10 floor (spec §7, nfpm depends)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
def test_python_floor(skip_no_docker, version):
|
||||||
|
"""Verify the Python 3.10 floor on the oldest supported deb target.
|
||||||
|
|
||||||
|
Two sub-checks:
|
||||||
|
1. Ubuntu 22.04 (Python 3.10): the same deb installs successfully,
|
||||||
|
confirming the floor is met on the oldest target.
|
||||||
|
2. Debian 11 (Python 3.9): installation fails cleanly because the
|
||||||
|
declared dependency ``python3 (>= 3.10)`` is unsatisfied.
|
||||||
|
"""
|
||||||
|
pkg = _find_package("deb")
|
||||||
|
pkg_name = pkg.name
|
||||||
|
|
||||||
|
# --- Sub-check 1: floor met on Ubuntu 22.04 ---
|
||||||
|
build_dir = REPO_ROOT / "build" / "test-container-floor"
|
||||||
|
build_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(build_dir / "dist").mkdir(exist_ok=True)
|
||||||
|
subprocess.run(
|
||||||
|
["cp", str(pkg), str(build_dir / "dist" / pkg_name)],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
dockerfile = _build_deb_dockerfile("ubuntu:22.04", pkg_name)
|
||||||
|
(build_dir / "Dockerfile").write_text(dockerfile)
|
||||||
|
|
||||||
|
tag = "fenris-floor-ubuntu-2204"
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "build", "-t", tag, str(build_dir)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
timeout=300,
|
||||||
|
)
|
||||||
|
container = f"fenris-floor-{os.getpid()}"
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"docker", "run", "-d", "--name", container,
|
||||||
|
"--tmpfs", "/tmp:exec,size=64m",
|
||||||
|
tag, "sleep", "infinity",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# Verify Python 3.10 is the system interpreter
|
||||||
|
rc, out = _container_exec(container, "python3 -c 'import sys; print(sys.version_info[:2])'")
|
||||||
|
assert rc == 0, f"Cannot check system Python: {out}"
|
||||||
|
major, minor = (int(x) for x in out.strip().strip("()").split(","))
|
||||||
|
assert (major, minor) >= (3, 10), \
|
||||||
|
f"Expected Python >= 3.10 on Ubuntu 22.04, got {major}.{minor}"
|
||||||
|
|
||||||
|
# Verify the package dependency is declared
|
||||||
|
rc, out = _container_exec(
|
||||||
|
container,
|
||||||
|
"dpkg -s fenris 2>/dev/null | grep -i 'Depends:' || true",
|
||||||
|
)
|
||||||
|
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"
|
||||||
|
|
||||||
|
# fenris on PATH
|
||||||
|
rc, _ = _container_exec(container, "command -v fenris")
|
||||||
|
assert rc == 0, "fenris not on PATH after floor-met install"
|
||||||
|
finally:
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "rm", "-f", container],
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Sub-check 2: below floor on Debian 11 (Python 3.9) ---
|
||||||
|
build_dir_floor = REPO_ROOT / "build" / "test-container-below-floor"
|
||||||
|
build_dir_floor.mkdir(parents=True, exist_ok=True)
|
||||||
|
(build_dir_floor / "dist").mkdir(exist_ok=True)
|
||||||
|
subprocess.run(
|
||||||
|
["cp", str(pkg), str(build_dir_floor / "dist" / pkg_name)],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
dockerfile_floor = textwrap.dedent(f"""\
|
||||||
|
FROM debian:bullseye
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
||||||
|
python3 systemd dbus && \\
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY dist/{pkg_name} /pkg/{pkg_name}
|
||||||
|
""")
|
||||||
|
(build_dir_floor / "Dockerfile").write_text(dockerfile_floor)
|
||||||
|
|
||||||
|
tag_floor = "fenris-below-floor-debian11"
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "build", "-t", tag_floor, str(build_dir_floor)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
timeout=300,
|
||||||
|
)
|
||||||
|
container_floor = f"fenris-below-floor-{os.getpid()}"
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"docker", "run", "-d", "--name", container_floor,
|
||||||
|
"--tmpfs", "/tmp:exec,size=64m",
|
||||||
|
tag_floor, "sleep", "infinity",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# Confirm Python 3.9 is present (below floor)
|
||||||
|
rc, out = _container_exec(
|
||||||
|
container_floor,
|
||||||
|
"python3 -c 'import sys; print(f\"{sys.version_info.major}.{sys.version_info.minor}\")'",
|
||||||
|
)
|
||||||
|
assert rc == 0, f"Cannot check system Python on Debian 11: {out}"
|
||||||
|
major, minor = (int(x) for x in out.strip().split("."))
|
||||||
|
assert (major, minor) < (3, 10), \
|
||||||
|
f"Expected Python < 3.10 on Debian 11, got {major}.{minor}"
|
||||||
|
|
||||||
|
# Attempt install — should fail due to unmet dependency
|
||||||
|
rc, out = _container_exec(
|
||||||
|
container_floor,
|
||||||
|
f"dpkg -i /pkg/{pkg_name} 2>&1; echo EXIT:$?",
|
||||||
|
)
|
||||||
|
# dpkg exits non-zero when dependencies are unmet; the EXIT:N
|
||||||
|
# line in the output captures the real exit code even if the
|
||||||
|
# shell wraps it.
|
||||||
|
assert "error" in out.lower() or "dependency" in out.lower() or "EXIT:0" not in out, \
|
||||||
|
f"Expected install failure below floor, but got: {out}"
|
||||||
|
|
||||||
|
# Confirm package is NOT properly configured (unpacked due to unmet deps)
|
||||||
|
rc, out = _container_exec(
|
||||||
|
container_floor,
|
||||||
|
"dpkg -s fenris 2>/dev/null | grep '^Status:' || echo NO_STATUS",
|
||||||
|
)
|
||||||
|
assert "unpacked" in out.lower() or "not installed" in out.lower() or rc != 0, \
|
||||||
|
f"Package should not be configured below Python floor: {out}"
|
||||||
|
finally:
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "rm", "-f", container_floor],
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tests — dormant install (tracer bullet)
|
# Tests — dormant install (tracer bullet)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user