Files
coredesktop/tests/utils.bats
T
davidandClaude Opus 5.5 d9da8208dd Add shellcheck lint and bats tests
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>
2026-10-30 16:15:12 -06:00

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 ]
}