Files
coredesktop/tests/utils.bats
T
davidandClaude Opus 5.5 8e18a6238b remove_packages: remove a package's -debug package too
An AUR package built with makepkg's debug option comes with pkg-debug,
which nothing depends on, so pacman -Rns leaves it behind. voxtype-debug
then conflicts with voxtype-bin-debug, and update fails on machines
installed before voxtype-bin replaced voxtype. Found by
30-upgrade-from-baseline at 0c0ddf5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 09:04:18 -07:00

324 lines
9.1 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 "install_packages leaves out packages in skip-packages" {
export INSTALLED="" AS_DEPS=""
stub_pacman
mkdir -p "$HOME/.config/coredesktop"
printf '# Not on this machine\nlibreoffice-fresh\n\n gimp \n' > "$HOME/.config/coredesktop/skip-packages"
source "$REPO/utils.sh"
run install_packages git libreoffice-fresh gimp tmux
[ "$status" -eq 0 ]
called yay -S --noconfirm --answerdiff=None --answeredit=None --answerclean=None git tmux
}
@test "install_packages does nothing when every package is skipped" {
export INSTALLED="" AS_DEPS=""
stub_pacman
mkdir -p "$HOME/.config/coredesktop"
printf 'gimp\n' > "$HOME/.config/coredesktop/skip-packages"
source "$REPO/utils.sh"
run install_packages gimp
[ "$status" -eq 0 ]
# pacman -Qdq with no names would mark every dependency explicit
not_called pacman
not_called yay
}
@test "install-flatpaks.sh leaves out Flatpaks in skip-packages" {
mkdir -p "$HOME/.local/share" "$HOME/.config/coredesktop"
ln -s "$REPO" "$HOME/.local/share/coredesktop"
printf 'us.zoom.Zoom\n' > "$HOME/.config/coredesktop/skip-packages"
stub flatpak 'exit 1'
stub sudo
run bash "$REPO/install-flatpaks.sh"
[ "$status" -eq 0 ]
called sudo flatpak install --system --noninteractive org.mozilla.firefox
! grep -q 'us.zoom.Zoom' <(grep '^sudo' "$STUB_LOG")
}
@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 removes a package's -debug package too, even on its own" {
# voxtype-debug outlives voxtype, and conflicts with voxtype-bin-debug
stub pacman '
[ "$1" = -Qq ] || exit 1
case "$2" in
voxtype|voxtype-debug|herdr-debug) echo "$2" ;;
*) exit 1 ;;
esac'
stub sudo
source "$REPO/utils.sh"
run remove_packages voxtype herdr vim
[ "$status" -eq 0 ]
called sudo pacman -Rns --noconfirm voxtype voxtype-debug herdr-debug
}
@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 ]
}
# --- mirrors ----------------------------------------------------------------
setup_mirrors() {
export MIRRORLIST="$BATS_TEST_TMPDIR/mirrorlist"
cat > "$MIRRORLIST" << 'LIST'
## Worldwide
#Server = https://commented.example/archlinux/$repo/os/$arch
Server = https://first.example/archlinux/$repo/os/$arch
Server = https://second.example/$repo/os/$arch
LIST
# lastsync is $SYNCED, a Unix time
stub curl 'echo "$SYNCED"; [ -n "$SYNCED" ]'
source "$REPO/utils.sh"
}
@test "first_mirror is the first Server line that isn't commented out" {
setup_mirrors
[ "$(first_mirror)" = "https://first.example/archlinux" ]
}
@test "mirror_age_hours reads the first mirror's lastsync" {
setup_mirrors
export SYNCED="$(( $(date +%s) - 30 * 3600 ))"
run mirror_age_hours
[ "$status" -eq 0 ]
[ "$output" = 30 ]
called curl -sf --max-time 10 https://first.example/archlinux/lastsync
}
@test "mirror_age_hours fails when the mirror can't be reached or has no lastsync" {
setup_mirrors
export SYNCED=""
run mirror_age_hours
[ "$status" -ne 0 ]
export SYNCED="<html>Not Found</html>"
run mirror_age_hours
[ "$status" -ne 0 ]
}
@test "mirror_age_hours fails without a mirrorlist" {
setup_mirrors
rm "$MIRRORLIST"
run mirror_age_hours
[ "$status" -ne 0 ]
not_called curl
}