Files
skeptical-updater/tests/unit/parsers.bats
T
2026-10-10 10:37:53 +02:00

402 lines
17 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' ' 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-<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 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): <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 (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): <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 (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): <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:'
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
}