feat(aur): list and upgrade AUR packages through paru or yay on Arch

On the pacman backend, an installed AUR helper (paru, else yay;
--aur-helper=paru|yay|none, --no-aur) adds the AUR updates to the review
list. The helper never runs as root: when the tool is root through sudo it
runs as SUDO_USER (runuser -u, else sudo -u, with that user's HOME, from
that home); unprivileged it runs as is; a root login gets no AUR, with a
warning. The banner names the helper ("+ AUR via paru").

- listing: <helper> -Qua (also with --no-refresh), rows with repository
  "aur"; [ignored] is shown [held]; a failing -Qua warns and the session
  goes on with the repository rows
- every row has a marker column after the number: "*" for AUR rows, a
  space otherwise (on every backend)
- N?? merges pacman -Qi with <helper> -Sia; N?, N+, Nx stay with pacman;
  N- excludes through the helper's --ignore
- <enter>: the repository step exactly as before, then <helper> -Sua
  --ignore <excluded> as the invoking user (--noconfirm only when stdin is
  not a terminal); pacman is skipped when only AUR rows are left; a helper
  failure is reported and its status returned; --dry-run prints it
- other backends never run a helper; --aur-helper is ignored with a warning

paru 2.1.0 and yay 13.0.1 formats verified in discovery/pacman.md section
15 (proof: discovery/pacman/aur.sh). Unit tests replay the recorded output
(fake paru, yay, runuser, getent); the archlinux E2E run installs the
foreign su-dummy-aur and drives a fake paru through a fake sudo built on
passwordless su for wheel.
This commit is contained in:
bryanpedini committed 2026-10-10 15:06:18 +02:00
1 parent 875c8294bf
commit def537af58
39 files changed
+1903 -115

No files matched your search

+22 -22
View File
@@ -16,7 +16,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_success
assert_line --index 0 'Backend: apt (Debian GNU/Linux 12 (bookworm))'
assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:'
assert_line ' [1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line '<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever'
assert_line '> q'
assert_line 'Quit: nothing was upgraded.'
@@ -81,7 +81,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
@test "N? on a new dependency (old version -) says 'not installed' without asking the package manager" {
FAKE_APT_SIM=real run skup --no-refresh <<<$'9?\nq'
assert_success
assert_line --regexp '^ \[9\] su-dummy-newdep +- +-> +1\.0 +su-fixture:su-repo$'
assert_line --regexp '^ \[9\] su-dummy-newdep +- +-> +1\.0 +su-fixture:su-repo$'
assert_output --partial "$(printf '%s\n' '> 9?' \
'su-dummy-newdep: (no description available: unknown package?)' \
'reason: not installed' \
@@ -114,8 +114,8 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_success
assert_line 'excluded from this run: su-dummy-b'
assert_line 're-included: su-dummy-b'
assert_line '-[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line '-[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'Excluded from this run: su-dummy-b'
log_has 'apt-mark hold su-dummy-b'
log_has 'apt-mark unhold su-dummy-b'
@@ -129,8 +129,8 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
log_has 'apt-get -y purge su-dummy-c'
log_lacks 'autoremove --purge'
assert_line '2 upgradable, 0 trusted (hidden, use --all to show), 2 to review:'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
[[ $(grep -c '^ \[3\] su-dummy-c' <<<"$output") -eq 1 ]]
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
[[ $(grep -c '^ \[3\] su-dummy-c' <<<"$output") -eq 1 ]]
assert_output --partial 'no package [3] in the list (l to redraw)'
}
@@ -148,7 +148,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
FAKE_APT_PURGE_RC=100 run skup --no-refresh <<<$'3x\nyes\nl\nq'
assert_success
assert_output --partial 'purge of su-dummy-c failed (apt exited 100); the row stays in the list'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
}
@test "Nx: EOF at the confirmation aborts with exit 2 and purges nothing" {
@@ -163,7 +163,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_line 'not purged: su-dummy-b is required by: su-dummy-a - purge the dependents first'
refute_output --partial '[y/N]'
assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:'
[[ $(grep -c '^ \[2\] su-dummy-b' <<<"$output") -eq 2 ]]
[[ $(grep -c '^ \[2\] su-dummy-b' <<<"$output") -eq 2 ]]
log_lacks '^apt-get -y'
}
@@ -173,7 +173,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_line 'Purge su-dummy-a now? This also removes: su-dummy-b [y/N] y'
assert_line 'purged: su-dummy-a su-dummy-b'
assert_line '1 upgradable, 0 trusted (hidden, use --all to show), 1 to review:'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
}
@test "Nx: rows the purge took with it unannounced leave the list too (re-listed afterwards)" {
@@ -181,7 +181,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_success
assert_line ' (su-dummy-a is no longer upgradable, removed from the list)'
assert_line '1 upgradable, 0 trusted (hidden, use --all to show), 1 to review:'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
}
@test "Nx on a new dependency (old version -, not installed) is refused" {
@@ -224,10 +224,10 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_success
assert_line 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
assert_line '3 upgradable, 2 trusted (hidden, use --all to show), 1 to review:'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
# [1] and [2] only appear in the first listing
[[ $(grep -c '^ \[1\] su-dummy-a' <<<"$output") -eq 1 ]]
[[ $(grep -c '^ \[2\] su-dummy-b' <<<"$output") -eq 1 ]]
[[ $(grep -c '^ \[1\] su-dummy-a' <<<"$output") -eq 1 ]]
[[ $(grep -c '^ \[2\] su-dummy-b' <<<"$output") -eq 1 ]]
run cut -f1-3 "$STATE_FILE"
assert_line "apt${T}su-dummy-a${T}user"
assert_line "apt${T}su-dummy-b${T}dep:su-dummy-a"
@@ -254,8 +254,8 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
run skup --no-refresh <<<$'1?\nq'
assert_success
assert_line '3 upgradable, 1 trusted (hidden, use --all to show), 2 to review:'
assert_line ' [1] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [1] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests'
}
@@ -264,11 +264,11 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
run skup --no-refresh --all <<<$'2+\nl\nq'
assert_success
assert_line '3 upgradable, 1 trusted (marked with =), 2 to review:'
assert_line '=[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line '=[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'trusted su-dummy-b'
assert_line '3 upgradable, 2 trusted (marked with =), 1 to review:'
assert_line '=[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line '=[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
}
@test "trusted packages are upgraded silently (never excluded)" {
@@ -305,7 +305,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_success
assert_line 'excluded all 3 listed packages from this run'
assert_line 're-included all 3 listed packages'
assert_line '-[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line '-[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'Excluded from this run: su-dummy-a su-dummy-b su-dummy-c'
log_has 'apt-mark hold su-dummy-a su-dummy-b su-dummy-c'
}
@@ -316,7 +316,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_line 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests'
assert_line 'excluded from this run: su-dummy-b'
assert_line 'su-dummy-c: dummy package su-dummy-c for skeptical-updater tests'
assert_line '-[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line '-[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
}
@test "an unknown token prints an error, runs nothing from that line and re-prompts" {
@@ -324,7 +324,7 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
assert_success
assert_output --partial "unknown command 'bogus' (h for help)"
assert_output --partial "unknown command '3y' (h for help)"
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
refute_output --partial 'excluded from this run'
[[ ! -e $STATE_FILE ]]
}
@@ -369,6 +369,6 @@ UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--forc
echo su-dummy-c >"$FAKE_DIR/holds"
FAKE_APT_SIM=held run skup --no-refresh <<<$'3?\nq'
assert_success
assert_line ' [3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]'
assert_line 'su-dummy-c: dummy package su-dummy-c for skeptical-updater tests'
}