#!/usr/bin/env bats # Black-box tests: the script runs with the stubs in tests/stubs and temp config/state folders. bats_require_minimum_version 1.5.0 bats_load_library bats-support bats_load_library bats-assert SCRIPT=$BATS_TEST_DIRNAME/../update-notify setup() { export STUB_DATA=$BATS_TEST_TMPDIR/data export XDG_CONFIG_HOME=$BATS_TEST_TMPDIR/config export XDG_STATE_HOME=$BATS_TEST_TMPDIR/state # The running kernel has its vmlinuz, so the reboot check stays quiet unless a test changes STUB_KERNEL export STUB_KERNEL=6.1.1-test export UPDATE_NOTIFY_MODULES_DIR=$BATS_TEST_TMPDIR/modules STATE=$XDG_STATE_HOME/update-notify BIN=$BATS_TEST_TMPDIR/bin mkdir -p "$STUB_DATA" "$XDG_CONFIG_HOME/update-notify" "$BIN" "$UPDATE_NOTIFY_MODULES_DIR/$STUB_KERNEL" touch "$UPDATE_NOTIFY_MODULES_DIR/$STUB_KERNEL/vmlinuz" # PATH holds only the stubs and the real tools the script needs, so a removed stub is a missing command local tool for tool in mkdir mv rm id timeout vercmp; do ln -s "$(command -v "$tool")" "$BIN/$tool" done for tool in checkupdates aur pacman pacdiff uname notify-send; do enable_stub "$tool" done } enable_stub() { ln -s "$BATS_TEST_DIRNAME/stubs/$1" "$BIN/$1"; } disable_stub() { rm "$BIN/$1"; } config() { printf '%s\n' "$@" >> "$XDG_CONFIG_HOME/update-notify/config"; } repo_updates() { printf '%s\n' "$@" > "$STUB_DATA/checkupdates"; } aur_updates() { printf '%s\n' "$@" > "$STUB_DATA/aur"; } installed() { printf '%s\n' "$@" > "$STUB_DATA/installed"; } pacnew_files() { printf '%s\n' "$@" > "$STUB_DATA/pacdiff"; } custom_linux() { config "KERNELS=([linux]=linux-custom)"; } # Runs the script; notifications and stub calls are logged from this run only run_script() { rm -f "$STUB_DATA/notify" "$STUB_DATA/calls" run /usr/bin/env PATH="$BIN" "$SCRIPT" "$@" } # Args: title, then body lines assert_notification() { local actual expected actual=$(<"$STUB_DATA/notify") || fail "no notification sent" printf -v expected '%s\n' "$@" -- assert_equal "$actual" "${expected%$'\n'}" } assert_no_notification() { [[ ! -e $STUB_DATA/notify ]] || fail "unexpected notification: $(<"$STUB_DATA/notify")" } assert_pending() { local expected printf -v expected '%s\n' "$@" assert_equal "$(<"$STATE/pending")" "${expected%$'\n'}" } assert_called() { grep -qxF -- "$1" "$STUB_DATA/calls" || fail "not called: $1" } # --- Threshold and arguments --- @test "no updates: no notification, pending file has only the threshold" { run_script assert_success assert_no_notification assert_pending "threshold 30" } @test "below the threshold: no notification, updates listed in the pending file" { repo_updates "foo 1-1 -> 2-1" aur_updates "bar 1-1 -> 1.1-1" run_script assert_success assert_no_notification assert_pending "threshold 30" "repo foo 2-1" "aur bar 1.1-1" } @test "threshold reached: notifies the counts" { config THRESHOLD=2 repo_updates "foo 1-1 -> 2-1" aur_updates "bar 1-1 -> 1.1-1" run_script assert_success assert_notification "Updates available" "Arch: 1, AUR: 1" } @test "threshold argument overrides the config, pending file keeps the config value" { config THRESHOLD=5 repo_updates "foo 1-1 -> 2-1" "baz 1-1 -> 2-1" run_script 2 assert_success assert_notification "Updates available" "Arch: 2, AUR: 0" assert_pending "threshold 5" "repo foo 2-1" "repo baz 2-1" } @test "threshold with a leading zero is read as decimal" { repo_updates "a 1 -> 2" "b 1 -> 2" "c 1 -> 2" "d 1 -> 2" "e 1 -> 2" "f 1 -> 2" "g 1 -> 2" "h 1 -> 2" "i 1 -> 2" run_script 010 # octal would be 8 assert_success assert_no_notification } @test "non-numeric threshold argument fails before touching the state" { run_script abc assert_failure 1 assert_output "Threshold must be a number" [[ ! -e $STATE ]] } @test "non-numeric threshold in the config fails" { config THRESHOLD=many run_script assert_failure 1 assert_output "Threshold must be a number" } # --- Gathering updates --- @test "checkupdates failure exits without touching the state" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script assert_notification "Updates available" "Arch: 1, AUR: 0" cp -r "$STATE" "$BATS_TEST_TMPDIR/before" repo_updates "foo 1-1 -> 2-1" "baz 1-1 -> 2-1" CHECKUPDATES_RC=1 run_script assert_failure 1 assert_output --partial "checkupdates failed" assert_no_notification diff -r "$BATS_TEST_TMPDIR/before" "$STATE" } @test "aur repo -u failure warns and counts no AUR updates" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" aur_updates "bar 1-1 -> 1.1-1" AUR_RC=1 run_script assert_success assert_output --partial "aur repo -u failed" assert_notification "Updates available" "Arch: 1, AUR: 0" assert_pending "threshold 1" "repo foo 2-1" } @test "aur missing: AUR updates count 0 without a warning" { disable_stub aur config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script assert_success assert_output "" assert_notification "Updates available" "Arch: 1, AUR: 0" } @test "aur is queried for the configured repo" { config AUR_REPO=myrepo run_script assert_success assert_called "aur repo -u -d myrepo" } # --- Kernel check --- @test "no kernel check by default" { installed "linux 6.1.1-1" "linux-custom 6.1.1-1" repo_updates "linux 6.1.1-1 -> 6.1.2-1" run_script assert_success assert_no_notification assert_pending "threshold 30" "repo linux 6.1.2-1" } @test "stock kernel update notifies the rebuild below the threshold" { custom_linux installed "linux 6.1.1-1" "linux-custom 6.1.1-1" repo_updates "linux 6.1.1-1 -> 6.1.2-1" run_script assert_success assert_notification "Kernel update available" "Rebuild linux-custom (linux 6.1.2-1)" assert_pending "threshold 30" "repo linux 6.1.2-1" "kernel linux-custom 6.1.2-1" } @test "kernel rebuild joins the counts over the threshold" { config THRESHOLD=1 custom_linux installed "linux 6.1.2-1" "linux-custom 6.1.1-1" run_script 0 assert_success assert_notification "Updates available" "Arch: 0, AUR: 0" "Rebuild linux-custom (linux 6.1.2-1)" } @test "kernel check ignores pkgrel" { custom_linux installed "linux 6.1.1-1" "linux-custom 6.1.1-1" repo_updates "linux 6.1.1-1 -> 6.1.1-2" run_script assert_success assert_no_notification } @test "custom kernel already built into the repo isn't flagged" { custom_linux installed "linux 6.1.1-1" "linux-custom 6.1.1-1" repo_updates "linux 6.1.1-1 -> 6.1.2-1" "linux-custom 6.1.1-1 -> 6.1.2-1" run_script assert_success assert_no_notification } @test "kernels without a custom build installed are skipped" { custom_linux installed "linux 6.1.1-1" repo_updates "linux 6.1.1-1 -> 6.1.2-1" run_script assert_success assert_no_notification } @test "custom kernel map comes from the config" { config "KERNELS=([linux-hardened]=linux-hardened-mine)" installed "linux-hardened 6.1.2-1" "linux-hardened-mine 6.1.1-1" run_script assert_success assert_notification "Kernel update available" "Rebuild linux-hardened-mine (linux-hardened 6.1.2-1)" } # --- Notification state --- @test "same updates don't notify again, a change does" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script assert_notification "Updates available" "Arch: 1, AUR: 0" run_script assert_success assert_no_notification repo_updates "foo 1-1 -> 2-1" "baz 1-1 -> 2-1" run_script assert_notification "Updates available" "Arch: 2, AUR: 0" } @test "updates gone: the next ones notify again" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script rm "$STUB_DATA/checkupdates" run_script assert_no_notification [[ ! -e $STATE/updates ]] repo_updates "foo 1-1 -> 2-1" run_script assert_notification "Updates available" "Arch: 1, AUR: 0" } @test "failed notify-send isn't remembered, the next run retries" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" pacnew_files /etc/a.pacnew NOTIFY_RC=1 run_script [[ ! -e $STATE/updates && ! -e $STATE/maintenance ]] run_script assert_notification "Updates available" "Arch: 1, AUR: 0" "Config files to merge with pacdiff: 1" } # --- Quiet runs (path unit) --- @test "--quiet writes the pending file but leaves a new notification to the timer" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script --quiet assert_success assert_no_notification assert_pending "threshold 1" "repo foo 2-1" [[ ! -e $STATE/updates ]] run_script assert_notification "Updates available" "Arch: 1, AUR: 0" } @test "--quiet tracks changes to a shown notification, so the timer doesn't repeat them" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script repo_updates "foo 1-1 -> 2-1" "baz 1-1 -> 2-1" run_script -q assert_no_notification run_script assert_no_notification } @test "--quiet leaves new maintenance items to the timer" { pacnew_files /etc/a.pacnew run_script --quiet assert_no_notification [[ ! -e $STATE/maintenance ]] run_script assert_notification "System maintenance" "Config files to merge with pacdiff: 1" } @test "--quiet takes a threshold argument after it" { repo_updates "foo 1-1 -> 2-1" run_script --quiet 1 assert_success assert_no_notification assert_pending "threshold 30" "repo foo 2-1" } # --- Maintenance reminders --- @test "pacnew files notify alone only when new" { pacnew_files /etc/a.pacnew /etc/b.pacsave run_script assert_success assert_notification "System maintenance" "Config files to merge with pacdiff: 2" assert_pending "threshold 30" "pacnew /etc/a.pacnew" "pacnew /etc/b.pacsave" run_script assert_no_notification pacnew_files /etc/a.pacnew /etc/b.pacsave /etc/c.pacnew run_script assert_notification "System maintenance" "Config files to merge with pacdiff: 3" rm "$STUB_DATA/pacdiff" run_script assert_no_notification [[ ! -e $STATE/maintenance ]] } @test "maintenance lines join every update notification" { config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" pacnew_files /etc/a.pacnew run_script assert_notification "Updates available" "Arch: 1, AUR: 0" "Config files to merge with pacdiff: 1" repo_updates "foo 1-1 -> 2-1" "baz 1-1 -> 2-1" run_script assert_notification "Updates available" "Arch: 2, AUR: 0" "Config files to merge with pacdiff: 1" } @test "reboot reminder when the running kernel's vmlinuz is gone" { STUB_KERNEL=0.0.0-gone run_script assert_success assert_notification "System maintenance" "Reboot to load the new kernel (running 0.0.0-gone)" assert_pending "threshold 30" "reboot 0.0.0-gone" } @test "REBOOT_CHECK=0 disables the reboot reminder" { config REBOOT_CHECK=0 STUB_KERNEL=0.0.0-gone run_script assert_success assert_no_notification } @test "PACNEW_CHECK=0 skips pacdiff" { config PACNEW_CHECK=0 pacnew_files /etc/a.pacnew run_script assert_success assert_no_notification run ! grep -q '^pacdiff' "$STUB_DATA/calls" } # --- Reminders over the threshold --- @test "git-monitor and firmware reminders over the threshold" { enable_stub git-monitor enable_stub fwupdmgr config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" FWUPD_RC=0 run_script assert_success assert_notification "Updates available" "Arch: 1, AUR: 0" \ "Also check: git-monitor, fwupdmgr (firmware updates available)" assert_called "fwupdmgr get-updates --no-metadata-check" } @test "no firmware reminder without firmware updates" { enable_stub git-monitor enable_stub fwupdmgr config THRESHOLD=1 repo_updates "foo 1-1 -> 2-1" run_script assert_notification "Updates available" "Arch: 1, AUR: 0" "Also check: git-monitor" } @test "no reminders below the threshold" { enable_stub git-monitor enable_stub fwupdmgr custom_linux installed "linux 6.1.2-1" "linux-custom 6.1.1-1" run_script assert_notification "Kernel update available" "Rebuild linux-custom (linux 6.1.2-1)" run ! grep -q '^fwupdmgr' "$STUB_DATA/calls" }