#!/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="Not Found" 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 }