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.
742 lines
31 KiB
Bash
742 lines
31 KiB
Bash
#!/usr/bin/env bats
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2026 Bryan Joshua Pedini
|
|
|
|
# Every backend parser, fed with the real outputs recorded in discovery/*.md
|
|
# (replayed by the fakes in tests/unit/fakes/, data in tests/unit/data/).
|
|
|
|
# each @test runs in its own subshell: per-test exports are intended
|
|
# shellcheck disable=SC2030,SC2031
|
|
|
|
load test_helper
|
|
|
|
setup() { common_setup; }
|
|
|
|
# ---- apt ----------------------------------------------------------------------------------------
|
|
|
|
@test "apt: Inst lines (all the edge cases) and the kept-back block (held, versions from policy)" {
|
|
FAKE_APT_SIM=real run skup list --no-refresh
|
|
assert_success
|
|
assert_line '10 upgradable, 0 trusted (hidden, use --all to show), 10 to review:'
|
|
assert_line --regexp '^ \[1\] libaudit-common +1:3\.1\.2-2\.1build1\.1 +-> +1:3\.1\.2-2\.1ubuntu0\.1 +Ubuntu:24\.04/noble-updates$'
|
|
assert_line --regexp '^ \[2\] libaudit1 +1:3\.1\.2-2\.1build1\.1 +-> +1:3\.1\.2-2\.1ubuntu0\.1 +Ubuntu:24\.04/noble-updates$'
|
|
assert_line --regexp '^ \[3\] libc6:i386 +2\.36-9 +-> +2\.36-9\+deb12u10 +Debian-Security:12/oldstable-security, Debian:12\.12/oldstable$'
|
|
assert_line --regexp '^ \[4\] libssl3t64 +3\.0\.13-0ubuntu3\.15 +-> +3\.0\.13-0ubuntu3\.16 +Ubuntu:24\.04/noble-updates, Ubuntu:24\.04/noble-security$'
|
|
assert_line --regexp '^ \[5\] perl-base +5\.36\.0-7\+deb12u3 +-> +5\.36\.0-7\+deb12u4 +http://deb\.debian\.org/debian-security bookworm-security/main amd64 \[held\]$'
|
|
assert_line --regexp '^ \[6\] su-dummy-c +1\.0 +-> +2\.0 +file:/srv/su-repo \./ \[held\]$'
|
|
assert_line --regexp '^ \[7\] su-dummy-e +1\.0 +-> +2\.0 +file:/srv/su-repo \./ \[held\]$'
|
|
assert_line --regexp '^ \[8\] su-dummy-m +1\.0 +-> +2\.0 +su-fixture:su-repo$'
|
|
assert_line --regexp '^ \[9\] su-dummy-newdep +- +-> +1\.0 +su-fixture:su-repo$'
|
|
assert_line --regexp '^ \[10\] su-dummy-slow +0\.9 +-> +1\.0$'
|
|
refute_output --partial 'su-dummy-f'
|
|
log_has 'apt-get -s --with-new-pkgs upgrade'
|
|
log_has 'apt-cache policy perl-base su-dummy-c su-dummy-e'
|
|
}
|
|
|
|
@test "apt: a held package from the fixture shows up with the held flag" {
|
|
FAKE_APT_SIM=held run skup list --no-refresh
|
|
assert_success
|
|
assert_line --regexp '^ \[1\] su-dummy-a +1\.0 +-> +2\.0 +su-fixture:su-repo$'
|
|
assert_line --regexp '^ \[3\] su-dummy-c +1\.0 +-> +2\.0 +file:/srv/su-repo \./ \[held\]$'
|
|
}
|
|
|
|
@test "apt: nothing to upgrade" {
|
|
FAKE_APT_SIM=none run skup list --no-refresh
|
|
assert_success
|
|
assert_line 'Nothing to upgrade.'
|
|
log_lacks '^apt-cache policy'
|
|
}
|
|
|
|
@test "apt: description = candidate Description; reverse deps deduplicated, '|' stripped" {
|
|
run skup info su-dummy-b
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests' 'reason: installed as a dependency' 'required by: su-dummy-a')"
|
|
log_has 'apt-cache show --no-all-versions su-dummy-b'
|
|
log_has 'apt-cache rdepends --installed --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances su-dummy-b'
|
|
run skup info gpgv
|
|
assert_success
|
|
assert_line 'gpgv: GNU privacy guard - signature verification tool'
|
|
assert_line 'required by: apt'
|
|
}
|
|
|
|
@test "apt: install reason from apt-mark showauto / showmanual, dpkg for a package in neither list" {
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests' 'reason: explicitly installed' 'required by: nothing')"
|
|
log_has 'apt-mark showauto su-dummy-a'
|
|
log_has 'apt-mark showmanual su-dummy-a'
|
|
log_lacks '^dpkg-query -W -f \$\{db:Status-Abbrev\} '
|
|
run skup info su-dummy-b
|
|
assert_line 'reason: installed as a dependency'
|
|
# apt-mark prints a native package without the ":arch" it was asked for
|
|
run skup info libc6:amd64
|
|
assert_line 'reason: installed as a dependency'
|
|
# installed but in neither list: manual
|
|
FAKE_APT_MARK_NEITHER=su-dummy-c run skup info su-dummy-c
|
|
assert_success
|
|
assert_line 'reason: explicitly installed'
|
|
# shellcheck disable=SC2016 # dpkg-query's own ${...} format, literally
|
|
log_has 'dpkg-query -W -f ${db:Status-Abbrev} su-dummy-c'
|
|
# removed with its configuration files kept (rc), or unknown to dpkg: not installed
|
|
run skup info su-dummy-f
|
|
assert_line 'reason: not installed'
|
|
run skup info gpgv
|
|
assert_success
|
|
assert_line 'reason: not installed'
|
|
# apt-mark cannot tell
|
|
FAKE_APT_MARK_SHOW_RC=100 run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'reason: unknown'
|
|
}
|
|
|
|
@test "apt: the description also comes from Description-<lang> fields (Translation files)" {
|
|
mkdir -p "$BATS_TEST_TMPDIR/data/apt/show"
|
|
cp -r "$FAKE_DATA"/. "$BATS_TEST_TMPDIR/data/"
|
|
sed -i 's/^Description: /Description-en: /' "$BATS_TEST_TMPDIR/data/apt/show/su-dummy-a"
|
|
sed -i 's/^Description: /Description-pt_BR: /' "$BATS_TEST_TMPDIR/data/apt/show/su-dummy-b"
|
|
FAKE_DATA=$BATS_TEST_TMPDIR/data run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests'
|
|
FAKE_DATA=$BATS_TEST_TMPDIR/data run skup info su-dummy-b
|
|
assert_line 'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests'
|
|
# Description-md5 is not a description
|
|
sed -i '/^Description-en: /d' "$BATS_TEST_TMPDIR/data/apt/show/su-dummy-a"
|
|
FAKE_DATA=$BATS_TEST_TMPDIR/data run skup info su-dummy-a
|
|
assert_line 'su-dummy-a: (no description available)'
|
|
}
|
|
|
|
@test "apt: info of an unknown package exits 1" {
|
|
run skup info no-such-pkg-xyz
|
|
assert_failure 1
|
|
assert_line 'no-such-pkg-xyz: (no description available: unknown package?)'
|
|
}
|
|
|
|
@test "apt: show merges dpkg's installed record (dpkg-query -s) with the candidate (apt-cache show)" {
|
|
# changed Version, local-only Status/Conffiles, identical fields once, a differing multi-line
|
|
# Description (prefixed on every line), candidate-only fields appended
|
|
run skup show su-dummy-b
|
|
assert_success
|
|
assert_output "$(cat <<'EOF'
|
|
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks
|
|
Package: su-dummy-b
|
|
Status: install ok installed
|
|
Priority: optional
|
|
Section: misc
|
|
Maintainer: Skeptical Updater Fixture <su-fixture@example.invalid>
|
|
Architecture: all
|
|
Version: 1.0
|
|
> Version: 2.0
|
|
Conffiles:
|
|
/etc/su-dummy/su-dummy-b.conf 607e2d3a119dfe832d937ce579ef1079
|
|
Description: dummy package su-dummy-b for skeptical-updater tests
|
|
This package only exists to exercise the package manager.
|
|
.
|
|
It ships one data file and one conffile (version 1.0).
|
|
> Description: dummy package su-dummy-b for skeptical-updater tests
|
|
> This package only exists to exercise the package manager.
|
|
> .
|
|
> It ships one data file and one conffile (version 2.0).
|
|
> Description-md5: 36da7dcdaf4ec207be2937d1f5dfa408
|
|
> Filename: ./su-dummy-b_2.0_all.deb
|
|
> Size: 840
|
|
EOF
|
|
)"
|
|
log_has 'dpkg-query -s -- su-dummy-b'
|
|
log_has 'apt-cache show --no-all-versions su-dummy-b'
|
|
run skup show su-dummy-a
|
|
assert_success
|
|
assert_line 'Depends: su-dummy-b'
|
|
assert_line '> Depends: su-dummy-b (>= 2.0)'
|
|
run skup show no-such-pkg-xyz
|
|
assert_failure 1
|
|
assert_output --partial "no package information for 'no-such-pkg-xyz' (apt exited 100)"
|
|
}
|
|
|
|
@test "apt: a one-line candidate Description (the archives' short form) equals the installed long one" {
|
|
# real debian:12 records: perl-base 5.36.0-7+deb12u3 installed, +deb12u4 from bookworm-security
|
|
run skup show perl-base
|
|
assert_success
|
|
[[ $(grep -c '^Description: minimal Perl system$' <<<"$output") -eq 1 ]]
|
|
refute_line --partial '> Description:'
|
|
assert_output --partial "$(printf '%s\n' 'Installed-Size: 7639' '> Installed-Size: 7643')"
|
|
assert_output --partial "$(printf '%s\n' 'Version: 5.36.0-7+deb12u3' '> Version: 5.36.0-7+deb12u4')"
|
|
[[ $(grep -c '^Breaks: ' <<<"$output") -eq 1 ]]
|
|
refute_line --regexp '^> (Breaks|Pre-Depends|Essential|Source):'
|
|
assert_line --index -1 '> SHA256: d7d1943aec9597629bf73075efcc5ef6dc9bda96d78e80d843156ecd448478b8'
|
|
}
|
|
|
|
@test "apt: show of a package that is not installed prints the candidate alone, with a note" {
|
|
run skup show zsh
|
|
assert_success
|
|
assert_line --index 0 '# not installed: this is the upstream candidate'
|
|
assert_line --index 1 'Package: zsh'
|
|
assert_line --index 3 'Version: 5.9-4+b15'
|
|
refute_line --partial '> '
|
|
log_has 'dpkg-query -s -- zsh'
|
|
}
|
|
|
|
# ---- pacman -------------------------------------------------------------------------------------
|
|
|
|
@test "pacman: -Qu joined with the FIRST -Sl repo; [ignored] => held" {
|
|
use_fakes pacman
|
|
FAKE_PACMAN_QU=real run skup list --no-refresh
|
|
assert_success
|
|
assert_line '6 upgradable, 0 trusted (hidden, use --all to show), 6 to review:'
|
|
assert_line ' [1] ca-certificates-mozilla 3.130-1 -> 3.131-1 core'
|
|
assert_line --regexp '^ \[2\] expat +2\.8\.5-1 +-> +2\.9\.0-1 +core \[held\]$'
|
|
assert_line --regexp '^ \[3\] glib2 +2\.88\.3-1 +-> +2\.90\.1-1 +core \[held\]$'
|
|
assert_line --regexp '^ \[5\] su-dummy-b +1\.0-1 +-> +2\.0-1 +su-dummy$'
|
|
refute_output --partial 'su-dummy2'
|
|
}
|
|
|
|
@test "pacman: rows survive an empty 'pacman -Sl' (no repo column instead of a broken join)" {
|
|
use_fakes pacman
|
|
FAKE_PACMAN_SL=empty run skup list --no-refresh
|
|
assert_success
|
|
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'
|
|
}
|
|
|
|
@test "pacman: packages only the transaction (-Sup) shows become a note" {
|
|
use_fakes pacman
|
|
FAKE_PACMAN_SUP_EXTRA='su-dummy-new' run skup list --no-refresh
|
|
assert_success
|
|
assert_line 'Note: the upgrade also installs (new dependencies or replacements): su-dummy-new'
|
|
}
|
|
|
|
@test "pacman: nothing in -Qu but pending work in -Sup: the upgrade is still offered" {
|
|
use_fakes pacman
|
|
FAKE_PACMAN_QU=none FAKE_PACMAN_SUP_EXTRA='su-dummy-new' run skup --no-refresh <<<''
|
|
assert_success
|
|
assert_line '0 to review, but pacman reports pending changes (see the notes): <enter> runs the upgrade.'
|
|
log_has 'pacman -Su --noconfirm'
|
|
}
|
|
|
|
@test "pacman: nothing upgradable (-Qu exits 1 with no output)" {
|
|
use_fakes pacman
|
|
FAKE_PACMAN_QU=none run skup list --no-refresh
|
|
assert_success
|
|
assert_line 'Nothing to upgrade.'
|
|
}
|
|
|
|
@test "pacman: description from -Qi, falling back to -Si for packages that are not installed" {
|
|
use_fakes pacman
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'su-dummy-a: Skeptical-updater dummy package A'
|
|
assert_line 'required by: nothing'
|
|
run skup info su-dummy-new
|
|
assert_success
|
|
assert_line 'su-dummy-new: Skeptical-updater dummy package NEW'
|
|
}
|
|
|
|
@test "pacman: reverse deps from 'Required By', wrapped continuation lines joined" {
|
|
use_fakes pacman
|
|
run skup info glibc
|
|
assert_success
|
|
assert_line 'glibc: GNU C Library'
|
|
assert_line 'required by: bash, gcc-libs, ncurses, readline'
|
|
run skup info su-dummy-b
|
|
assert_line 'required by: su-dummy-a'
|
|
}
|
|
|
|
@test "pacman: install reason from the 'Install Reason' of -Qi; -Qi failing = not installed" {
|
|
use_fakes pacman
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-a: Skeptical-updater dummy package A' 'reason: explicitly installed' 'required by: nothing')"
|
|
run skup info su-dummy-b
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-b: Skeptical-updater dummy package B (dependency of A)' 'reason: installed as a dependency' 'required by: su-dummy-a')"
|
|
run skup info glibc
|
|
assert_line 'reason: installed as a dependency'
|
|
# in a sync database only
|
|
run skup info su-dummy-new
|
|
assert_success
|
|
assert_line 'reason: not installed'
|
|
# a value this tool does not know, or no such field
|
|
FAKE_PACMAN_REASON='su-dummy-c=Installed for a reason pacman 9 invented' run skup info su-dummy-c
|
|
assert_success
|
|
assert_line 'reason: unknown'
|
|
FAKE_PACMAN_REASON='su-dummy-c=' run skup info su-dummy-c
|
|
assert_line 'reason: unknown'
|
|
log_has 'pacman -Qi -- su-dummy-c'
|
|
}
|
|
|
|
@test "pacman: the closure resolves provides (sonames, sh) and version constraints" {
|
|
use_fakes pacman
|
|
run skup trust bash
|
|
assert_success
|
|
assert_output 'trusted bash (+8 dependencies: filesystem gcc-libs glibc iana-etc linux-api-headers ncurses readline tzdata)'
|
|
run skup trust su-dummy-a
|
|
assert_output 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
|
|
}
|
|
|
|
@test "pacman: show merges -Qi with -Si of the candidate's repository: '>' lines for what differs" {
|
|
# real archlinux records (glib2 2.88.3-1 installed, core 2.90.1-1), one Optional Deps entry
|
|
# added to the candidate: changed fields, identical ones once, -Qi-only fields kept, a differing
|
|
# multi-line block, -Si-only fields appended
|
|
use_fakes pacman
|
|
run skup show glib2
|
|
assert_success
|
|
assert_output "$(cat <<'EOF'
|
|
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks
|
|
Name : glib2
|
|
Version : 2.88.3-1
|
|
> Version : 2.90.1-1
|
|
Description : Low level core library
|
|
Architecture : x86_64
|
|
URL : https://gitlab.gnome.org/GNOME/glib
|
|
Licenses : LGPL-2.1-or-later
|
|
Groups : None
|
|
Provides : libglib-2.0.so=0-64 libgio-2.0.so=0-64 libgirepository-2.0.so=0-64 libgmodule-2.0.so=0-64 libgobject-2.0.so=0-64 libgthread-2.0.so=0-64
|
|
Depends On : bash glibc libffi libsysprof-capture pcre2 util-linux-libs zlib libffi.so=8-64 libmount.so=1-64
|
|
Optional Deps : dconf: GSettings storage backend
|
|
glib2-devel: development tools
|
|
gvfs: most gio functionality
|
|
> Optional Deps : dconf: GSettings storage backend
|
|
> glib2-devel: development tools
|
|
> gvfs: most gio functionality
|
|
> python: gdbus-codegen
|
|
Required By : libsecret pinentry
|
|
Optional For : None
|
|
Conflicts With : None
|
|
Replaces : None
|
|
Installed Size : 37.89 MiB
|
|
> Installed Size : 38.39 MiB
|
|
Packager : Jan Alexander Steffens (heftig) <heftig@archlinux.org>
|
|
Build Date : Wed Jul 29 14:57:16 2026
|
|
> Build Date : Tue Oct 6 16:19:12 2026
|
|
Install Date : Sun Oct 4 00:00:00 2026
|
|
Install Reason : Installed as a dependency for another package
|
|
Install Script : No
|
|
Validated By : Signature
|
|
> Validated By : SHA-256 Sum Signature
|
|
> Repository : core
|
|
> Download Size : 5.12 MiB
|
|
EOF
|
|
)"
|
|
log_has 'pacman -Qi -- glib2'
|
|
log_has 'pacman -Si -- core/glib2'
|
|
}
|
|
|
|
@test "pacman: the candidate comes from the first repository -Sl lists; no -Si record = the plain -Qi" {
|
|
use_fakes pacman
|
|
run skup show su-dummy-b
|
|
assert_success
|
|
assert_output --partial "$(printf '%s\n' 'Version : 1.0-1' '> Version : 2.0-1')"
|
|
assert_line 'Required By : su-dummy-a'
|
|
refute_output --partial '3.0-1'
|
|
log_has 'pacman -Si -- su-dummy/su-dummy-b'
|
|
# bash: in the sync list, but no -Si record - the output stays what it was (-Qi, then -Si)
|
|
run skup show bash
|
|
assert_success
|
|
assert_line --index 0 'Name : bash'
|
|
refute_line --partial '# lines starting with >'
|
|
# not installed: the candidate alone
|
|
run skup show su-dummy-new
|
|
assert_success
|
|
assert_line --index 0 '# not installed: this is the upstream candidate'
|
|
assert_line --index 1 'Repository : su-dummy'
|
|
refute_line --partial '> '
|
|
run skup show nope
|
|
assert_failure 1
|
|
}
|
|
|
|
@test "pacman: every query runs with COLUMNS=0 (no wrapping)" {
|
|
use_fakes pacman
|
|
export COLUMNS=20
|
|
run skup info glibc
|
|
run skup list --no-refresh
|
|
run skup trust bash
|
|
run skup show glib2
|
|
run grep -v ' 0$' "$FAKE_DIR/pacman-columns"
|
|
assert_output ''
|
|
}
|
|
|
|
# ---- dnf ----------------------------------------------------------------------------------------
|
|
|
|
@test "dnf4: check-update (blank line, padding, Obsoleting section), old version from rpm -qa" {
|
|
use_fakes dnf rpm
|
|
run skup list --no-refresh
|
|
assert_success
|
|
assert_line '6 upgradable, 0 trusted (hidden, use --all to show), 6 to review:'
|
|
assert_line ' [1] gnutls 3.8.10-4.el9_8 -> 3.8.10-9.el9_8 baseos'
|
|
# installonly: the highest installed version is the old one
|
|
assert_line --regexp '^ \[2\] kernel-core +5\.14\.0-611\.5\.el9 +-> +5\.14\.0-612\.1\.el9 +baseos$'
|
|
assert_line --regexp '^ \[3\] su-dummy-a +1\.0-1 +-> +2\.0-1 +su-dummy$'
|
|
assert_line --regexp '^ \[5\] su-dummy-c +1\.0-1 +-> +2\.0-1 +su-dummy$'
|
|
# epoch kept on both sides
|
|
assert_line --regexp '^ \[6\] vim-minimal +2:8\.2\.2637-26\.el9_8\.21 +-> +2:8\.2\.2637-26\.el9_8\.23 +baseos$'
|
|
refute_line --regexp '\] +su-dummy-new'
|
|
refute_output --partial 'su-dummy-old'
|
|
refute_output --partial 'Obsoleting'
|
|
assert_line 'Note: the upgrade also installs (obsoleting installed packages): su-dummy-new'
|
|
}
|
|
|
|
@test "dnf: check-update exit 100 with only obsoleting packages: 0 rows, but the upgrade is offered" {
|
|
use_fakes dnf rpm
|
|
FAKE_DNF_CHECK=obsoletes run skup --no-refresh <<<''
|
|
assert_success
|
|
assert_line '0 upgradable, 0 trusted (hidden, use --all to show), 0 to review:'
|
|
assert_line 'Note: the upgrade also installs (obsoleting installed packages): su-dummy-new'
|
|
assert_line '0 to review, but dnf reports pending changes (see the notes): <enter> runs the upgrade.'
|
|
refute_output --partial 'Nothing to upgrade.'
|
|
log_has 'dnf -y --setopt=skip_if_unavailable=0 --setopt=*.metadata_expire=-1 upgrade'
|
|
}
|
|
|
|
@test "dnf5: the 'Upgrades' header is skipped" {
|
|
use_fakes dnf rpm
|
|
FAKE_DNF_MAJOR=5 run skup list --no-refresh
|
|
assert_success
|
|
assert_line '5 upgradable, 0 trusted (hidden, use --all to show), 5 to review:'
|
|
assert_line --regexp '^ \[1\] ca-certificates +2025\.2\.80_v9\.0\.305-91\.el9 +-> +2026\.2\.90_v9\.0\.317-1\.fc44 +updates$'
|
|
assert_line --regexp '^ \[2\] curl +8\.18\.0-1\.fc44 +-> +8\.18\.0-12\.fc44 +updates$'
|
|
refute_output --partial 'Upgrades'
|
|
}
|
|
|
|
@test "dnf: check-update exit 0 means nothing to upgrade, exit 1 is an error (exit 1)" {
|
|
use_fakes dnf rpm
|
|
FAKE_DNF_CHECK_RC=0 run skup list --no-refresh
|
|
assert_success
|
|
assert_line 'Nothing to upgrade.'
|
|
FAKE_DNF_CHECK_RC=1 run skup list --no-refresh
|
|
assert_failure 1
|
|
assert_output --partial 'dnf check-update failed (exit 1)'
|
|
assert_output --partial 'could not list the upgradable packages'
|
|
}
|
|
|
|
@test "dnf: description from repoquery (candidate), rpm fallback when not upgradable" {
|
|
use_fakes dnf rpm
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'su-dummy-a: skeptical-updater dummy su-dummy-a (v2.0)'
|
|
run skup info su-dummy-d
|
|
assert_success
|
|
assert_line 'su-dummy-d: skeptical-updater dummy su-dummy-d (v1.0)'
|
|
assert_line 'required by: nothing'
|
|
FAKE_DNF_MAJOR=5 run skup info su-dummy-a
|
|
assert_line 'su-dummy-a: skeptical-updater dummy su-dummy-a (v2.0)'
|
|
}
|
|
|
|
@test "rpm reverse deps: provides + owned files joined with every requirement (rich deps too)" {
|
|
use_fakes dnf rpm
|
|
run skup info su-dummy-b
|
|
assert_line 'required by: su-dummy-a, su-dummy-d'
|
|
run skup info su-dummy-c
|
|
assert_line 'required by: su-dummy-d'
|
|
run skup info bash
|
|
assert_line 'required by: filesystem, glibc-common, redhat-rpm-config, rpm, su-dummy-d'
|
|
}
|
|
|
|
@test "dnf4: install reason from repoquery --installed %{reason}; plain rpm -i = unknown" {
|
|
use_fakes dnf rpm
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-a: skeptical-updater dummy su-dummy-a (v2.0)' 'reason: explicitly installed' 'required by: su-dummy-d')"
|
|
log_has 'dnf -q repoquery --installed --queryformat %{reason}'
|
|
run skup info su-dummy-b
|
|
assert_line 'reason: installed as a dependency'
|
|
run skup info su-dummy-c
|
|
assert_line 'reason: unknown'
|
|
# weak-dependency counts as a dependency, a group member as asked for
|
|
run skup info glibc-gconv-extra
|
|
assert_line 'reason: installed as a dependency'
|
|
run skup info basesystem
|
|
assert_line 'reason: explicitly installed'
|
|
# only in a repository: repoquery --installed prints nothing
|
|
run skup info su-dummy-new
|
|
assert_success
|
|
assert_line 'su-dummy-new: skeptical-updater dummy su-dummy-new (v1.0)'
|
|
assert_line 'reason: not installed'
|
|
# repoquery failing: unknown
|
|
FAKE_DNF_REPOQUERY_RC=1 run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'reason: unknown'
|
|
}
|
|
|
|
@test "dnf5: install reason in dnf5's words (External User = plain rpm -i, counted as user-installed)" {
|
|
use_fakes dnf rpm
|
|
export FAKE_DNF_MAJOR=5
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'reason: explicitly installed'
|
|
run skup info su-dummy-b
|
|
assert_line 'reason: installed as a dependency'
|
|
run skup info su-dummy-c
|
|
assert_line 'reason: explicitly installed'
|
|
run skup info glibc-gconv-extra
|
|
assert_line 'reason: installed as a dependency'
|
|
FAKE_DNF_REASON='su-dummy-c=None' run skup info su-dummy-c
|
|
assert_line 'reason: unknown'
|
|
run skup info su-dummy-new
|
|
assert_line 'reason: not installed'
|
|
}
|
|
|
|
@test "rpm closure: file deps, rich deps without their condition, rpmlib() dropped" {
|
|
use_fakes dnf rpm
|
|
run skup trust su-dummy-d
|
|
assert_success
|
|
assert_output 'trusted su-dummy-d (+11 dependencies: basesystem bash filesystem glibc glibc-common glibc-gconv-extra ncurses-libs setup su-dummy-a su-dummy-b su-dummy-c)'
|
|
refute_output --partial 'redhat-rpm-config'
|
|
refute_output --partial 'rpmlib'
|
|
run skup trust su-dummy-a
|
|
assert_output 'trusted su-dummy-a (its installed dependency was already trusted)'
|
|
}
|
|
|
|
@test "dnf4: a removal-table row wrapped onto two lines (long name) is one package" {
|
|
use_fakes dnf rpm
|
|
export FAKE_RPM_NO_D=1
|
|
FAKE_DNF_UNUSED=su-dummy-a:su-dummy-b FAKE_DNF_WRAP=1 run skup --no-refresh <<<$'3x\ny\nq'
|
|
assert_success
|
|
assert_output --partial 'Purge su-dummy-a now? This also removes: su-dummy-b [y/N]'
|
|
refute_output --partial 'noarch'
|
|
log_has 'dnf -y remove su-dummy-a su-dummy-b'
|
|
}
|
|
|
|
@test "dnf5: a wrapped removal-table row is one package as well" {
|
|
use_fakes dnf rpm
|
|
export FAKE_RPM_NO_D=1
|
|
FAKE_DNF_MAJOR=5 FAKE_DNF_UNUSED=su-dummy-a:su-dummy-b FAKE_DNF_WRAP=1 run skup --no-refresh <<<$'3x\ny\nq'
|
|
assert_success
|
|
assert_output --partial 'Purge su-dummy-a now? This also removes: su-dummy-b [y/N]'
|
|
log_has 'dnf -y remove su-dummy-a su-dummy-b'
|
|
}
|
|
|
|
@test "dnf4: show merges info --installed with info --upgrades (headings dropped)" {
|
|
use_fakes dnf rpm
|
|
run skup show su-dummy-b
|
|
assert_success
|
|
assert_output "$(cat <<'EOF'
|
|
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks
|
|
Name : su-dummy-b
|
|
Version : 1.0
|
|
> Version : 2.0
|
|
Release : 1
|
|
Architecture : noarch
|
|
Size : 15
|
|
> Size : 6.5 k
|
|
Source : su-dummy-b-1.0-1.src.rpm
|
|
> Source : su-dummy-b-2.0-1.src.rpm
|
|
Repository : @System
|
|
> Repository : su-dummy
|
|
Summary : skeptical-updater dummy su-dummy-b (v1.0)
|
|
> Summary : skeptical-updater dummy su-dummy-b (v2.0)
|
|
License : MIT
|
|
Description : Dummy package su-dummy-b version 1.0 for skeptical-updater tests.
|
|
:
|
|
: Second paragraph of su-dummy-b 1.0 (blank line above on purpose).
|
|
> Description : Dummy package su-dummy-b version 2.0 for skeptical-updater tests.
|
|
> :
|
|
> : Second paragraph of su-dummy-b 2.0 (blank line above on purpose).
|
|
EOF
|
|
)"
|
|
log_has 'dnf -q info --installed su-dummy-b'
|
|
log_has 'dnf -q info --upgrades su-dummy-b'
|
|
# real almalinux:9 records: an identical multi-line Description once, "From repo" (installed only) kept
|
|
run skup show glibc
|
|
assert_success
|
|
assert_output --partial "$(printf '%s\n' 'Release : 275.el9_8' '> Release : 283.el9_8')"
|
|
assert_line 'From repo : baseos'
|
|
[[ $(grep -c '^Description : The glibc package contains' <<<"$output") -eq 1 ]]
|
|
[[ $(grep -c 'Linux system will not function\.$' <<<"$output") -eq 1 ]]
|
|
}
|
|
|
|
@test "dnf5: show merges records whose keys are padded differently (re-padded, extra fields appended)" {
|
|
use_fakes dnf rpm
|
|
FAKE_DNF_MAJOR=5 run skup show su-dummy-b
|
|
assert_success
|
|
assert_output "$(cat <<'EOF'
|
|
# lines starting with > come from the upstream candidate: a differing value, or a field the installed record lacks
|
|
Name : su-dummy-b
|
|
Epoch : 0
|
|
Version : 1.0
|
|
> Version : 2.0
|
|
Release : 1
|
|
Architecture : noarch
|
|
Installed size : 15.0 B
|
|
Source : su-dummy-b-1.0-1.src.rpm
|
|
> Source : su-dummy-b-2.0-1.src.rpm
|
|
From repository : <unknown>
|
|
Summary : skeptical-updater dummy su-dummy-b (v1.0)
|
|
> Summary : skeptical-updater dummy su-dummy-b (v2.0)
|
|
URL :
|
|
License : MIT
|
|
Description : Dummy package su-dummy-b version 1.0 for skeptical-updater tests.
|
|
:
|
|
: Second paragraph of su-dummy-b 1.0 (blank line above on purpose).
|
|
> Description : Dummy package su-dummy-b version 2.0 for skeptical-updater tests.
|
|
> :
|
|
> : Second paragraph of su-dummy-b 2.0 (blank line above on purpose).
|
|
Vendor : <NULL>
|
|
> Vendor :
|
|
> Download size : 6.6 KiB
|
|
> Repository : su-dummy
|
|
EOF
|
|
)"
|
|
}
|
|
|
|
@test "dnf: not installed = info --available alone; not one record per side = the plain dnf -q info" {
|
|
use_fakes dnf rpm
|
|
run skup show zsh
|
|
assert_success
|
|
assert_line --index 0 '# not installed: this is the upstream candidate'
|
|
assert_line --index 1 'Name : zsh'
|
|
refute_line 'Available Packages'
|
|
log_has 'dnf -q info --available zsh'
|
|
# two installed records (e.g. two architectures): nothing to pair, the output stays dnf -q info
|
|
FAKE_DNF_INFO_TWICE=installed run skup show su-dummy-a
|
|
assert_success
|
|
refute_line --partial '# lines starting with >'
|
|
assert_line 'Installed Packages'
|
|
assert_line 'Available Upgrades'
|
|
log_has 'dnf -q info su-dummy-a'
|
|
}
|
|
|
|
# ---- zypper -------------------------------------------------------------------------------------
|
|
|
|
@test "zypper: --xmlout list-updates (one-line XML, multi-line descriptions, last record, entities)" {
|
|
use_fakes zypper rpm
|
|
FAKE_ZYPPER_LU=real run skup list --no-refresh
|
|
assert_success
|
|
assert_line '4 upgradable, 0 trusted (hidden, use --all to show), 4 to review:'
|
|
assert_line ' [1] openSUSE-build-key 1.0-68.1 -> 1.0-69.1 repo-update'
|
|
assert_line --regexp '^ \[2\] su-dummy-a +1\.0-1 +-> +2\.0-1 +su-dummy$'
|
|
assert_line --regexp '^ \[3\] su-dummy-b +1\.0-1 +-> +2\.0-1 +openSUSE:repo-oss & co$'
|
|
assert_line --regexp '^ \[4\] su-dummy-c +1\.0-1 +-> +2\.0-1 +su-dummy$'
|
|
log_has 'zypper --non-interactive --xmlout list-updates'
|
|
}
|
|
|
|
@test "zypper on Tumbleweed (root): the review list is the dist-upgrade plan" {
|
|
use_fakes zypper rpm
|
|
export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-tumbleweed
|
|
run skup list --no-refresh
|
|
assert_success
|
|
log_has 'zypper --non-interactive --no-refresh --xmlout dist-upgrade --dry-run'
|
|
log_lacks 'list-updates'
|
|
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 'openSUSE-release'
|
|
}
|
|
|
|
@test "zypper on Tumbleweed: new packages are rows (old -), removals are a note" {
|
|
use_fakes zypper rpm
|
|
export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-tumbleweed
|
|
FAKE_ZYPPER_DUP=removes run skup list --no-refresh
|
|
assert_success
|
|
assert_line ' [2] su-dummy-newdep - -> 1.0-1 su-dummy'
|
|
assert_line 'Note: the dist-upgrade also removes: libcurl-mini4'
|
|
}
|
|
|
|
@test "zypper on Tumbleweed: a plan with only removals is not 'Nothing to upgrade'" {
|
|
use_fakes zypper rpm
|
|
export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-tumbleweed
|
|
FAKE_ZYPPER_DUP=onlyremove run skup --no-refresh <<<''
|
|
assert_success
|
|
assert_line '0 to review, but zypper reports pending changes (see the notes): <enter> runs the upgrade.'
|
|
log_has 'zypper --non-interactive --no-refresh dist-upgrade'
|
|
FAKE_ZYPPER_DUP=empty run skup --no-refresh </dev/null
|
|
assert_success
|
|
assert_line 'Nothing to upgrade.'
|
|
}
|
|
|
|
@test "zypper on Tumbleweed as non-root: list-updates with a note (the plan needs root)" {
|
|
use_fakes zypper rpm
|
|
export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-tumbleweed
|
|
SKEPTICAL_UPDATER_EUID=1000 run skup list --no-refresh
|
|
assert_success
|
|
log_has 'zypper --non-interactive --xmlout list-updates'
|
|
log_lacks 'dist-upgrade'
|
|
assert_line 'Note: not root: this is "zypper list-updates"; the dist-upgrade plan (which may differ) needs root'
|
|
}
|
|
|
|
@test "zypper: an empty update list" {
|
|
use_fakes zypper rpm
|
|
FAKE_ZYPPER_LU=none run skup list --no-refresh
|
|
assert_success
|
|
assert_line 'Nothing to upgrade.'
|
|
}
|
|
|
|
@test "zypper: description from 'zypper info' Summary, rpm fallback when zypper exits 104" {
|
|
use_fakes zypper rpm
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_line 'su-dummy-a: skeptical-updater dummy su-dummy-a (v2.0)'
|
|
run skup info su-dummy-d
|
|
assert_success
|
|
assert_line 'su-dummy-d: skeptical-updater dummy su-dummy-d (v1.0)'
|
|
run skup info nothing-like-this
|
|
assert_failure 1
|
|
}
|
|
|
|
@test "zypper: show is zypper info --requires; unknown package => exit 1" {
|
|
use_fakes zypper rpm
|
|
run skup show su-dummy-a
|
|
assert_success
|
|
assert_line 'Information for package su-dummy-a:'
|
|
refute_line --partial '# lines starting with >'
|
|
log_has 'zypper --non-interactive --quiet info --requires su-dummy-a'
|
|
run skup show nothing-like-this
|
|
assert_failure 1
|
|
assert_output --partial "(zypper exited 104)"
|
|
}
|
|
|
|
@test "zypper: reverse deps and closure use the same pure-rpm code" {
|
|
use_fakes zypper rpm
|
|
run skup info su-dummy-c
|
|
assert_line 'required by: su-dummy-d'
|
|
run skup trust su-dummy-a
|
|
assert_output 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
|
|
run cut -f1 "$STATE_FILE"
|
|
assert_line zypper
|
|
}
|
|
|
|
@test "zypper: install reason from the Installed line of zypper info (not 'Installed Size')" {
|
|
use_fakes zypper rpm
|
|
run skup info su-dummy-a
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-a: skeptical-updater dummy su-dummy-a (v2.0)' 'reason: explicitly installed' 'required by: su-dummy-d')"
|
|
log_has 'zypper --non-interactive --no-refresh --quiet info su-dummy-a'
|
|
run skup info su-dummy-b
|
|
assert_success
|
|
assert_output "$(printf '%s\n' 'su-dummy-b: skeptical-updater dummy su-dummy-b (v2.0)' 'reason: installed as a dependency' 'required by: su-dummy-a, su-dummy-d')"
|
|
# only in a repository
|
|
run skup info su-dummy-orphan-parent
|
|
assert_success
|
|
assert_line 'su-dummy-orphan-parent: skeptical-updater dummy su-dummy-orphan-parent (v1.0)'
|
|
assert_line 'reason: not installed'
|
|
# unknown to zypper (exit 104) and to rpm
|
|
run skup info nothing-like-this
|
|
assert_failure 1
|
|
assert_line 'reason: not installed'
|
|
}
|
|
|
|
@test "zypper: without an answer from zypper (lock held: exit 7) rpm + libzypp's AutoInstalled decide" {
|
|
use_fakes zypper rpm
|
|
export SKEPTICAL_UPDATER_ZYPP_ROOT=$BATS_TEST_TMPDIR/zroot FAKE_ZYPPER_INFO_RC=7
|
|
# no readable AutoInstalled: the tool cannot tell
|
|
run skup info su-dummy-b
|
|
assert_success
|
|
assert_line 'su-dummy-b: skeptical-updater dummy su-dummy-b (v1.0)'
|
|
assert_line 'reason: unknown'
|
|
mkdir -p "$SKEPTICAL_UPDATER_ZYPP_ROOT/var/lib/zypp"
|
|
printf '# AutoInstalled generated Sat Oct 10 11:14:20 2026\n#\nsu-dummy-b\npattern:minimal_base\n#\n' >"$SKEPTICAL_UPDATER_ZYPP_ROOT/var/lib/zypp/AutoInstalled"
|
|
run skup info su-dummy-b
|
|
assert_line 'reason: installed as a dependency'
|
|
run skup info su-dummy-a
|
|
assert_line 'reason: explicitly installed'
|
|
run skup info su-dummy-orphan-parent
|
|
assert_line 'reason: not installed'
|
|
}
|
|
|
|
@test "zypper on Tumbleweed: N? on a new dependency (old -) says 'not installed' without asking" {
|
|
use_fakes zypper rpm
|
|
export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-tumbleweed
|
|
FAKE_ZYPPER_DUP=removes run skup --no-refresh <<<$'2?\nq'
|
|
assert_success
|
|
assert_line ' [2] su-dummy-newdep - -> 1.0-1 su-dummy'
|
|
assert_line 'reason: not installed'
|
|
assert_line 'required by: nothing'
|
|
log_lacks 'zypper --non-interactive --no-refresh --quiet info su-dummy-newdep'
|
|
}
|