N?? / show marks the upstream candidate with "*" instead of ">" (the
prompt, which read as a command in logs), and every line but the note
gets a two-character gutter: " " for the installed record, "* " for the
candidate, so keys and colons line up. The plain native fallback (zypper,
unmergeable records) and the not-installed candidate use the same gutter.
pacman records (-Qi with -Si or the AUR helper's -Sia) are now compared
the way they are meant:
- values unwrapped (continuation lines joined, runs of blanks as one):
yay wraps Depends On / Optional Deps at 80 columns even without a
terminal unless COLUMNS=0, which the helper now gets for -Qua and -Sia
- Depends On, Provides, Conflicts With, Make/Check Deps and the other
lists as sets of exact tokens in any order (a constraint is a change)
- Optional Deps by package names ("name: description [installed]" in
-Qi, bare names from the AUR)
- a differing value is one line like -Qi, Optional Deps one entry per
line; candidate-only fields sit next to their counterpart (Repository
after Version, AUR URL after URL, Maintainer after Packager, ...)
Verified on recorded claude-desktop records (paru 2.1.0, yay 13.0.1, the
installed 2.9939.4-1 from the AUR's own .SRCINFO); discovery/pacman.md
section 15.2.
152 lines
4.9 KiB
Bash
152 lines
4.9 KiB
Bash
#!/usr/bin/env bats
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2026 Bryan Joshua Pedini
|
|
|
|
# Root handling: what needs root, sudo re-exec with identical arguments, clear failures,
|
|
# and the commands that must work unprivileged.
|
|
|
|
load test_helper
|
|
|
|
setup() {
|
|
common_setup
|
|
export SKEPTICAL_UPDATER_EUID=1000
|
|
}
|
|
|
|
@test "the interactive upgrade as non-root without sudo fails clearly (exit 1)" {
|
|
run skup <<<''
|
|
assert_failure 1
|
|
assert_output --partial 'upgrading needs root and sudo is not available; run skeptical-updater as root (or use --dry-run)'
|
|
[[ -z $(cat "$FAKE_LOG") ]]
|
|
}
|
|
|
|
@test "list with a refresh needs root; without sudo it fails clearly" {
|
|
run skup list
|
|
assert_failure 1
|
|
assert_output --partial 'refreshing the package lists needs root (use --no-refresh)'
|
|
log_lacks 'update'
|
|
}
|
|
|
|
@test "list --no-refresh works unprivileged" {
|
|
run skup list --no-refresh
|
|
assert_success
|
|
assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:'
|
|
}
|
|
|
|
@test "trusted, info and show work unprivileged" {
|
|
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-10-09T09:40:12Z"
|
|
run skup trusted
|
|
assert_success
|
|
assert_line "apt${T}su-dummy-a${T}user${T}2026-10-09T09:40:12Z"
|
|
run skup info su-dummy-b
|
|
assert_success
|
|
assert_line 'reason: installed as a dependency'
|
|
assert_line 'required by: su-dummy-a'
|
|
run skup show su-dummy-a
|
|
assert_success
|
|
assert_line ' Package: su-dummy-a'
|
|
}
|
|
|
|
@test "--dry-run works unprivileged: the refresh is printed, not run" {
|
|
run skup --dry-run <<<''
|
|
assert_success
|
|
assert_line 'Refreshing package lists... skipped (not root), the command would be:'
|
|
assert_line '+ apt-get update -qq -o APT::Update::Error-Mode=any'
|
|
assert_line '+ apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o DPkg::Lock::Timeout=60 --with-new-pkgs upgrade'
|
|
log_lacks '^apt-get update'
|
|
log_lacks '^apt-get -y'
|
|
}
|
|
|
|
@test "with sudo available, non-root re-execs through sudo: this file itself, same arguments, hidden marker" {
|
|
use_fakes apt sudo
|
|
run skup --all list
|
|
assert_success
|
|
log_has "sudo -- $SU_BIN --sudo-reexec --state-dir=$STATE --all list"
|
|
log_lacks '^apt-get'
|
|
}
|
|
|
|
@test "the sudo re-exec carries --no-color when NO_COLOR is set" {
|
|
use_fakes apt sudo
|
|
unset SKEPTICAL_UPDATER_STATE_DIR
|
|
NO_COLOR=1 run skup --backend=apt
|
|
assert_success
|
|
log_has "sudo -- $SU_BIN --sudo-reexec --no-color --backend=apt"
|
|
}
|
|
|
|
@test "an explicit --state-dir is passed through unchanged" {
|
|
use_fakes apt sudo
|
|
run skup --state-dir=/srv/x list
|
|
assert_success
|
|
log_has "sudo -- $SU_BIN --sudo-reexec --state-dir=/srv/x list"
|
|
}
|
|
|
|
@test "a copy that is not executable and not in PATH is re-run through bash with its absolute path" {
|
|
use_fakes apt sudo
|
|
mkdir -p "$BATS_TEST_TMPDIR/copy"
|
|
cp "$SU_BIN" "$BATS_TEST_TMPDIR/copy/su-copy"
|
|
chmod 0644 "$BATS_TEST_TMPDIR/copy/su-copy"
|
|
cd "$BATS_TEST_TMPDIR/copy"
|
|
run env PATH="$TEST_PATH" bash su-copy list
|
|
assert_success
|
|
log_matches "^sudo -- /[^ ]*/bash $BATS_TEST_TMPDIR/copy/su-copy --sudo-reexec --state-dir=$STATE list\$"
|
|
}
|
|
|
|
@test "an executable copy invoked through bash is still re-run directly (sudoers rules match the file)" {
|
|
use_fakes apt sudo
|
|
mkdir -p "$BATS_TEST_TMPDIR/copy"
|
|
cp "$SU_BIN" "$BATS_TEST_TMPDIR/copy/su-copy"
|
|
chmod 0755 "$BATS_TEST_TMPDIR/copy/su-copy"
|
|
run env PATH="$TEST_PATH" bash "$BATS_TEST_TMPDIR/copy/su-copy" list
|
|
assert_success
|
|
log_has "sudo -- $BATS_TEST_TMPDIR/copy/su-copy --sudo-reexec --state-dir=$STATE list"
|
|
}
|
|
|
|
@test "after a sudo re-exec that did not make us root, there is no second re-exec" {
|
|
use_fakes apt sudo
|
|
run skup --sudo-reexec list
|
|
assert_failure 1
|
|
assert_output --partial 're-executed through sudo but still not running as root (euid 1000); not trying again'
|
|
log_lacks '^sudo'
|
|
}
|
|
|
|
@test "--sudo-reexec is only recognised as the first argument and is never forwarded" {
|
|
run skup list --sudo-reexec
|
|
assert_failure 1
|
|
assert_output --partial 'unknown option: --sudo-reexec'
|
|
SKEPTICAL_UPDATER_EUID=0 run skup --sudo-reexec list --no-refresh
|
|
assert_success
|
|
log_has 'apt-get -s --with-new-pkgs upgrade'
|
|
log_lacks 'sudo-reexec'
|
|
}
|
|
|
|
@test "trust into a writable state dir does not need root" {
|
|
use_fakes apt sudo
|
|
run skup trust su-dummy-c
|
|
assert_success
|
|
assert_output 'trusted su-dummy-c'
|
|
log_lacks '^sudo'
|
|
}
|
|
|
|
@test "trust into a non-writable state dir re-execs through sudo" {
|
|
ro=$BATS_TEST_DIRNAME/no-such-state-dir
|
|
[[ -w $BATS_TEST_DIRNAME ]] && skip "the repository is writable here (the suite mounts it read-only in Docker)"
|
|
use_fakes apt sudo
|
|
run skup --state-dir="$ro" trust su-dummy-c
|
|
assert_success
|
|
log_has "sudo -- $SU_BIN --sudo-reexec --state-dir=$ro trust su-dummy-c"
|
|
}
|
|
|
|
@test "--dry-run never re-execs through sudo" {
|
|
use_fakes apt sudo
|
|
run skup --dry-run list
|
|
assert_success
|
|
log_lacks '^sudo'
|
|
}
|
|
|
|
@test "as root nothing is re-executed" {
|
|
use_fakes apt sudo
|
|
SKEPTICAL_UPDATER_EUID=0 run skup list
|
|
assert_success
|
|
log_lacks '^sudo'
|
|
log_has 'apt-get update -qq -o APT::Update::Error-Mode=any'
|
|
}
|