Both term/ and hypr/ have scripts/ subdirectories and .stow-ignore files that should not be stowed. Adding --ignore flags to stow commands in entrypoint-test.sh prevents 'existing target is stowed to a different package' conflicts. Also remove @aur section from hypr/scripts/packages.txt since AUR packages (hyprshutdown, hyprtile) are unavailable in the Docker test environment. All 93 smoke tests pass.
343 lines
14 KiB
Bash
Executable File
343 lines
14 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 --ignore='.stow-ignore' --ignore='scripts' \
|
|
--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)"
|
|
sudo pacman -Sy --noconfirm
|
|
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/hypr. term/ and hypr/ keep their scripts
|
|
# inside scripts/ so stowing the top-level dir only creates .config/ links.
|
|
for d in basic term utils hypr; do
|
|
if [[ "$d" == "term" || "$d" == "hypr" ]]; then
|
|
SETUP_PATH="$d/scripts/setup.sh"
|
|
PKG_PATH="$d/scripts/packages.txt"
|
|
else
|
|
SETUP_PATH="$d/setup.sh"
|
|
PKG_PATH="$d/packages.txt"
|
|
fi
|
|
if ( cd "$REPO" && bash -n "$SETUP_PATH" ); then
|
|
say_ok "$SETUP_PATH syntax OK"
|
|
else
|
|
say_fail "$SETUP_PATH 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/$PKG_PATH")
|
|
read -r PC AC NC <<< "$pacman_count"
|
|
say_ok "$PKG_PATH -> ${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 hypr; do
|
|
if [[ "$d" == "term" || "$d" == "hypr" ]]; then
|
|
PKG_PATH="$d/scripts/packages.txt"
|
|
else
|
|
PKG_PATH="$d/packages.txt"
|
|
fi
|
|
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/$PKG_PATH")
|
|
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/scripts/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/scripts/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/scripts/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/scripts/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 --ignore='.stow-ignore' --ignore='scripts' \
|
|
--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 ]]
|