From 70429b90f1dc27235f48d97d8d572c325a130499 Mon Sep 17 00:00:00 2001 From: quality Date: Fri, 4 Sep 2026 19:14:18 -0400 Subject: [PATCH] [quality] test: BATS coverage for the k8s recipe in files/os/justfile Adds tests/unit/os-justfile_test.bats: 20 cases covering the only executable logic in files/os/justfile -- the k8s recipe. Every invocation runs inside an unprivileged user+mount namespace with tmpfs masks over /etc and /var/lib, and systemctl, systemd-sysext and systemd-sysupdate replaced by logging stubs on PATH, so no host state is touched and no unit is ever enabled. The parse gate fails on the current tree: the cat < Signed-off-by: quality --- tests/unit/os-justfile_test.bats | 260 +++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 tests/unit/os-justfile_test.bats diff --git a/tests/unit/os-justfile_test.bats b/tests/unit/os-justfile_test.bats new file mode 100644 index 0000000..b144f41 --- /dev/null +++ b/tests/unit/os-justfile_test.bats @@ -0,0 +1,260 @@ +#!/usr/bin/env bats +# +# Unit tests for the `k8s` recipe in files/os/justfile. +# +# The recipe writes to absolute host paths (/etc/rancher/k3s) and probes +# /var/lib/extensions, so every invocation runs inside an unprivileged user + +# mount namespace with tmpfs masks over /etc and /var/lib. Nothing on the host +# is touched. systemctl, systemd-sysext and systemd-sysupdate are replaced by +# logging stubs on PATH, so no unit is ever enabled and no OTA is ever fetched. +# +# Results that must outlive the namespace (the call log, the seeded config) are +# copied back into BATS_TEST_TMPDIR, which is outside both tmpfs masks. + +setup() { + if ! unshare --map-root-user --mount true 2>/dev/null; then + skip "unprivileged user+mount namespaces are unavailable" + fi + if ! command -v just >/dev/null 2>&1; then + skip "just is not installed" + fi + + REPO_ROOT="$(cd "${BATS_TEST_DIRNAME}/../.." && pwd)" + JUSTFILE="${REPO_ROOT}/files/os/justfile" + STUB_DIR="${BATS_TEST_TMPDIR}/bin" + LOG="${BATS_TEST_TMPDIR}/calls.log" + OUT_CONFIG="${BATS_TEST_TMPDIR}/config.yaml" + + mkdir -p "$STUB_DIR" + : > "$LOG" + rm -f "$OUT_CONFIG" + + # The recipe cannot be exercised at all while the justfile is unparseable. + # The behavioural tests skip in that state so the suite reports exactly one + # failure -- the parse gate below -- instead of twenty derived ones. + if just --justfile "$JUSTFILE" --summary >/dev/null 2>&1; then + JUSTFILE_PARSES=1 + else + JUSTFILE_PARSES=0 + fi + + # Defaults: every helper succeeds, and the k3s sysext is not yet present. + make_stub systemctl 0 + make_stub systemd-sysext 0 + make_stub systemd-sysupdate 0 + SYSEXT_PRESENT=0 + SEED_CONFIG="" +} + +# make_stub +make_stub() { + cat > "${STUB_DIR}/$1" <> "${LOG}" +exit $2 +EOF + chmod +x "${STUB_DIR}/$1" +} + +# run_k8s [args...] -- invoke `just k8s` inside the namespace sandbox. +run_k8s() { + [ "$JUSTFILE_PARSES" = "1" ] || \ + skip "files/os/justfile does not parse; see the parse gate test" + run env \ + PATH="${STUB_DIR}:${PATH}" \ + LOG="$LOG" \ + JUSTFILE="$JUSTFILE" \ + OUT_CONFIG="$OUT_CONFIG" \ + SYSEXT_PRESENT="$SYSEXT_PRESENT" \ + SEED_CONFIG="$SEED_CONFIG" \ + WORKDIR="$BATS_TEST_TMPDIR" \ + unshare --map-root-user --mount bash -s "$@" <<'SANDBOX' +set -u +mount -t tmpfs tmpfs /etc +mount -t tmpfs tmpfs /var/lib +mkdir -p /var/lib/extensions +[ "$SYSEXT_PRESENT" = "1" ] && : > /var/lib/extensions/k3s.raw +if [ -n "$SEED_CONFIG" ]; then + mkdir -p /etc/rancher/k3s + printf '%s' "$SEED_CONFIG" > /etc/rancher/k3s/config.yaml +fi + +just --justfile "$JUSTFILE" --working-directory "$WORKDIR" k8s "$@" +rc=$? + +[ -f /etc/rancher/k3s/config.yaml ] && cp /etc/rancher/k3s/config.yaml "$OUT_CONFIG" +exit "$rc" +SANDBOX +} + +calls() { + cat "$LOG" +} + +config() { + cat "$OUT_CONFIG" +} + +# --- the recipe file itself -------------------------------------------------- + +# This is the parse gate. It fails on the current tree: the `cat <