#!/usr/bin/env bats # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2026 Bryan Joshua Pedini # AUR packages on the pacman backend, through the fake paru/yay (tests/unit/fakes/paru) replaying # the output recorded in discovery/pacman.md section 15: helper detection and overrides, the user # the helper runs as (runuser/sudo -u, SUDO_USER, a root login), the "*" marker column, -Qua # parsing and failures, N? / N?? / N- / N+ / Nx on an AUR row, and the upgrade (repository # packages with pacman first, then `-Sua --ignore ...`; --noconfirm only without a terminal). # Rows with the fake paru: [1]* pfetch [2] su-dummy-a [3]* su-dummy-aur [4] su-dummy-b # [5] su-dummy-c (pfetch and su-dummy-aur are foreign: in no repository). # each @test runs in its own subshell: per-test exports are intended # shellcheck disable=SC2030,SC2031 load test_helper setup() { common_setup use_fakes paru pacman runuser getent export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-arch # root through our sudo re-exec, invoked by alice (fake getent: uid 1000, home below) export SUDO_USER=alice ALICE_HOME=$FAKE_DIR/home/alice mkdir -p "$ALICE_HOME" AS_ALICE="runuser -u alice -- env HOME=$ALICE_HOME" } ROW1=' [1]* pfetch 1.10.0-1 -> 1.11.0-1 aur' ROW2=' [2] su-dummy-a 1.0-1 -> 2.0-1 su-dummy' ROW3=' [3]* su-dummy-aur 1.0-1 -> 2.0-1 aur' ROW4=' [4] su-dummy-b 1.0-1 -> 2.0-1 su-dummy' ROW5=' [5] su-dummy-c 1.0-1 -> 2.0-1 su-dummy' # upgrade_steps: the upgrade's steps from the fake log, in order (throwaway --dbpath normalised) upgrade_steps() { grep -E '^(pacman -Sup --noconfirm|pacman -Su |real sync at -Su|(runuser|sudo) -u .* -Sua|paru -Sua|yay -Sua)' "$FAKE_LOG" | sed -E 's#--dbpath /tmp/skeptical-updater-db\.[^ ]+#--dbpath DB#' } # ---- detection and overrides ------------------------------------------------------------------- @test "detection: paru first; the banner names the helper" { use_fakes paru yay pacman runuser getent run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux) + AUR via paru' log_has 'paru -Qua' log_lacks '^yay' } @test "detection: yay when paru is not installed" { use_fakes yay pacman runuser getent run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux) + AUR via yay' log_has "$AS_ALICE yay -Qua" assert_line "$ROW1" assert_line "$ROW3" } @test "pman: the banner reads pman (Arch Linux) + AUR via paru" { use_fakes pman paru pacman runuser getent run skup list --no-refresh assert_success assert_line --index 0 'Backend: pman (Arch Linux) + AUR via paru' } @test "--aur-helper=yay picks yay although paru is installed" { use_fakes paru yay pacman runuser getent run skup --aur-helper=yay list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux) + AUR via yay' log_has 'yay -Qua' log_lacks '^paru' run skup --aur-helper yay list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux) + AUR via yay' } @test "--no-aur and --aur-helper=none: no helper runs, no AUR row, plain banner" { for opt in --no-aur --aur-helper=none; do : >"$FAKE_LOG" run skup "$opt" list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux)' assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:' assert_line ' [1] su-dummy-a 1.0-1 -> 2.0-1 su-dummy' refute_output --partial 'pfetch' log_lacks '^(paru|yay|runuser)' done } @test "no helper installed: no AUR, plain banner, every row still has the marker column" { use_fakes pacman runuser getent run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux)' assert_line ' [1] su-dummy-a 1.0-1 -> 2.0-1 su-dummy' log_lacks '^runuser' } @test "--aur-helper names a helper that is not installed: error (exit 1), nothing runs" { use_fakes paru pacman runuser getent run skup --aur-helper=yay list --no-refresh assert_failure 1 assert_output --partial 'error: --aur-helper=yay: yay not found in PATH' [[ -z $(cat "$FAKE_LOG") ]] } @test "--aur-helper: an unknown or empty value is a usage error" { run skup --aur-helper=trizen list assert_failure 1 assert_output --partial "unknown AUR helper 'trizen' (expected paru, yay or none)" run skup --aur-helper= list assert_failure 1 assert_output --partial '--aur-helper needs a value' run skup list --aur-helper assert_failure 1 assert_output --partial '--aur-helper needs a value' [[ -z $(cat "$FAKE_LOG") ]] } @test "apt: no helper is ever run, even with paru in PATH; --aur-helper is ignored with a warning" { use_fakes apt paru runuser getent export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-debian12 run skup list --no-refresh assert_success assert_line --index 0 'Backend: apt (Debian GNU/Linux 12 (bookworm))' assert_line ' [1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo' refute_output --partial 'aur' run skup --aur-helper=paru list --no-refresh assert_success assert_output --partial 'warning: --aur-helper=paru is ignored: AUR helpers only apply to the pacman backend (Arch Linux and derivatives)' run skup --no-aur list --no-refresh assert_success refute_output --partial 'warning' log_lacks '^(paru|yay|runuser)' } # ---- the user the helper runs as --------------------------------------------------------------- @test "root through sudo: the helper runs as SUDO_USER with runuser -u, that user's HOME, from that home" { run skup list --no-refresh assert_success log_has "$AS_ALICE paru -Qua" log_has "paru as: HOME=$ALICE_HOME PWD=$ALICE_HOME stdin=notty" } @test "root through sudo without runuser: sudo -u USER -- env HOME=..." { use_fakes paru pacman sudo getent run skup list --no-refresh assert_success log_has "sudo -u alice -- env HOME=$ALICE_HOME paru -Qua" log_has "paru as: HOME=$ALICE_HOME PWD=$ALICE_HOME stdin=notty" assert_line "$ROW1" } @test "unprivileged (list --no-refresh): the helper runs directly, as we are" { export SKEPTICAL_UPDATER_EUID=1000 unset SUDO_USER run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux) + AUR via paru' log_has 'paru -Qua' log_lacks '^(runuser|sudo)' assert_line "$ROW1" } @test "a root login (no SUDO_USER): AUR skipped with a warning after the banner; repository rows listed" { unset SUDO_USER run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux)' assert_line --index 1 'skeptical-updater: warning: aur: paru is never run as root and there is no invoking user (SUDO_USER is not set): AUR packages are neither listed nor upgraded (run skeptical-updater through sudo from your own account, or use --no-aur)' assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:' [[ $(grep -c 'warning: aur:' <<<"$output") -eq 1 ]] log_lacks '^(paru|runuser)' } @test "SUDO_USER root (or another uid 0 name) or an unknown user: AUR skipped with a warning" { for u in root toor; do SUDO_USER=$u run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux)' assert_output --partial "warning: aur: paru is never run as root and the invoking user ($u) is root" done SUDO_USER=nosuchuser run skup list --no-refresh assert_success assert_output --partial "warning: aur: the invoking user 'nosuchuser' (SUDO_USER) is unknown" log_lacks '^(paru|runuser)' } @test "a root login: the interactive upgrade runs pacman only, no helper" { unset SUDO_USER run skup --no-refresh <<<'' assert_success log_has 'pacman -Su --noconfirm' log_lacks '^(paru|runuser)' } # ---- listing ------------------------------------------------------------------------------------ @test "the * marker: right after the bracket of AUR rows, a space there on the others; columns aligned" { run skup list --no-refresh assert_success assert_line '5 upgradable, 0 trusted (hidden, use --all to show), 5 to review:' assert_output --partial "$(printf '%s\n' "$ROW1" "$ROW2" "$ROW3" "$ROW4" "$ROW5")" } @test "the marker column stays aligned with the - and = markers" { write_state "$HEADER" "pacman${T}su-dummy-aur${T}user${T}2026-10-09T09:40:12Z" run skup --all --no-refresh <<<$'1-\nl\nq' assert_success assert_line '-[1]* pfetch 1.10.0-1 -> 1.11.0-1 aur' assert_line '=[3]* su-dummy-aur 1.0-1 -> 2.0-1 aur' assert_line ' [2] su-dummy-a 1.0-1 -> 2.0-1 su-dummy' } @test "--no-refresh still lists the AUR rows (the helper always asks the AUR)" { run skup list --no-refresh assert_success log_lacks '^pacman -Sy' log_has "$AS_ALICE paru -Qua" assert_line "$ROW3" } @test "with a refresh too: AUR rows next to the rows of the reviewed databases" { run skup list assert_success assert_line 'Refreshing package lists... done.' assert_line "$ROW1" assert_line "$ROW2" } @test "-Qua failing (AUR unreachable): the helper's message and a warning; repository rows listed" { FAKE_AUR_QUA=fail run skup list --no-refresh assert_success assert_line --index 0 'Backend: pacman (Arch Linux) + AUR via paru' assert_output --partial 'warning: paru: error: error sending request for url (https://aur.archlinux.org/rpc): error trying to connect' assert_output --partial 'warning: aur: paru -Qua failed, AUR packages not listed' assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:' refute_output --partial '*' } @test "yay -Qua failing: its multi-line message is shown without the blank lines" { use_fakes yay pacman runuser getent FAKE_AUR_QUA=fail run skup list --no-refresh assert_success assert_output --partial 'warning: yay: -> 1 error occurred:' assert_output --partial 'dial tcp 127.0.0.1:443: connect: connection refused' assert_output --partial 'warning: aur: yay -Qua failed, AUR packages not listed' refute_line 'skeptical-updater: warning: yay: ' } @test "-Qua with nothing to upgrade (exit 1, silent) is not a failure" { FAKE_AUR_QUA=none run skup list --no-refresh assert_success refute_output --partial 'warning' assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:' } @test "paru marks IgnorePkg packages [ignored] (shown [held]); yay leaves them out" { FAKE_AUR_QUA=ignored run skup list --no-refresh assert_success assert_line ' [1]* pfetch 1.10.0-1 -> 1.11.0-1 aur [held]' use_fakes yay pacman runuser getent FAKE_AUR_QUA=ignored run skup list --no-refresh assert_success refute_output --partial 'pfetch' assert_line ' [2]* su-dummy-aur 1.0-1 -> 2.0-1 aur' } @test "yay: the age suffix ([122d13h]) is no flag; its colored root warning on stdout is no row" { use_fakes yay pacman runuser getent for q in fixture noise; do FAKE_AUR_QUA=$q run skup list --no-refresh assert_success assert_output --partial "$(printf '%s\n' "$ROW1" "$ROW2" "$ROW3")" refute_output --partial 'Avoid' refute_output --partial '122d13h' done } @test "devel packages: the new version is latest-commit (paru by default, yay with --devel)" { for h in paru yay; do use_fakes "$h" pacman runuser getent FAKE_AUR_QUA=devel run skup list --no-refresh assert_success assert_line --regexp '^ \[1\]\* pfetch-git +r432\.a906ff8-1 +-> +latest-commit +aur$' done } @test "a name a repository lists wins over the AUR row (moved from the AUR into a repository)" { FAKE_AUR_QUA=dup run skup list --no-refresh assert_success assert_line --regexp '^ \[3\] su-dummy-b +1\.0-1 +-> +2\.0-1 +su-dummy$' refute_output --partial '9.0-1' } # ---- N? N?? N+ Nx on an AUR row --------------------------------------------------------------- @test "N? on an AUR row: description, install reason and dependents from pacman -Qi" { run skup --no-refresh <<<$'1?\nq' assert_success assert_output --partial "$(printf '%s\n' '> 1?' \ 'pfetch: A pretty system information tool written in POSIX sh.' \ 'reason: explicitly installed' \ 'required by: nothing')" log_lacks 'paru -Si' } @test "N?? on an AUR row: pacman -Qi merged with paru -Sia (same key column)" { run skup --no-refresh <<<$'1??\nq' assert_success log_has "$AS_ALICE paru -Sia pfetch" assert_line '# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks' assert_output --partial "$(printf '%s\n' 'Version : 1.10.0-1' '> Version : 1.11.0-1')" assert_line 'Install Reason : Explicitly installed' assert_line 'Description : A pretty system information tool written in POSIX sh.' [[ $(count_lines 'Description : A pretty system information tool written in POSIX sh.') -eq 1 ]] assert_output --partial "$(printf '%s\n' \ '> Repository : aur' \ '> AUR URL : https://aur.archlinux.org/packages/pfetch' \ '> Make Deps : None' \ '> Check Deps : None' \ '> Maintainer : yigits' \ '> Votes : 48' \ '> Popularity : 0.10344' \ '> First Submitted : Sat, 16 Jan 2021 09:13:08' \ '> Last Modified : Tue, 9 Jun 2026 22:43:50' \ '> Out Of Date : No')" } @test "N?? with yay: its wider key column is re-padded to pacman's" { use_fakes yay pacman runuser getent run skup --no-refresh <<<$'1??\nq' assert_success log_has "$AS_ALICE yay -Sia pfetch" assert_output --partial "$(printf '%s\n' 'Version : 1.10.0-1' '> Version : 1.11.0-1')" assert_line '> Repository : aur' assert_line '> Keywords : fetch' assert_line '> First Submitted : Sat 16 Jan 2021 09:13:08 AM UTC' assert_line '> Out-of-date : No' refute_output --regexp 'Repository {10,}:' } @test "N?? when the AUR has no record of it: the installed record alone" { run skup --no-refresh <<<$'3??\nq' assert_success assert_line 'Name : su-dummy-aur' assert_line 'Install Reason : Explicitly installed' refute_line --regexp '^> [A-Z#]' log_has "$AS_ALICE paru -Sia su-dummy-aur" } @test "N?? when the AUR record does not merge: -Qi, then the AUR record with every line marked >" { FAKE_AUR_SIA=twice run skup --no-refresh <<<$'1??\nq' assert_success assert_line '# lines starting with > are the AUR record (paru -Sia) as it prints it' refute_line --partial '# lines starting with > come from the upstream candidate' assert_line 'Version : 1.10.0-1' [[ $(count_lines '> Version : 1.11.0-1') -eq 2 ]] [[ $(count_lines '> Repository : aur') -eq 2 ]] assert_line 'Install Reason : Explicitly installed' } @test "show PKG for an AUR package: the same merge, the helper decided on the spot" { run skup show pfetch assert_success assert_line '> Repository : aur' assert_output --partial "$(printf '%s\n' 'Version : 1.10.0-1' '> Version : 1.11.0-1')" export SKEPTICAL_UPDATER_EUID=1000 unset SUDO_USER : >"$FAKE_LOG" run skup show pfetch assert_success assert_line '> Maintainer : yigits' log_has 'paru -Sia pfetch' log_lacks '^runuser' } @test "show of an AUR package as a root login: one warning, the installed record" { unset SUDO_USER run skup show pfetch assert_success assert_line 'Name : pfetch' refute_line --regexp '^> ' [[ $(grep -c 'warning: aur:' <<<"$output") -eq 1 ]] log_lacks '^paru' } @test "N+ on an AUR row trusts it like any installed package (backend pacman)" { run skup --no-refresh <<<$'1+\nl\nq' assert_success assert_line 'trusted pfetch' assert_line '5 upgradable, 1 trusted (hidden, use --all to show), 4 to review:' run cat "$STATE_FILE" assert_line --regexp "^pacman${T}pfetch${T}user${T}${DATE_RE}\$" } @test "Nx on an AUR row purges it with pacman -Rns; the row leaves the list" { run skup --no-refresh <<<$'1x\ny\nl\nq' assert_success log_has 'pacman -Rs --print --print-format %n -- pfetch' assert_line 'Purge pfetch now? [y/N] y' log_has 'pacman -Rns --noconfirm -- pfetch' assert_line '4 upgradable, 0 trusted (hidden, use --all to show), 4 to review:' [[ $(grep -c '^ \[1\]\* pfetch' <<<"$output") -eq 1 ]] || fail "row 1 is still listed after the purge" assert_line ' [3]* su-dummy-aur 1.0-1 -> 2.0-1 aur' } # ---- the upgrade -------------------------------------------------------------------------------- @test ": the repository step first (pre-check, databases, -Su), then the helper as the invoking user" { run skup <<<'' assert_success assert_line 'Upgrade finished.' run upgrade_steps assert_output "$(printf '%s\n' \ 'pacman -Sup --noconfirm --dbpath DB --print-format %n %v' \ 'pacman -Su --noconfirm' \ 'real sync at -Su: reviewed 1 / no-sig' \ "$AS_ALICE paru -Sua --noconfirm" \ 'paru -Sua --noconfirm')" log_has "paru as: HOME=$ALICE_HOME PWD=$ALICE_HOME stdin=notty" } @test "N- on an AUR row: the helper's --ignore; pacman never sees AUR names" { run skup --no-refresh <<<$'1-\n' assert_success assert_line 'Excluded from this run: pfetch' log_has 'pacman -Su --noconfirm' assert_line "+ $AS_ALICE paru -Sua --noconfirm --ignore pfetch" log_has 'paru -Sua --noconfirm --ignore pfetch' log_lacks '^pacman .*--ignore' } @test "N- on a repository row: pacman --ignore and the helper's --ignore both get it" { run skup --no-refresh <<<$'2-\n1-\n' assert_success log_has 'pacman -Su --noconfirm --ignore su-dummy-a' log_has "$AS_ALICE paru -Sua --noconfirm --ignore pfetch,su-dummy-a" } @test "every AUR row excluded: the helper is not run" { run skup --no-refresh <<<$'1-\n3-\n' assert_success log_has 'pacman -Su --noconfirm' log_lacks '-Sua' } @test "only AUR rows left to upgrade: pacman is not run, only the helper" { run skup <<<$'2- 4- 5-\n' assert_success assert_line 'No repository package to upgrade: pacman is not run, only paru.' log_lacks '^pacman -Su' log_lacks '^pacman -Sup --noconfirm' log_lacks '^real sync' log_has "$AS_ALICE paru -Sua --noconfirm --ignore su-dummy-a,su-dummy-b,su-dummy-c" assert_line 'Upgrade finished.' } @test "no AUR row at all: exactly the repository upgrade as before" { FAKE_AUR_QUA=none run skup --no-refresh <<<'' assert_success log_has 'pacman -Su --noconfirm' log_lacks '-Sua' } @test "trusted (hidden) AUR rows are upgraded by the helper too" { write_state "$HEADER" "pacman${T}pfetch${T}user${T}2026-10-09T09:40:12Z" "pacman${T}su-dummy-aur${T}user${T}2026-10-09T09:40:12Z" # listed: [1] su-dummy-a [2] su-dummy-b [3] su-dummy-c (both AUR rows trusted: hidden) run skup --no-refresh <<<$'1- 2- 3-\n' assert_success assert_line 'No repository package to upgrade: pacman is not run, only paru.' log_has "$AS_ALICE paru -Sua --noconfirm --ignore su-dummy-a,su-dummy-b,su-dummy-c" } @test "an [ignored] AUR row alone does not make pacman stand aside" { FAKE_AUR_QUA=ignored run skup --no-refresh <<<$'2- 3- 4- 5-\n' assert_success log_has 'pacman -Su --noconfirm --ignore su-dummy-a,su-dummy-b,su-dummy-c' log_lacks '-Sua' } @test "the helper failing after a successful repository step: reported, its exit status returned" { FAKE_AUR_SUA_RC=1 run skup --no-refresh <<<'' assert_failure 1 log_has 'pacman -Su --noconfirm' assert_output --partial 'error: the AUR upgrade failed: paru exited with status 1 (the repository packages were upgraded)' refute_output --partial 'hint:' refute_output --partial 'Upgrade finished.' } @test "the helper failing in an AUR-only upgrade: its exit status" { FAKE_AUR_SUA_RC=4 run skup --no-refresh <<<$'2- 4- 5-\n' assert_failure 4 assert_output --partial 'error: the AUR upgrade failed: paru exited with status 4' refute_output --partial 'repository packages were upgraded' } @test "a failing repository step: the helper is not run" { FAKE_PACMAN_SU_RC=1 run skup --no-refresh <<<'' assert_failure 1 assert_output --partial 'the upgrade failed: pacman exited with status 1' log_lacks '-Sua' FAKE_PACMAN_SUP_RC=1 run skup <<<'2-'$'\n' assert_failure 1 log_lacks '-Sua' } @test "--dry-run prints the helper's command line (root through sudo: as the invoking user)" { run skup --dry-run --no-refresh <<<$'1-\n' assert_success assert_line '+ pacman -Su --noconfirm' assert_line "+ $AS_ALICE paru -Sua --noconfirm --ignore pfetch" assert_line 'Dry run: nothing was changed.' log_lacks '-Sua' log_lacks '^pacman -Su ' } @test "--dry-run unprivileged: the helper's plain command line" { export SKEPTICAL_UPDATER_EUID=1000 unset SUDO_USER run skup --dry-run <<<'' assert_success assert_line '+ paru -Sua --noconfirm' log_lacks '-Sua' log_lacks '^(runuser|sudo)' } @test "on a terminal: no --noconfirm, the helper gets the terminal (its PKGBUILD review)" { pty_skup $'3-\r\r' --no-refresh assert_success log_has "$AS_ALICE paru -Sua --ignore su-dummy-aur" log_has "paru as: HOME=$ALICE_HOME PWD=$ALICE_HOME stdin=tty" log_lacks 'noconfirm --ignore su-dummy-aur' log_has 'pacman -Su --noconfirm' } @test "help and --help mention the * marker and the options" { run skup --no-refresh <<<$'h\nq' assert_success assert_line " l redraw the list (a '*' after the number marks an AUR package)" run skup --help assert_success assert_line --partial '--aur-helper=NAME pacman backend: list and upgrade AUR packages with paru or yay, or' assert_line --partial '--no-aur same as --aur-helper=none' assert_output --partial "A '*' right after the number marks an AUR package" }