#!/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' ' required by (installed): 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 (installed): apt' } @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 is apt-cache show --no-all-versions (the candidate)" { run skup show su-dummy-a assert_success assert_line 'Package: su-dummy-a' assert_line 'Version: 2.0' 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)" } # ---- 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 (installed): 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 (installed): bash, gcc-libs, ncurses, readline' run skup info su-dummy-b assert_line ' required by (installed): su-dummy-a' } @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 prints the installed (-Qi) and the candidate (-Si) records" { use_fakes pacman run skup show su-dummy-a assert_success assert_line 'Install Reason : Explicitly installed' assert_line 'Repository : su-dummy' assert_line 'Version : 2.0-1' log_has 'pacman -Qi -- su-dummy-a' log_has 'pacman -Si -- su-dummy-a' 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 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 (installed): 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 (installed): su-dummy-a, su-dummy-d' run skup info su-dummy-c assert_line ' required by (installed): su-dummy-d' run skup info bash assert_line ' required by (installed): filesystem, glibc-common, redhat-rpm-config, rpm, su-dummy-d' } @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 "dnf: show is dnf -q info" { use_fakes dnf rpm run skup show su-dummy-a assert_success assert_line 'Available Upgrades' assert_line 'Version : 2.0' 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:' 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 (installed): 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 }