diff --git a/AGENTS.md b/AGENTS.md index fed0014..031b53b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ Only entries that would be missed without explicit help are included. - **Dotfiles** for a personal workstation (hyprland + Neovim + VST plugin). - No CI or build scripts – the repo is purely configuration data; the only executable content is the `install_*` scripts and the `test/` harness. -- **Testing** lives in `test/`. Run `make test-lint` / `make test-stow` (offline, no Docker) or `make test-smoke` (Docker-based, see `test/Dockerfile` and `README.md → Testing`). The harness currently surfaces pre-existing Makefile bugs by design (missing `/` in `install_*` script paths, `stow nvim` with no `nvim/` package). +- **Testing** lives in `test/`. `make test` runs the offline lint plus the Docker smoke with the real pacman install (`REAL_SMOKE=1`); `make test-lint` / `make test-stow` are the fast offline subset, and `make test-smoke REAL_SMOKE=0` runs dry probes only (see `test/Dockerfile` and `README.md → Testing`). --- ## 2. System Setup diff --git a/Makefile b/Makefile index e34bef3..79f4b88 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,8 @@ buntes-monster: hypr kreation # --------------------------------------------------------------------------- .PHONY: test test-lint test-smoke test-stow test-shellcheck test-lint-docker -# Full test suite: fast offline lint first, then the optional Docker smoke. +# Full test suite: offline lint first, then the Docker smoke (which by +# default runs the real install + verification; see test-smoke below). test: test-lint test-smoke # Fast, offline integrity checks. No Docker required. @@ -58,14 +59,19 @@ test-lint-docker: docker run --rm dotfiles-lint # Docker smoke test: builds the Arch container and runs the dry probes. -# RUN_SMOKE=1 also installs the real pacman packages inside the container. -# RUN_SMOKE=1 RUN_FULL=1 additionally attempts AUR/npm + hypr/kreation. -# SKIP_NET=1 skips anything that contacts the network. -# Example: make test-smoke RUN_SMOKE=1 +# By default (REAL_SMOKE=1) it also installs the real pacman packages inside +# the container and verifies them, so a plain `make test` actually exercises +# the install path. Set REAL_SMOKE=0 (or RUN_SMOKE=0) for the fast dry-probe +# run. RUN_FULL=1 additionally attempts AUR/npm + hypr/kreation. SKIP_NET=1 +# skips anything that contacts the network. +# Example: make test-smoke # real term package install + verify +# make test-smoke REAL_SMOKE=0 # dry probes only +# make test-smoke RUN_FULL=1 # full AUR/npm + hypr/kreation +REAL_SMOKE ?= 1 test-smoke: docker build --target test -t dotfiles-test -f test/Dockerfile . docker run --rm \ - -e RUN_SMOKE="$${RUN_SMOKE:-0}" \ + -e RUN_SMOKE="$${RUN_SMOKE:-$(REAL_SMOKE)}" \ -e RUN_FULL="$${RUN_FULL:-0}" \ -e SKIP_NET=$${SKIP_NET:-0} \ -v "$$PWD":/dotfiles \ diff --git a/README.md b/README.md index cb2fc9b..515478a 100644 --- a/README.md +++ b/README.md @@ -140,14 +140,14 @@ There are **two tiers**: | `make test-stow` | `stow --simulate` for every stow-able package; fails if a package the Makefile stows doesn't exist (e.g. `stow nvim` — nvim lives under `term/`). | No | | `make test-shellcheck` | Runs shellcheck over all tracked scripts (shellcheck must be installed). | No | | `make test-lint-docker` | Builds the `/dev` stage (`archlinux:latest` + shellcheck) and runs `test/lint.sh` inside it. | Yes | -| `make test-smoke` | Builds the `/test` stage and runs the container's dry probes: `make -n` over every target, `stow --simulate` over every Makefile-stowed package, plus `test/lint.sh`. A network-enabled `RUN_SMOKE=1` variant installs the real pacman packages. | Yes | -| `make test` | `test-lint` + `test-smoke`. | Partially | +| `make test-smoke` | Builds the `/test` stage, runs the container's dry probes (`make -n` over every target, `stow --simulate` over every Makefile-stowed package, `test/lint.sh`), **and by default installs the real pacman packages** (`REAL_SMOKE=1`) then verifies them. | Yes | +| `make test` | `test-lint` + `test-smoke` (the real install runs). | Partially | ### Docker smoke details ```bash -make test-smoke # dry probes only (fast, no installs) -make test-smoke RUN_SMOKE=1 # + real pacman installs of the term packages +make test-smoke # real pacman install of the term packages + verify +make test-smoke REAL_SMOKE=0 # dry probes only (fast, no installs) make test-smoke RUN_SMOKE=1 RUN_FULL=1 # + AUR/npm + hypr/kreation/buntes-monster make test-smoke SKIP_NET=1 # skip anything that contacts the network ``` @@ -159,24 +159,19 @@ The container: scripts don't hang on a password prompt, - mounts the live repo at `/dotfiles` (`-v "$PWD":/dotfiles`), so the tests always run against your current checkout, -- stubs host daemon commands (`systemctl`, `updatedb`, `kbuildsycoca6`, - `dbus-update-activation-environment`) as no-ops so nothing talks to a real - systemd/dbus inside the container. +- stubs host-dependent commands as no-ops so nothing talks to a real systemd/dbus + inside the container (`systemctl`, `updatedb`, `kbuildsycoca6`, + `dbus-update-activation-environment`, `chsh`), and routes `yay` through + `pacman` for the pacman-only smoke. -In the `RUN_SMOKE=1` install path, the harness additionally **verifies the -installed packages** (`test/verify_packages.sh`): +In the `REAL_SMOKE=1` (default) install path, the harness additionally +**verifies the installed packages** (`test/verify_packages.sh`): - every `@pacman` entry is confirmed via `pacman -Q ` (covers fonts, libs and python modules that ship no binary), - a curated mapping probes each package's binary with a real `--version`-style call (e.g. `fish --version`, `nvim --version`, `ssh -V`), failing if the binary is missing or does not report a version. -> **Note for the current repo state:** `make test-lint` and `make test-smoke` -> currently **fail** because the harness surfaces pre-existing Makefile bugs -> (missing `/` in the `install_*` script paths and `stow nvim` for a -> non-existent `nvim/` package). That is the intended behaviour — fix the -> Makefile and the tests should go green. - --- **When in doubt:** run `stow --verbose --simulate ` first — it prints what would happen (linking) without changing anything. Never `rm -rf` the `~/.config` of something you still want to keep; move it to a backup directory instead. diff --git a/test/Dockerfile b/test/Dockerfile index 1015bf4..6a39306 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -32,7 +32,11 @@ RUN pacman -Syu --noconfirm \ sudo \ && pacman -Scc --noconfirm -RUN useradd -m -u "$UID_ARG" -s /bin/bash -G wheel "$USERNAME" +# Remove the /etc/skel profile files so `useradd -m` starts the test user with +# a truly empty HOME: the term package owns .bashrc/.bash_profile, and stow +# must be able to take ownership of them without conflicting with copied files. +RUN rm -f /etc/skel/.bashrc /etc/skel/.bash_profile /etc/skel/.bash_logout \ + && useradd -m -u "$UID_ARG" -s /bin/bash -G wheel "$USERNAME" # Passwordless sudo so scripts that call `sudo pacman`, `sudo systemctl`, etc. # do not hang on a password prompt inside the container. diff --git a/test/entrypoint-test.sh b/test/entrypoint-test.sh index 2511ba9..2859a77 100755 --- a/test/entrypoint-test.sh +++ b/test/entrypoint-test.sh @@ -42,6 +42,12 @@ 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. : @@ -59,25 +65,23 @@ header "Dry probes" 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.txt ); then + 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.txt 2>/dev/null + sed 's/^/ /' "$ERR_LOG" 2>/dev/null fi - rm -f err.txt 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.txt; then + 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.txt 2>/dev/null + sed 's/^/ /' "$ERR_LOG" 2>/dev/null fi - rm -f err.txt 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). @@ -125,6 +129,21 @@ 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 pacman-only smoke, so term/setup.sh + # only ever passes repo packages 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" @@ -195,15 +214,18 @@ NOOP # 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.txt; 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.txt 2>/dev/null + sed 's/^/ /' "$ERR_LOG" 2>/dev/null fi - rm -f err.txt else say_fail "package dir missing: $pkg" fi diff --git a/test/probe_stow.sh b/test/probe_stow.sh index cc3c145..e54e955 100755 --- a/test/probe_stow.sh +++ b/test/probe_stow.sh @@ -48,16 +48,20 @@ fi echo "== Probing stow packages: ${PACKAGES[*]} ==" FAIL=0 +ERR_LOG="$(mktemp -t probe_stow.XXXXXX)" + +cleanup() { rm -f "$ERR_LOG"; } +trap cleanup EXIT + for pkg in "${PACKAGES[@]}"; do echo "--- stow --simulate: $pkg ---" - if stow --simulate --verbose=1 --dir="$REPO_ROOT" --target="$REPO_ROOT" "$pkg" >/dev/null 2>probe_err.txt; then + if stow --simulate --verbose=1 --dir="$REPO_ROOT" --target="$REPO_ROOT" "$pkg" >/dev/null 2>"$ERR_LOG"; then echo "ok: stow --simulate '$pkg' resolved without conflicts" else echo "FAIL: stow --simulate '$pkg'" - sed 's/^/ /' probe_err.txt + sed 's/^/ /' "$ERR_LOG" FAIL=$((FAIL+1)) fi - rm -f probe_err.txt done # Cross-check: does the Makefile stow anything that is NOT in our package set? diff --git a/test/verify_packages.sh b/test/verify_packages.sh index 307c3e7..9d4a606 100755 --- a/test/verify_packages.sh +++ b/test/verify_packages.sh @@ -46,7 +46,7 @@ declare -A BINS=( [tree-sitter-cli]="tree-sitter --version" [python]="python --version" [python-pip]="pip3 --version" - [unzip]="unzip --version" + [unzip]="unzip -v" [selene]="selene --version" [tmux]="tmux -V" [yazi]="yazi --version"