make lint runs shellcheck on every tracked bash script, make test runs the bats suite in tests/, and make check runs both. The tests cover the update orchestrator (hook isolation, failure reporting, pull and free-space handling) and utils.sh (migrations, package install and removal, services, initramfs build check), using stub commands so they never touch the real system. Fixes from the first lint run: 20-aur.sh stops if a cd fails rather than building or deleting in the wrong directory, and 80-gnome-extensions.sh refuses to rm -rf with an empty path. The rest are false positives, silenced in .shellcheckrc or per line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
222 lines
6.0 KiB
Bash
222 lines
6.0 KiB
Bash
#!/usr/bin/env bats
|
|
|
|
load test_helper
|
|
|
|
# --- run_migrations ---------------------------------------------------------
|
|
|
|
# migration NAME [EXIT]: add migrations/NAME.sh that logs its name and exits EXIT
|
|
migration() {
|
|
mkdir -p "$COREDESKTOP/migrations"
|
|
printf '#!/bin/bash\necho %s >> "%s"\nexit %s\n' "$1" "$RAN" "${2:-0}" \
|
|
> "$COREDESKTOP/migrations/$1.sh"
|
|
}
|
|
|
|
setup_migrations() {
|
|
export COREDESKTOP="$BATS_TEST_TMPDIR/coredesktop"
|
|
RAN="$BATS_TEST_TMPDIR/ran"
|
|
STATE="$HOME/.local/state/coredesktop/migrations"
|
|
source "$REPO/utils.sh"
|
|
}
|
|
|
|
@test "run_migrations runs each migration once, oldest first" {
|
|
setup_migrations
|
|
migration 1792592710
|
|
migration 1792592700
|
|
migration 1792592720
|
|
|
|
run run_migrations
|
|
[ "$status" -eq 0 ]
|
|
[ "$(cat "$RAN")" = "$(printf '1792592700\n1792592710\n1792592720')" ]
|
|
[ -f "$STATE/1792592700.sh" ]
|
|
[ -f "$STATE/1792592720.sh" ]
|
|
|
|
run run_migrations
|
|
[ "$status" -eq 0 ]
|
|
[ "$(wc -l < "$RAN")" -eq 3 ]
|
|
}
|
|
|
|
@test "run_migrations stops at a failure and retries it next time" {
|
|
setup_migrations
|
|
migration 1792592700
|
|
migration 1792592710 1
|
|
migration 1792592720
|
|
|
|
run run_migrations
|
|
[ "$status" -eq 1 ]
|
|
[ "$(cat "$RAN")" = "$(printf '1792592700\n1792592710')" ]
|
|
[ -f "$STATE/1792592700.sh" ]
|
|
[ ! -f "$STATE/1792592710.sh" ]
|
|
[ ! -f "$STATE/1792592720.sh" ]
|
|
|
|
migration 1792592710
|
|
run run_migrations
|
|
[ "$status" -eq 0 ]
|
|
[ "$(tail -n 2 "$RAN")" = "$(printf '1792592710\n1792592720')" ]
|
|
}
|
|
|
|
@test "run_migrations does nothing without a migrations dir" {
|
|
setup_migrations
|
|
run run_migrations
|
|
[ "$status" -eq 0 ]
|
|
[ ! -e "$RAN" ]
|
|
}
|
|
|
|
@test "migrations are named by timestamp so they sort in the order written" {
|
|
for file in "$REPO"/migrations/*; do
|
|
[[ "$(basename "$file")" =~ ^[0-9]{10}\.sh$ ]] || {
|
|
echo "bad migration name: $file"
|
|
return 1
|
|
}
|
|
done
|
|
}
|
|
|
|
# --- install_packages / remove_packages -------------------------------------
|
|
|
|
# A fake pacman that knows about the packages in $INSTALLED, of which those in
|
|
# $AS_DEPS were installed as dependencies
|
|
stub_pacman() {
|
|
stub pacman '
|
|
case "$1" in
|
|
-Qi) [[ " $INSTALLED " == *" $2 "* ]] ;;
|
|
-Qg) exit 1 ;;
|
|
-Qdq) shift; for p; do [[ " $AS_DEPS " == *" $p "* ]] && echo "$p"; done; exit 0 ;;
|
|
esac'
|
|
stub sudo
|
|
stub yay
|
|
}
|
|
|
|
@test "install_packages installs only the missing packages" {
|
|
export INSTALLED="git vim" AS_DEPS=""
|
|
stub_pacman
|
|
source "$REPO/utils.sh"
|
|
|
|
run install_packages git tmux vim neovim
|
|
[ "$status" -eq 0 ]
|
|
called yay -S --noconfirm --answerdiff=None --answeredit=None --answerclean=None tmux neovim
|
|
not_called sudo
|
|
}
|
|
|
|
@test "install_packages does nothing when everything is installed" {
|
|
export INSTALLED="git vim" AS_DEPS=""
|
|
stub_pacman
|
|
source "$REPO/utils.sh"
|
|
|
|
run install_packages git vim
|
|
[ "$status" -eq 0 ]
|
|
not_called yay
|
|
}
|
|
|
|
@test "install_packages marks packages installed as dependencies explicit" {
|
|
export INSTALLED="git python" AS_DEPS="python"
|
|
stub_pacman
|
|
source "$REPO/utils.sh"
|
|
|
|
run install_packages git python
|
|
[ "$status" -eq 0 ]
|
|
called sudo pacman -D --asexplicit python
|
|
not_called yay
|
|
}
|
|
|
|
@test "remove_packages removes only exact matches, not providers" {
|
|
# herdr-bin provides herdr, so pacman -Qq herdr prints herdr-bin
|
|
stub pacman '
|
|
[ "$1" = -Qq ] || exit 1
|
|
case "$2" in
|
|
herdr) echo herdr-bin ;;
|
|
vim) echo vim ;;
|
|
*) exit 1 ;;
|
|
esac'
|
|
stub sudo
|
|
source "$REPO/utils.sh"
|
|
|
|
run remove_packages herdr vim missing
|
|
[ "$status" -eq 0 ]
|
|
called sudo pacman -Rns --noconfirm vim
|
|
}
|
|
|
|
@test "remove_packages does nothing when nothing is installed" {
|
|
stub pacman 'exit 1'
|
|
stub sudo
|
|
source "$REPO/utils.sh"
|
|
|
|
run remove_packages vim
|
|
[ "$status" -eq 0 ]
|
|
not_called sudo
|
|
}
|
|
|
|
# --- enable_services --------------------------------------------------------
|
|
|
|
@test "enable_services enables disabled services and skips the rest" {
|
|
stub systemctl '[ "$1" = is-enabled ] && [ "$2" = sshd ]'
|
|
stub sudo
|
|
mkdir -p "$HOME/.config/coredesktop"
|
|
echo "system cups" > "$HOME/.config/coredesktop/services-off"
|
|
source "$REPO/utils.sh"
|
|
|
|
run enable_services --now sshd cups bluetooth
|
|
[ "$status" -eq 0 ]
|
|
called sudo systemctl daemon-reload
|
|
called sudo systemctl enable --now bluetooth
|
|
uncalled sudo systemctl enable --now sshd
|
|
uncalled sudo systemctl enable --now cups
|
|
[[ "$output" == *"cups is turned off"* ]]
|
|
}
|
|
|
|
@test "enable_services without --now only enables" {
|
|
stub systemctl 'exit 1'
|
|
stub sudo
|
|
source "$REPO/utils.sh"
|
|
|
|
run enable_services bluetooth
|
|
[ "$status" -eq 0 ]
|
|
called sudo systemctl enable bluetooth
|
|
uncalled sudo systemctl daemon-reload
|
|
}
|
|
|
|
# --- initramfs_build_failed -------------------------------------------------
|
|
|
|
pacman_log() {
|
|
mkdir -p "$HOME/.local/state/coredesktop"
|
|
printf '%s\n' "$@" > "$HOME/.local/state/coredesktop/pacman-update.log"
|
|
source "$REPO/utils.sh"
|
|
}
|
|
|
|
@test "initramfs_build_failed is false without a log" {
|
|
source "$REPO/utils.sh"
|
|
run initramfs_build_failed
|
|
[ "$status" -ne 0 ]
|
|
}
|
|
|
|
@test "initramfs_build_failed is false when no initramfs was built" {
|
|
pacman_log "upgrading vim..."
|
|
run initramfs_build_failed
|
|
[ "$status" -ne 0 ]
|
|
}
|
|
|
|
@test "initramfs_build_failed is false when every image built" {
|
|
pacman_log "(3/4) Updating linux initcpios..." \
|
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'" \
|
|
"==> Image generation successful" \
|
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'" \
|
|
"==> Initcpio image generation successful"
|
|
run initramfs_build_failed
|
|
[ "$status" -ne 0 ]
|
|
}
|
|
|
|
@test "initramfs_build_failed is true when mkinitcpio reports an error" {
|
|
pacman_log "(3/4) Updating linux initcpios..." \
|
|
"==> ERROR: module not found" \
|
|
"==> Image generation successful"
|
|
run initramfs_build_failed
|
|
[ "$status" -eq 0 ]
|
|
}
|
|
|
|
@test "initramfs_build_failed is true when an image didn't build" {
|
|
pacman_log "(3/4) Updating linux initcpios..." \
|
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'" \
|
|
"==> Image generation successful" \
|
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'"
|
|
run initramfs_build_failed
|
|
[ "$status" -eq 0 ]
|
|
}
|