feat: package as pip-installable distribution

Turns the loose main.py into the 'swap-forge' distribution with a src layout, shipping both the swap-forge and the older swafo command.

A failing swap command no longer produces a traceback: CalledProcessError is caught and reported as "'mkswap' failed with exit code 1" at exit 1, a missing binary as exit 127. Required binaries are declared per filesystem path so an ext4 host is never asked for btrfs tooling.

The fstab entry is still written last, after swapon, so a failed activation leaves /etc/fstab untouched. The e2e suite proves that byte for byte across a create, a same-size rerun and a resize cycle, with real fallocate, chmod and file sizes.

Adds unit, integration and container-based e2e tests, ruff and markdown/mermaid linting, CodeQL and Dependabot, and pins the core metadata to 2.4 so twine accepts the artifacts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 14:28:08 +02:00
parent e1eef73ae7
commit 02760e5864
23 changed files with 1005 additions and 125 deletions

12
tests/e2e/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends util-linux e2fsprogs btrfs-progs \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
COPY . /src
RUN pip install --no-cache-dir .
CMD ["sh", "/src/tests/e2e/run.sh"]

103
tests/e2e/run.sh Normal file
View File

@@ -0,0 +1,103 @@
#!/bin/sh
# An unprivileged container cannot activate swap, so the run is expected to fail
# at mkswap or swapon - which of the two depends on the host's /proc/swaps.
# Everything before it - findmnt, fallocate, chmod - runs for real.
set -eu
fail() {
echo "FAIL: $1" >&2
exit 1
}
SWAPFORGE="$(command -v swap-forge)" || fail "swap-forge is not on PATH after install"
echo "ok: swap-forge installed at ${SWAPFORGE}"
command -v swafo >/dev/null || fail "the swafo alias is not on PATH after install"
echo "ok: swafo alias installed"
case "${SWAPFORGE}" in
/usr/local/bin/*) ;;
*) fail "swap-forge resolved to ${SWAPFORGE}, outside the install prefix" ;;
esac
echo "ok: resolved from the installed package, not the source tree"
for binary in findmnt swapoff chmod mkswap swapon fallocate chattr btrfs dd; do
command -v "${binary}" >/dev/null || fail "${binary} is missing from the image"
done
echo "ok: every declared binary is present"
swap-forge --help | grep -q "Swapfile size" || fail "--help does not describe the tool"
echo "ok: --help"
set +e
swap-forge >/dev/null 2>&1
status=$?
set -e
[ "${status}" -eq 2 ] || fail "a missing size exited ${status}, expected 2"
echo "ok: a missing size exits 2"
set +e
output="$(swap-forge 64T 2>&1)"
status=$?
set -e
[ "${status}" -eq 1 ] || fail "an invalid size exited ${status}, expected 1"
echo "${output}" | grep -q "Invalid size format" || fail "invalid size message wrong"
if echo "${output}" | grep -q "Traceback"; then
fail "an invalid size produced a traceback"
fi
echo "ok: an invalid size exits 1 without a traceback"
printf 'UUID=deadbeef / ext4 defaults 0 1\n' >/etc/fstab
cp /etc/fstab /tmp/fstab.before
set +e
output="$(swap-forge 8M 2>&1)"
status=$?
set -e
[ "${status}" -eq 1 ] || fail "the blocked activation exited ${status}, expected 1"
echo "${output}" | grep -q "Creating 8 MiB swapfile" || fail "the creation banner is missing"
echo "${output}" | grep -qE "'(mkswap|swapon)' failed with exit code" || fail "the blocked activation was not reported cleanly"
if echo "${output}" | grep -q "Traceback"; then
fail "the blocked activation produced a traceback"
fi
echo "ok: real fallocate and chmod ran; the blocked activation exits 1 cleanly"
[ -f /swapfile ] || fail "/swapfile was not created"
size="$(stat -c %s /swapfile)"
[ "${size}" -eq 8388608 ] || fail "/swapfile is ${size} bytes, expected 8388608"
echo "ok: /swapfile exists with exactly the requested size"
cmp -s /etc/fstab /tmp/fstab.before || fail "/etc/fstab changed although activation failed"
echo "ok: /etc/fstab is untouched when activation fails"
set +e
output="$(swap-forge 8M 2>&1)"
status=$?
set -e
[ "${status}" -eq 0 ] || fail "the idempotent rerun exited ${status}, expected 0"
echo "${output}" | grep -q "already exists with correct size (8 MiB). Skipping." \
|| fail "an existing swapfile of the right size was not detected"
echo "ok: a rerun with the same size is a no-op - the idempotency the role relies on"
set +e
output="$(swap-forge 12M 2>&1)"
status=$?
set -e
echo "${output}" | grep -q "has size 8 MiB, expected 12 MiB. Recreating..." \
|| fail "a size change did not trigger a recreation"
size="$(stat -c %s /swapfile)"
[ "${size}" -eq 12582912 ] || fail "/swapfile is ${size} bytes after resize, expected 12582912"
echo "ok: a different size really removes and recreates the swapfile"
cmp -s /etc/fstab /tmp/fstab.before || fail "/etc/fstab changed during the resize cycle"
echo "ok: /etc/fstab survived the resize cycle byte for byte"
set +e
output="$(env PATH=/nonexistent-bin "${SWAPFORGE}" 8M 2>&1)"
status=$?
set -e
[ "${status}" -eq 127 ] || fail "missing findmnt exited ${status}, expected 127"
echo "${output}" | grep -q "required command 'findmnt' is not installed" || fail "missing findmnt message wrong"
echo "ok: missing findmnt exits 127 with a clean message"
echo "ALL E2E CHECKS PASSED"