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>
324 lines
9.1 KiB
Bash
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
|
|
}
|