327 lines
13 KiB
Bash
Executable File
327 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
# test/entrypoint-test.sh
|
|
#
|
|
# Runs inside the docker `test` stage. The repo is mounted read-write at
|
|
# /dotfiles (see the Makefile's `docker run -v "$PWD":/dotfiles`).
|
|
#
|
|
# Two tiers:
|
|
# 1. DRY PROBES (always, fast, offline)
|
|
# - `make -n` dry-runs to catch Makefile typos / missing scripts
|
|
# - `stow --simulate` to prove every stow target resolves
|
|
# 2. REAL SMOKE (guarded by RUN_SMOKE=1)
|
|
# - resolves every @pacman entry against the repo DB
|
|
# - exercises term/setup.sh (pacman + npm) and re-runs it to prove
|
|
# idempotency
|
|
# - verifies stow actually creates symlinks into $HOME
|
|
# AUR + kreation/buntes-monster only run with RUN_FULL=1.
|
|
#
|
|
# Env flags (set via `docker run -e`):
|
|
# RUN_SMOKE=1 run the real pacman+npm install smoke
|
|
# RUN_FULL=1 also attempt AUR + hypr/kreation/buntes-monster
|
|
# SKIP_NET=1 pretend no network; skip anything that needs the mirror
|
|
|
|
set -uo pipefail
|
|
|
|
# Resolve the repo root: prefer the container's /dotfiles mount, otherwise
|
|
# fall back to the directory above this script (a local checkout).
|
|
if [[ -d /dotfiles ]]; then
|
|
cd /dotfiles || { echo "FATAL: repo not mounted at /dotfiles"; exit 1; }
|
|
else
|
|
cd "$(dirname "${BASH_SOURCE[0]}")/.." || { echo "FATAL: cannot locate repo root"; exit 1; }
|
|
fi
|
|
REPO="$(pwd)"
|
|
|
|
# Guard: both resolution paths must find a real checkout.
|
|
if [[ ! -f "$REPO/Makefile" || ! -d "$REPO/test" ]]; then
|
|
echo "FATAL: '$REPO' does not look like the dotfiles repo root." >&2
|
|
echo " In docker, run with -v \"\$PWD\":/dotfiles ." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
say_ok() { printf ' ok: %s\n' "$*"; PASS=$((PASS+1)); }
|
|
say_fail(){ printf ' FAIL: %s\n' "$*"; FAIL=$((FAIL+1)); }
|
|
header() { printf '\n==== %s ====\n' "$*"; }
|
|
|
|
# Scratch file for captured stderr; kept OUTSIDE the repo mount so it never
|
|
# leaks into the host workspace (the mount is /dotfiles).
|
|
ERR_LOG="$(mktemp -t entrypoint.XXXXXX)"
|
|
cleanup() { rm -f "$ERR_LOG"; }
|
|
trap cleanup EXIT
|
|
|
|
if command -v git >/dev/null 2>&1 && [[ ! -d .git ]]; then
|
|
# when mounted from a worktree, .git may be a file; fine.
|
|
:
|
|
fi
|
|
|
|
echo "entrypoint-test.sh: running as $(id -un) in $(pwd)"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. DRY PROBES - always run, cheap, offline.
|
|
# ---------------------------------------------------------------------------
|
|
header "Dry probes"
|
|
|
|
# 1a. Makefile dry runs for every target.
|
|
# Discover targets dynamically so we never drift from the Makefile.
|
|
targets=$(grep -oE '^[A-Za-z0-9_-]+:' Makefile | tr -d ':' | grep -v '^test' | sort -u)
|
|
for target in $targets; do
|
|
# `make -n` must not error out from missing scripts / dirs.
|
|
if ( cd "$REPO" && make -n "$target" >/dev/null 2>"$ERR_LOG" ); then
|
|
say_ok "make -n $target parses"
|
|
else
|
|
say_fail "make -n $target (typo? missing script?)"
|
|
sed 's/^/ /' "$ERR_LOG" 2>/dev/null
|
|
fi
|
|
done
|
|
|
|
# 1b. stow --simulate for every package the Makefile stows.
|
|
while IFS= read -r pkg; do
|
|
[[ -z "$pkg" ]] && continue
|
|
if stow --simulate --dir="$REPO" --target="$REPO" "$pkg" 2>"$ERR_LOG"; then
|
|
say_ok "stow --simulate $pkg resolves"
|
|
else
|
|
say_fail "stow --simulate $pkg (non-existent package or conflict)"
|
|
sed 's/^/ /' "$ERR_LOG" 2>/dev/null
|
|
fi
|
|
done < <(grep -P '^\t' Makefile | grep -oE 'stow [A-Za-z0-9_-]+' | awk '{print $2}' | sort -u)
|
|
|
|
# 1c. run the repo's own lint + probe scripts (they're mounted here).
|
|
if [[ -x test/lint.sh ]] || [[ -f test/lint.sh ]]; then
|
|
if bash test/lint.sh >/dev/null 2>&1; then
|
|
say_ok "test/lint.sh"
|
|
else
|
|
say_fail "test/lint.sh (see logs above / rerun outside container)"
|
|
fi
|
|
else
|
|
say_fail "test/lint.sh not present in mount"
|
|
fi
|
|
|
|
header "Smoke gates"
|
|
echo " RUN_SMOKE=${RUN_SMOKE:-0} RUN_FULL=${RUN_FULL:-0} SKIP_NET=${SKIP_NET:-0}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. REAL SMOKE (RUN_SMOKE=1)
|
|
# ---------------------------------------------------------------------------
|
|
if [[ "${RUN_SMOKE:-0}" == "1" ]]; then
|
|
header "Real smoke (RUN_SMOKE=1)"
|
|
|
|
# 2a. Allow scripts to use sudo without a password (already NOPASSWD via
|
|
# the Dockerfile for the wheel group).
|
|
#
|
|
# 2b. Override host-daemon commands with no-ops so nothing tries to talk
|
|
# to systemd/dbus/dbus in the container.
|
|
mkdir -p "$HOME/bin"
|
|
cat > "$HOME/bin/systemctl" <<'NOOP'
|
|
#!/bin/bash
|
|
echo "[stub] systemctl $*"
|
|
exit 0
|
|
NOOP
|
|
cat > "$HOME/bin/dbus-update-activation-environment" <<'NOOP'
|
|
#!/bin/bash
|
|
echo "[stub] dbus-update-activation-environment $*"
|
|
exit 0
|
|
NOOP
|
|
cat > "$HOME/bin/kbuildsycoca6" <<'NOOP'
|
|
#!/bin/bash
|
|
echo "[stub] kbuildsycoca6 $*"
|
|
exit 0
|
|
NOOP
|
|
cat > "$HOME/bin/updatedb" <<'NOOP'
|
|
#!/bin/bash
|
|
echo "[stub] updatedb $*"
|
|
exit 0
|
|
NOOP
|
|
# chsh has no functional /etc/passwd in the container; make it a no-op so
|
|
# set_fish_default_shell doesn't fail (documented in the header comment).
|
|
cat > "$HOME/bin/chsh" <<'NOOP'
|
|
#!/bin/bash
|
|
echo "[stub] chsh $*"
|
|
exit 0
|
|
NOOP
|
|
# The AUR section is stripped for the smoke, so term/setup.sh only ever
|
|
# passes repo packages (+ npm) here; route yay through pacman directly.
|
|
# --noconfirm is needed because the container isn't interactive.
|
|
cat > "$HOME/bin/yay" <<'NOOP'
|
|
#!/bin/bash
|
|
echo "[stub] yay $* (routed through pacman)"
|
|
exec sudo pacman --noconfirm "$@"
|
|
NOOP
|
|
chmod +x "$HOME/bin/"*
|
|
export PATH="$HOME/bin:$PATH"
|
|
|
|
# 2c. setup.sh package parsing sanity (mirrors read_packages) for each
|
|
# package dir: basic/term/utils.
|
|
for d in basic term utils; do
|
|
if ( cd "$REPO" && bash -n "$d/setup.sh" ); then
|
|
say_ok "$d/setup.sh syntax OK"
|
|
else
|
|
say_fail "$d/setup.sh syntax error"
|
|
fi
|
|
pacman_count=$(awk '
|
|
/^@pacman/{s="pacman"; next}
|
|
/^@aur/{s="aur"; next}
|
|
/^@npm/{s="npm"; next}
|
|
/^[[:space:]]*#/{next}
|
|
/^[[:space:]]*$/{next}
|
|
s=="pacman"{pc++} s=="aur"{ac++} s=="npm"{nc++}
|
|
END{printf "%d %d %d", pc,ac,nc}
|
|
' "$REPO/$d/packages.txt")
|
|
read -r PC AC NC <<< "$pacman_count"
|
|
say_ok "$d/packages.txt -> ${PC} pacman, ${AC} aur, ${NC} npm entries"
|
|
done
|
|
|
|
# 2c2. Every @pacman entry in every packages.txt must resolve in the
|
|
# repo DB, using the same resolution as the installer (`pacman -S
|
|
# --print-format`). Aliases/provides count as resolved (locate ->
|
|
# plocate, ttf-font-awesome -> woff2-font-awesome), while a typo'd or
|
|
# AUR-only name (e.g. `rd`) fails here before any install happens.
|
|
header "package resolvability (@pacman entries)"
|
|
resolv_fail=0
|
|
for d in basic term utils; do
|
|
while IFS= read -r pkg; do
|
|
[[ -z "$pkg" ]] && continue
|
|
if pacman -S --print-format '%n' "$pkg" >/dev/null 2>&1; then
|
|
say_ok "pacman resolves $pkg ($d)"
|
|
else
|
|
say_fail "pacman cannot resolve $pkg ($d) - target not found"
|
|
resolv_fail=1
|
|
fi
|
|
done < <(awk '
|
|
BEGIN{s="pacman"}
|
|
/^@pacman/{s="pacman"; next}
|
|
/^@aur/{s="skip"; next}
|
|
/^@npm/{s="skip"; next}
|
|
/^[[:space:]]*#/{next} /^[[:space:]]*$/{next}
|
|
s=="pacman"{ sub(/^[^\/]*\//,""); print }
|
|
' "$REPO/$d/packages.txt")
|
|
done
|
|
|
|
# 2d. If network allowed and not skipped, actually install the pacman
|
|
# and npm sections of term/setup.sh (mirrors `make term` without AUR;
|
|
# the @aur section stays RUN_FULL-gated since it needs yay/AUR builds).
|
|
if [[ "${SKIP_NET:-0}" != "1" ]]; then
|
|
header "make term (pacman + npm packages, AUR skipped)"
|
|
# Patch packages.txt: keep @pacman and @npm (@npm runs via sudo npm
|
|
# in the container), drop @aur so we don't hit AUR builds.
|
|
cp "$REPO/term/packages.txt" "$HOME/packages.txt.full"
|
|
awk '
|
|
/^@aur$/{s="skip"}; /^@npm$/{s="npm"}; /^@pacman$/{s="pacman"}
|
|
{ if (s!="skip" && NF>0 && $1 !~ /^#/) print }
|
|
' "$REPO/term/packages.txt" > "$HOME/packages.txt.term-smoke"
|
|
# pacman-only list for verify_packages.sh (npm packages are not in
|
|
# the pacman DB).
|
|
awk '
|
|
/^@aur$/{s="skip"}; /^@npm$/{s="skip"}; /^@pacman$/{s="pacman"}
|
|
{ if (s!="skip" && NF>0 && $1 !~ /^#/) print }
|
|
' "$REPO/term/packages.txt" > "$HOME/packages.txt.term-only"
|
|
# set a scratch HOME for setup.sh so it doesn't touch the real one.
|
|
mkdir -p "$HOME/term"
|
|
cp "$REPO/term/setup.sh" "$HOME/term/setup.sh"
|
|
cp "$HOME/packages.txt.term-smoke" "$HOME/term/packages.txt"
|
|
# chsh is stubbed via $HOME/bin so even `chsh` works without a pty.
|
|
setup_ok=0
|
|
if ( cd "$HOME/term" && bash setup.sh ) ; then
|
|
say_ok "term/setup.sh (pacman+npm) completed"
|
|
setup_ok=1
|
|
else
|
|
say_fail "term/setup.sh (pacman+npm) failed (exit $?)"
|
|
fi
|
|
|
|
# 2d1. Idempotency probe: a second run must also succeed. Catches
|
|
# steps that break on re-runs over an already-populated state (e.g.
|
|
# `ya pkg add` failing with "already exists").
|
|
if [[ "$setup_ok" == "1" ]]; then
|
|
if ( cd "$HOME/term" && bash setup.sh ) ; then
|
|
say_ok "term/setup.sh re-run: idempotent"
|
|
else
|
|
say_fail "term/setup.sh re-run failed (exit $?) - not idempotent"
|
|
fi
|
|
else
|
|
echo " skipping idempotency probe (first run did not complete)"
|
|
fi
|
|
|
|
# 2d2. Verify that every package is really installed and its binary
|
|
# runs. Only meaningful if the install itself succeeded.
|
|
if [[ "$setup_ok" == "1" ]]; then
|
|
if [[ -x test/verify_packages.sh ]]; then
|
|
header "verify installed packages"
|
|
if bash test/verify_packages.sh "$HOME/packages.txt.term-only"; then
|
|
say_ok "verify_packages.sh: all packages verified"
|
|
else
|
|
say_fail "verify_packages.sh: one or more packages failed verification (see above)"
|
|
fi
|
|
else
|
|
say_fail "test/verify_packages.sh missing"
|
|
fi
|
|
else
|
|
echo " skipping package verification (install did not complete)"
|
|
fi
|
|
else
|
|
echo " SKIP_NET=1 -> skipping real package install and verification"
|
|
fi
|
|
|
|
# 2e. stow --actual into $HOME to prove real symlinks are created.
|
|
header "stow real symlinks into \$HOME"
|
|
# term/setup.sh may have just re-created the yazi plugin (`ya pkg add`)
|
|
# as real files. The repo vendors the same plugin under term/.config/yazi,
|
|
# so drop the duplicate first, letting stow take ownership of that config.
|
|
rm -rf "$HOME/.config/yazi"
|
|
for pkg in term hypr kreation local_ai; do
|
|
if [[ -d "$REPO/$pkg" ]]; then
|
|
if stow --dir="$REPO" --target="$HOME" "$pkg" 2>"$ERR_LOG"; then
|
|
say_ok "stow $pkg into \$HOME"
|
|
else
|
|
say_fail "stow $pkg into \$HOME"
|
|
sed 's/^/ /' "$ERR_LOG" 2>/dev/null
|
|
fi
|
|
else
|
|
say_fail "package dir missing: $pkg"
|
|
fi
|
|
done
|
|
# sanity: representative symlinks must now exist in $HOME.
|
|
missing=""
|
|
for link in .config/fish .config/hypr .vst3/MT-PowerDrumKit.vst3; do
|
|
[[ -e "$HOME/$link" ]] || missing="$missing $link"
|
|
done
|
|
if [[ -z "$missing" ]]; then
|
|
say_ok "stow created representative \$HOME symlinks"
|
|
else
|
|
say_fail "missing expected stow symlinks:$missing"
|
|
fi
|
|
|
|
# 2f. kreation package: verify the VST path is stow-able and present.
|
|
header "kreation VST"
|
|
if [[ -f "$REPO/kreation/.vst3/MT-PowerDrumKit.vst3/Contents/x86_64-linux/MT-PowerDrumKit.so" ]]; then
|
|
say_ok "MT-PowerDrumKit.vst3 binary present"
|
|
else
|
|
say_fail "MT-PowerDrumKit.vst3 binary missing"
|
|
fi
|
|
else
|
|
echo " RUN_SMOKE not set -> skipping real installs & symlink creation."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. FULL (RUN_FULL=1) - opt-in heavy path (AUR/npm/hypr/kreation)
|
|
# ---------------------------------------------------------------------------
|
|
if [[ "${RUN_FULL:-0}" == "1" ]] && [[ "${SKIP_NET:-0}" != "1" ]]; then
|
|
header "Full smoke (RUN_FULL=1)"
|
|
if ( cd "$REPO" && make buntes-monster ); then
|
|
say_ok "make buntes-monster"
|
|
else
|
|
say_fail "make buntes-monster (exit $?)"
|
|
fi
|
|
else
|
|
echo " RUN_FULL not set (or SKIP_NET) -> skipping full/hypr/kreation install."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Summary & exit code
|
|
# ---------------------------------------------------------------------------
|
|
echo
|
|
echo "=============================================="
|
|
echo "entrypoint-test.sh: $PASS passed, $FAIL failed"
|
|
echo "=============================================="
|
|
# Exit non-zero if anything failed.
|
|
[[ $FAIL -eq 0 ]]
|