#!/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- 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 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): 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) 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): 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 : 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 : > 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): runs the upgrade.' log_has 'zypper --non-interactive --no-refresh dist-upgrade' FAKE_ZYPPER_DUP=empty run skup --no-refresh 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' }