mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Four sites where a check could report a verdict it had not established, or detect a failure and then not turn it red. assert_no_panic in both NAT suites read `docker logs ... || true`, so a container that could not be read produced empty output, matched no panic pattern, and returned success. The assertion's failure mode was indistinguishable from its success condition. It now reports that absence of panics is not established. deb-install's apt capture discarded the exit status, so a failed docker exec gave an empty capture that matched neither error pattern and reached the pass branch. The status is now kept and checked before the output is inspected, with the output still printed on failure. wait_for_systemd printed a warning and returned success on timeout, so every check after it read a system that may not have started its units. It now returns non-zero and the caller abandons that distro leg rather than testing an unstarted system. interop's copy of count_log_pattern carried the same defect fixed in rekey: a node whose logs could not be read contributed zero to eight expect-zero assertions. Fixed the same way, and its consumer now reports the unreadable case rather than comparing a sentinel against zero. Each validated by breaking what it guards and by confirming the healthy path is unchanged: an absent container now fails each check where it previously passed, a readable panic-free container still passes, a real panic is still caught, and a genuinely successful install still passes.
522 lines
21 KiB
Bash
Executable File
522 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
# Test the fips Debian package install path across target distros.
|
|
#
|
|
# Each scenario builds (or reuses) the .deb in a Debian 12 cargo-deb
|
|
# builder image (cached), boots a privileged systemd container with
|
|
# TUN access for the target distro, installs the .deb via `apt
|
|
# install ./fips_*.deb`, waits for fips.service + fips-dns.service
|
|
# to come up, and verifies that `dig @127.0.0.53 AAAA <npub>.fips`
|
|
# returns a non-empty AAAA answer through the resolver backend that
|
|
# fips-dns-setup configured. Then exercises fips-gateway against the
|
|
# same daemon to verify the gateway/daemon default-pairing.
|
|
#
|
|
# This is the most thorough test surface — it exercises:
|
|
# - cargo deb packaging (binary stripping, dependency declaration)
|
|
# - dpkg conffile placement (/etc/fips/fips.yaml)
|
|
# - postinst maintainer scripts (systemd unit enablement,
|
|
# fips-dns.service running fips-dns-setup)
|
|
# - The fips, fips-dns, and (optionally) fips-gateway systemd units
|
|
# - End-to-end .fips resolution as a real user would experience it
|
|
#
|
|
# Usage: ./test.sh [scenario ...]
|
|
# No args = run all scenarios.
|
|
# Named args = run only those (e.g., ./test.sh ubuntu26 debian12)
|
|
#
|
|
# Requirements: Docker with privileged container support, /dev/net/tun
|
|
# on the host (standard).
|
|
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
CACHE_DIR="$SCRIPT_DIR/.cache"
|
|
DEB_CACHE_DIR="$CACHE_DIR/deb"
|
|
|
|
# Timeouts
|
|
BOOT_TIMEOUT=30
|
|
SERVICE_TIMEOUT=20
|
|
DAEMON_TIMEOUT=15
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
SKIP=0
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Helpers
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
log() { echo "=== $*"; }
|
|
pass() { echo " PASS: $*"; PASS=$((PASS + 1)); }
|
|
fail() { echo " FAIL: $*"; FAIL=$((FAIL + 1)); }
|
|
skip() { echo " SKIP: $*"; SKIP=$((SKIP + 1)); }
|
|
|
|
cleanup_container() {
|
|
local name="$1"
|
|
docker rm -f "$name" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
build_image() {
|
|
local tag="$1"
|
|
shift
|
|
echo "$@" | docker build -t "$tag" -f - "$REPO_ROOT" >/dev/null 2>&1
|
|
}
|
|
|
|
start_systemd_container_with_tun() {
|
|
local name="$1" image="$2"
|
|
cleanup_container "$name"
|
|
docker run -d --name "$name" \
|
|
--label com.corganlabs.fips-ci=1 \
|
|
--privileged \
|
|
--cgroupns=host \
|
|
--device /dev/net/tun \
|
|
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
|
--tmpfs /run --tmpfs /run/lock \
|
|
"$image" >/dev/null 2>&1
|
|
}
|
|
|
|
wait_for_systemd() {
|
|
local name="$1"
|
|
for _i in $(seq 1 "$BOOT_TIMEOUT"); do
|
|
if docker exec "$name" systemctl is-system-running --wait 2>/dev/null | grep -qE 'running|degraded'; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
# A boot that never reached `running` or `degraded` is not a warning: every
|
|
# check after this point reads a system that may not have started its units,
|
|
# and returning 0 here made the timeout indistinguishable from a clean boot.
|
|
echo " ERROR: systemd did not reach running state in ${BOOT_TIMEOUT}s" >&2
|
|
return 1
|
|
}
|
|
|
|
wait_for_service_active() {
|
|
local name="$1" service="$2" timeout="${3:-$SERVICE_TIMEOUT}"
|
|
for _i in $(seq 1 "$timeout"); do
|
|
if docker exec "$name" systemctl is-active --quiet "$service" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
container_systemd_version() {
|
|
local name="$1"
|
|
docker exec "$name" systemctl --version 2>/dev/null | head -1 \
|
|
| grep -oE '[0-9]+' | head -1
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Build the .deb once in a Debian 12 cargo-deb builder image (cached
|
|
# between runs). Output cached at testing/deb-install/.cache/deb/.
|
|
# Rebuilt if any source/Cargo/packaging file is newer than the cached
|
|
# .deb, or if the .deb is missing.
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
build_deb() {
|
|
mkdir -p "$DEB_CACHE_DIR"
|
|
|
|
local cached_deb
|
|
cached_deb=$(ls "$DEB_CACHE_DIR"/fips_*_amd64.deb 2>/dev/null | head -1)
|
|
|
|
if [ -n "$cached_deb" ] && [ -f "$cached_deb" ]; then
|
|
local newest_src
|
|
newest_src=$(find "$REPO_ROOT/src" "$REPO_ROOT/Cargo.toml" \
|
|
"$REPO_ROOT/Cargo.lock" "$REPO_ROOT/packaging" \
|
|
-type f -printf '%T@\n' 2>/dev/null | sort -nr | head -1)
|
|
local cached_age
|
|
cached_age=$(stat -c '%Y' "$cached_deb" 2>/dev/null || echo 0)
|
|
if awk "BEGIN { exit !($cached_age >= $newest_src) }"; then
|
|
log "Using cached .deb at $cached_deb"
|
|
return 0
|
|
fi
|
|
log "Cached .deb is stale, rebuilding"
|
|
else
|
|
log "No cached .deb, building"
|
|
fi
|
|
|
|
local builder_tag="fips-deb-test:builder"
|
|
log "Building Debian 12 cargo-deb builder image (slow on first run)"
|
|
docker build -t "$builder_tag" -f - "$REPO_ROOT" <<'DOCKERFILE' >/dev/null
|
|
FROM debian:12
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
build-essential pkg-config libdbus-1-dev curl ca-certificates \
|
|
libclang-dev clang && \
|
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
|
sh -s -- -y --default-toolchain stable --profile minimal
|
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
|
RUN cargo install cargo-deb --version 3.6.3 --locked
|
|
WORKDIR /src
|
|
COPY Cargo.toml Cargo.lock build.rs LICENSE README.md ./
|
|
COPY src ./src
|
|
COPY packaging ./packaging
|
|
COPY docs ./docs
|
|
RUN cargo build --release && cargo deb --no-build
|
|
DOCKERFILE
|
|
|
|
if [ ! "$(docker images -q "$builder_tag" 2>/dev/null)" ]; then
|
|
echo " ERROR: builder image build failed"
|
|
return 1
|
|
fi
|
|
|
|
log "Extracting .deb from builder image"
|
|
rm -f "$DEB_CACHE_DIR"/*.deb
|
|
local cid
|
|
cid=$(docker create "$builder_tag")
|
|
docker cp "$cid:/src/target/debian/." "$DEB_CACHE_DIR/" >/dev/null 2>&1
|
|
docker rm "$cid" >/dev/null
|
|
# Cargo-deb leaves intermediate artifacts; keep just the .deb.
|
|
find "$DEB_CACHE_DIR" -mindepth 1 -not -name 'fips_*_amd64.deb' -delete 2>/dev/null || true
|
|
cached_deb=$(ls "$DEB_CACHE_DIR"/fips_*_amd64.deb 2>/dev/null | head -1)
|
|
if [ -n "$cached_deb" ]; then
|
|
log "Cached at $cached_deb ($(stat -c %s "$cached_deb") bytes)"
|
|
else
|
|
echo " ERROR: no .deb produced by cargo-deb"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Scenario runner
|
|
#
|
|
# Args: <distro_label> <docker_base_image>
|
|
# distro_label: short tag for container/image names (e.g. "debian12")
|
|
# docker_base_image: e.g. "debian:12", "ubuntu:26.04"
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
_run_deb_install_scenario() {
|
|
local distro_label="$1"
|
|
local base_image="$2"
|
|
|
|
local name="fips-deb-test-${distro_label}${FIPS_CI_NAME_SUFFIX:-}"
|
|
local image="fips-deb-test:${distro_label}"
|
|
log ".deb install: ${base_image}"
|
|
|
|
build_deb || { fail ".deb build failed"; return; }
|
|
|
|
local cached_deb
|
|
cached_deb=$(ls "$DEB_CACHE_DIR"/fips_*_amd64.deb 2>/dev/null | head -1)
|
|
if [ -z "$cached_deb" ] || [ ! -f "$cached_deb" ]; then
|
|
fail "no .deb available at $DEB_CACHE_DIR"
|
|
return
|
|
fi
|
|
local deb_basename
|
|
deb_basename=$(basename "$cached_deb")
|
|
|
|
# Ubuntu 22.04 bundles systemd-resolved into systemd; other
|
|
# distros require it as a separate package. Compose the apt
|
|
# package list accordingly.
|
|
local apt_packages="systemd iproute2 dbus dnsutils procps"
|
|
if [ "$base_image" != "ubuntu:22.04" ]; then
|
|
apt_packages="systemd systemd-resolved iproute2 dbus dnsutils procps"
|
|
fi
|
|
|
|
log "Building ${base_image} runtime image"
|
|
cp "$cached_deb" "$CACHE_DIR/deb-for-image"
|
|
# Place the .deb under /opt — systemd remounts /tmp during boot
|
|
# (PrivateTmp / tmpfs) which would wipe a .deb COPY'd to /tmp.
|
|
build_image "$image" "$(cat <<DOCKERFILE
|
|
FROM ${base_image}
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
${apt_packages} && \\
|
|
apt-get clean && rm -rf /var/lib/apt/lists/* && \\
|
|
systemctl enable systemd-resolved && \\
|
|
mkdir -p /opt/fips-deb
|
|
COPY testing/deb-install/.cache/deb-for-image /opt/fips-deb/${deb_basename}
|
|
CMD ["/lib/systemd/systemd"]
|
|
DOCKERFILE
|
|
)" || { fail "runtime build failed"; rm -f "$CACHE_DIR/deb-for-image"; return; }
|
|
rm -f "$CACHE_DIR/deb-for-image"
|
|
|
|
start_systemd_container_with_tun "$name" "$image"
|
|
wait_for_systemd "$name" || {
|
|
fail "systemd did not boot in $name; the install checks below would read an unstarted system"
|
|
cleanup_container "$name"
|
|
return
|
|
}
|
|
|
|
# Install the .deb. apt handles dependencies (libc6, systemd,
|
|
# libdbus-1-3) and runs the maintainer scripts (postinst →
|
|
# systemctl enable fips.service; fips-dns.service starts and
|
|
# runs fips-dns-setup).
|
|
log "Installing .deb (apt install /opt/fips-deb/${deb_basename})"
|
|
# `|| true` here used to discard apt's exit status, and an empty capture (a
|
|
# failed `docker exec`) matches neither error pattern below, so a install
|
|
# that never ran reached `pass "apt install completed"`. Keep the capture on
|
|
# failure so the diagnostics below can print it, but remember the status.
|
|
local install_output install_rc=0
|
|
install_output=$(docker exec "$name" bash -c "
|
|
apt-get update >/dev/null 2>&1
|
|
cd /opt/fips-deb && apt-get install -y --no-install-recommends ./${deb_basename} 2>&1
|
|
") || install_rc=$?
|
|
if [ "$install_rc" -ne 0 ]; then
|
|
fail "apt install exited $install_rc"
|
|
echo "$install_output" | tail -20
|
|
cleanup_container "$name"
|
|
return
|
|
fi
|
|
if echo "$install_output" | grep -qE "^E:|errors? were encountered"; then
|
|
fail "apt install reported errors"
|
|
echo "$install_output" | tail -20
|
|
cleanup_container "$name"
|
|
return
|
|
else
|
|
pass "apt install completed"
|
|
fi
|
|
|
|
# Verify shipped files landed where expected.
|
|
if docker exec "$name" test -x /usr/bin/fips; then
|
|
pass "/usr/bin/fips installed"
|
|
else
|
|
fail "/usr/bin/fips missing"
|
|
fi
|
|
if docker exec "$name" test -x /usr/bin/fips-gateway; then
|
|
pass "/usr/bin/fips-gateway installed"
|
|
else
|
|
fail "/usr/bin/fips-gateway missing"
|
|
fi
|
|
if docker exec "$name" test -f /etc/fips/fips.yaml; then
|
|
pass "/etc/fips/fips.yaml conffile installed"
|
|
else
|
|
fail "/etc/fips/fips.yaml conffile missing"
|
|
fi
|
|
|
|
# Verify fips.service is enabled (postinst enables but does not
|
|
# start on fresh install — standard Debian convention).
|
|
if docker exec "$name" systemctl is-enabled --quiet fips.service; then
|
|
pass "fips.service enabled by postinst"
|
|
else
|
|
fail "fips.service not enabled after install"
|
|
fi
|
|
if docker exec "$name" systemctl is-enabled --quiet fips-dns.service; then
|
|
pass "fips-dns.service enabled by postinst"
|
|
else
|
|
fail "fips-dns.service not enabled after install"
|
|
fi
|
|
|
|
# ── nftables firewall baseline (v0.3.0) ──────────────────────────
|
|
# The fips-firewall.service unit ships installed but DISABLED by
|
|
# default; operators opt in explicitly. The fips.nft ruleset is a
|
|
# dpkg conffile, and /etc/fips/fips.d/ is a drop-in directory the
|
|
# ruleset includes via a glob. None of these are exercised by the
|
|
# service-start path below — verify them as static install state.
|
|
if docker exec "$name" test -f /lib/systemd/system/fips-firewall.service; then
|
|
pass "fips-firewall.service unit installed at /lib/systemd/system/"
|
|
else
|
|
fail "fips-firewall.service unit missing from /lib/systemd/system/"
|
|
fi
|
|
# is-enabled prints 'disabled' (and exits non-zero) for an
|
|
# installed-but-not-enabled unit; capture stdout, don't gate on rc.
|
|
fw_state=$(docker exec "$name" systemctl is-enabled fips-firewall.service 2>/dev/null || true)
|
|
if [ "$fw_state" = "disabled" ]; then
|
|
pass "fips-firewall.service disabled by default (opt-in)"
|
|
else
|
|
fail "fips-firewall.service unexpected state: '$fw_state' (expected 'disabled')"
|
|
fi
|
|
if docker exec "$name" test -f /etc/fips/fips.nft && \
|
|
docker exec "$name" dpkg-query -W -f='${Conffiles}\n' fips 2>/dev/null \
|
|
| grep -q '/etc/fips/fips.nft'; then
|
|
pass "/etc/fips/fips.nft installed and registered as dpkg conffile"
|
|
else
|
|
fail "/etc/fips/fips.nft missing or not a registered conffile"
|
|
fi
|
|
if docker exec "$name" test -d /etc/fips/fips.d; then
|
|
fwd_mode=$(docker exec "$name" stat -c '%a %U:%G' /etc/fips/fips.d 2>/dev/null)
|
|
if [ "$fwd_mode" = "755 root:root" ]; then
|
|
pass "/etc/fips/fips.d/ drop-in dir present (755 root:root)"
|
|
else
|
|
fail "/etc/fips/fips.d/ wrong mode/owner: '$fwd_mode' (expected '755 root:root')"
|
|
fi
|
|
else
|
|
fail "/etc/fips/fips.d/ drop-in directory missing"
|
|
fi
|
|
if docker exec "$name" grep -qF 'include "/etc/fips/fips.d/*.nft"' /etc/fips/fips.nft; then
|
|
pass "fips.nft includes drop-in glob /etc/fips/fips.d/*.nft"
|
|
else
|
|
fail "fips.nft missing drop-in include for /etc/fips/fips.d/*.nft"
|
|
fi
|
|
|
|
# Start the services as a simulated boot. (On a real system,
|
|
# they'd come up on next reboot.)
|
|
docker exec "$name" systemctl start fips.service 2>&1 || true
|
|
docker exec "$name" systemctl start fips-dns.service 2>&1 || true
|
|
|
|
if wait_for_service_active "$name" fips.service; then
|
|
pass "fips.service active after explicit start"
|
|
else
|
|
fail "fips.service did not become active in ${SERVICE_TIMEOUT}s"
|
|
echo " --- fips.service status ---"
|
|
docker exec "$name" systemctl status fips.service --no-pager 2>&1 | tail -20
|
|
echo " --- fips.service journal ---"
|
|
docker exec "$name" journalctl -u fips.service --no-pager 2>&1 | tail -20
|
|
cleanup_container "$name"
|
|
return
|
|
fi
|
|
|
|
# Wait for the DNS responder to bind on the daemon's [::1]:5354.
|
|
local ready=0
|
|
for _i in $(seq 1 "$DAEMON_TIMEOUT"); do
|
|
if docker exec "$name" ss -uln 2>/dev/null | grep -q ':5354'; then
|
|
ready=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [ "$ready" = "1" ]; then
|
|
pass "fips DNS listener up on port 5354"
|
|
else
|
|
fail "fips DNS listener did not appear within ${DAEMON_TIMEOUT}s"
|
|
cleanup_container "$name"
|
|
return
|
|
fi
|
|
|
|
# Wait for fips-dns.service. This should have run fips-dns-setup
|
|
# which configures the resolver backend and writes
|
|
# /run/fips/dns-backend.
|
|
sleep 2
|
|
local backend
|
|
backend=$(docker exec "$name" cat /run/fips/dns-backend 2>/dev/null || echo "(missing)")
|
|
local ver
|
|
ver=$(container_systemd_version "$name")
|
|
local expected_backend
|
|
if [ -n "$ver" ] && [ "$ver" -ge 258 ]; then
|
|
expected_backend="dns-delegate"
|
|
else
|
|
expected_backend="global-drop-in"
|
|
fi
|
|
if [ "$backend" = "$expected_backend" ]; then
|
|
pass "fips-dns.service picked $expected_backend backend (systemd $ver)"
|
|
else
|
|
fail "expected $expected_backend (systemd $ver), got: $backend"
|
|
echo " --- fips-dns.service journal ---"
|
|
docker exec "$name" journalctl -u fips-dns.service --no-pager 2>&1 | tail -20
|
|
fi
|
|
|
|
# Get the daemon's npub via fipsctl. Works for both ephemeral
|
|
# and persistent identity (no need to override the conffile).
|
|
local npub
|
|
npub=$(docker exec "$name" fipsctl show status 2>/dev/null \
|
|
| grep -oE 'npub1[a-z0-9]+' | head -1)
|
|
if [ -z "$npub" ]; then
|
|
fail "could not read npub from fipsctl show status"
|
|
cleanup_container "$name"
|
|
return
|
|
fi
|
|
echo " daemon npub: $npub"
|
|
|
|
# The actual end-to-end test: a stock dpkg-installed deployment
|
|
# must successfully resolve a .fips query through the system
|
|
# resolver. This covers the full path: maintainer scripts ran,
|
|
# service started, DNS responder bound, resolver backend
|
|
# configured, query routed and answered.
|
|
sleep 1
|
|
local stub_output
|
|
stub_output=$(docker exec "$name" dig +tries=1 +time=3 @127.0.0.53 AAAA "${npub}.fips" 2>&1)
|
|
if echo "$stub_output" | grep -qE '^[a-zA-Z0-9].*\sAAAA\s+[0-9a-f:]+'; then
|
|
pass "end-to-end dig @127.0.0.53 returns AAAA on stock .deb install"
|
|
else
|
|
fail "end-to-end dig @127.0.0.53 did not return AAAA"
|
|
echo " --- dig output ---"
|
|
echo "$stub_output" | tail -15
|
|
echo " --- resolved status ---"
|
|
docker exec "$name" resolvectl status 2>&1 | tail -25 || true
|
|
echo " --- fips journal ---"
|
|
docker exec "$name" journalctl -u fips.service --no-pager 2>&1 | tail -10
|
|
fi
|
|
|
|
# Verify fips-gateway can run against the installed daemon. Tests
|
|
# the gateway/daemon default-pairing on a real .deb install (no
|
|
# custom config). Requires enabling the unit (it's not in the
|
|
# default preset) and ipv6 forwarding (gateway checks before
|
|
# the DNS upstream check).
|
|
docker exec "$name" sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1 || true
|
|
docker exec "$name" bash -c '
|
|
systemctl unmask fips-gateway.service 2>/dev/null
|
|
# Patch in a minimal gateway config since the shipped fips.yaml
|
|
# has gateway disabled by default.
|
|
cp /etc/fips/fips.yaml /etc/fips/fips.yaml.orig
|
|
cat >> /etc/fips/fips.yaml <<EOF
|
|
gateway:
|
|
enabled: true
|
|
pool: "fd01::/112"
|
|
lan_interface: "eth0"
|
|
EOF
|
|
systemctl restart fips.service
|
|
' >/dev/null 2>&1
|
|
|
|
sleep 3
|
|
if wait_for_service_active "$name" fips.service 5; then
|
|
:
|
|
else
|
|
fail "fips.service did not stay up after gateway-enable restart"
|
|
fi
|
|
|
|
docker exec "$name" systemctl start fips-gateway.service >/dev/null 2>&1 || true
|
|
sleep 3
|
|
if docker exec "$name" journalctl -u fips-gateway.service --no-pager 2>/dev/null \
|
|
| grep -q "DNS upstream is reachable"; then
|
|
pass "fips-gateway reaches DNS upstream at [::1]:5354 via .deb install"
|
|
else
|
|
fail "fips-gateway DNS upstream check failed against installed daemon"
|
|
echo " --- fips-gateway journal ---"
|
|
docker exec "$name" journalctl -u fips-gateway.service --no-pager 2>&1 | tail -15
|
|
fi
|
|
|
|
cleanup_container "$name"
|
|
}
|
|
|
|
# Per-distro wrappers
|
|
test_debian12() { _run_deb_install_scenario debian12 debian:12; }
|
|
test_debian13() { _run_deb_install_scenario debian13 debian:trixie; }
|
|
test_ubuntu22() { _run_deb_install_scenario ubuntu22 ubuntu:22.04; }
|
|
test_ubuntu24() { _run_deb_install_scenario ubuntu24 ubuntu:24.04; }
|
|
test_ubuntu26() { _run_deb_install_scenario ubuntu26 ubuntu:26.04; }
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Main
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
ALL_SCENARIOS="debian12 debian13 ubuntu22 ubuntu24 ubuntu26"
|
|
|
|
if [ $# -eq 0 ]; then
|
|
scenarios="$ALL_SCENARIOS"
|
|
else
|
|
scenarios="$*"
|
|
fi
|
|
|
|
for scenario in $scenarios; do
|
|
case "$scenario" in
|
|
debian12) test_debian12 ;;
|
|
debian13) test_debian13 ;;
|
|
ubuntu22) test_ubuntu22 ;;
|
|
ubuntu24) test_ubuntu24 ;;
|
|
ubuntu26) test_ubuntu26 ;;
|
|
*)
|
|
echo "Unknown scenario: $scenario"
|
|
echo "Available: $ALL_SCENARIOS"
|
|
exit 1
|
|
;;
|
|
esac
|
|
echo
|
|
done
|
|
|
|
echo "═══════════════════════════════════════"
|
|
echo "Results: $PASS passed, $FAIL failed, $SKIP skipped"
|
|
echo "═══════════════════════════════════════"
|
|
|
|
# A skip must not read as a pass. Nothing calls skip() today, so SKIP is
|
|
# always 0 and this changes no current outcome -- which is exactly why it is
|
|
# worth adding now: the helper and the counter already existed and were
|
|
# reported, so a later skip path would have printed "N skipped" next to a
|
|
# zero exit and looked like coverage. Gate on it before that happens.
|
|
if [ "$SKIP" -ne 0 ]; then
|
|
echo "FAIL: $SKIP check(s) skipped; a skipped check is not a passed one." >&2
|
|
echo " Either make the check run here, or remove it and record the" >&2
|
|
echo " gap deliberately rather than skipping it at runtime." >&2
|
|
fi
|
|
|
|
[ "$FAIL" -eq 0 ] && [ "$SKIP" -eq 0 ]
|