#!/bin/bash # test/verify_packages.sh # # Post-install verification of the packages the smoke test installs. # Given a package-list file (one package per line, `@section` markers # allowed - only the section content is verified), it checks that every # listed package is: # 1. installed in the pacman DB -> `pacman -Q ` # 2. its expected binary exists and reports a version (curated mapping) # # Layer 2 runs a real `--version`-style probe for packages that install a # binary, so a broken/renamed executable is caught even if pacman reports # the package as installed. Fonts/libs/modules without a binary are verified # by Layer 1 only. # # Usage: bash test/verify_packages.sh set -uo pipefail PKGS_FILE="${1:?usage: verify_packages.sh }" [[ -f "$PKGS_FILE" ]] || { echo "verify_packages.sh: not a file: $PKGS_FILE" >&2; exit 1; } PASS=0 FAIL=0 say_ok() { printf ' ok: %s\n' "$*"; PASS=$((PASS+1)); } say_fail() { printf ' FAIL: %s\n' "$*"; FAIL=$((FAIL+1)); } # --------------------------------------------------------------------------- # Curated mapping: package -> "binary version_flag" # - packages not listed here are checked with pacman -Q only # - flag missing (empty) => existence check only # --------------------------------------------------------------------------- declare -A BINS=( [fish]="fish --version" [kitty]="kitty --version" [neovim]="nvim --version" [openssh]="ssh -V" [rclone]="rclone --version" [rsync]="rsync --version" [unison]="unison -version" [stow]="stow --version" [nodejs]="node --version" [npm]="npm --version" [starship]="starship --version" [fastfetch]="fastfetch --version" [tree-sitter-cli]="tree-sitter --version" [python]="python --version" [python-pip]="pip3 --version" [unzip]="unzip --version" [selene]="selene --version" [tmux]="tmux -V" [yazi]="yazi --version" [zoxide]="zoxide --version" [fzf]="fzf --version" [chromium]="chromium --version" [locate]="locate --version" ) # python-pip may expose `pip` instead of `pip3` on some setups; treat them # as interchangeable so the probe does not spuriously fail. probe_pip() { for b in pip3 pip; do if command -v "$b" >/dev/null 2>&1; then out=$("$b" --version 2>&1) && [[ -n "$out" ]] && { say_ok "$b --version ($out)"; return 0; } fi done say_fail "python-pip: neither pip3 nor pip found/probe failed" return 1 } echo "== Verifying installed packages from: $PKGS_FILE ==" while IFS= read -r line || [[ -n "$line" ]]; do line="${line%%#*}" # strip trailing comments line="${line// /}" # drop inline whitespace [[ -z "$line" ]] && continue [[ "$line" == @* ]] && continue # skip @pacman/@aur/@npm section markers pkg="${line#*/}" # strip repo prefix (extra/kitty -> kitty) if [[ -z "$pkg" ]]; then say_fail "empty package name in '$line'" continue fi # Layer 1: installed in the pacman DB? if pacman -Q "$pkg" >/dev/null 2>&1; then say_ok "pacman -Q $pkg (installed)" else say_fail "pacman -Q $pkg (not installed)" continue fi # Layer 2: runtime binary probe (curated mapping)? spec="${BINS[$pkg]:-}" [[ -z "$spec" ]] && continue # no binary (font/lib/module) - Layer 1 only # python-pip has special multi-candidate handling [[ "$pkg" == "python-pip" ]] && { probe_pip; continue; } read -r bin flag <<< "$spec" if command -v "$bin" >/dev/null 2>&1; then if [[ -z "$flag" ]]; then say_ok "command -v $bin (present)" else out=$("$bin" $flag 2>&1) && [[ -n "$out" ]] if [[ $? -eq 0 ]]; then say_ok "$bin $flag ($out)" else say_fail "$bin $flag (binary present but probe failed)" fi fi else say_fail "command -v $bin (expected from package '$pkg')" fi done < "$PKGS_FILE" echo echo "----------------------------------------------" echo "verify_packages.sh: $PASS passed, $FAIL failed" echo "----------------------------------------------" [[ $FAIL -eq 0 ]]