feat(info): show the install reason and drop the indentation

N? and `info PKG` now print three lines, none indented:

    PKG: <description>
    reason: explicitly installed | installed as a dependency | unknown | not installed
    required by: <installed dependents, comma separated> | nothing

The reason comes from a new <be>_reason backend function: apt-mark
showauto / showmanual (dpkg decides for a package in neither list), the
Install Reason of pacman -Qi, dnf repoquery --installed %{reason} (dnf4
and dnf5 words) and the Installed line of zypper info (rpm plus libzypp's
AutoInstalled when zypper is locked). A row whose old version is "-" is
not installed and the backend is not asked. The "(installed)" qualifier
moves to --help, README and DESIGN.

The rpm E2E fixture and e2e_reset now also mark su-dummy-a and su-dummy-c
as user-installed on dnf, which records plain rpm installs as "unknown",
so 1? and 2? show both reasons on all 12 distros. The verified commands
are recorded in discovery/{apt,pacman,rpm}.md.
This commit is contained in:
bryanpedini committed 2026-10-10 13:32:42 +02:00
1 parent 1354db6c39
commit 71d7f87aa3
21 files changed
+589 -48

No files matched your search

+156 -10
View File
@@ -50,13 +50,43 @@ setup() { common_setup; }
@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')"
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 (installed): apt'
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)" {
@@ -141,7 +171,7 @@ setup() { common_setup; }
run skup info su-dummy-a
assert_success
assert_line 'su-dummy-a: Skeptical-updater dummy package A'
assert_line ' required by (installed): nothing'
assert_line 'required by: nothing'
run skup info su-dummy-new
assert_success
assert_line 'su-dummy-new: Skeptical-updater dummy package NEW'
@@ -152,9 +182,32 @@ setup() { common_setup; }
run skup info glibc
assert_success
assert_line 'glibc: GNU C Library'
assert_line ' required by (installed): bash, gcc-libs, ncurses, readline'
assert_line 'required by: bash, gcc-libs, ncurses, readline'
run skup info su-dummy-b
assert_line ' required by (installed): su-dummy-a'
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" {
@@ -249,7 +302,7 @@ setup() { common_setup; }
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'
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)'
}
@@ -257,11 +310,55 @@ setup() { common_setup; }
@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'
assert_line 'required by: su-dummy-a, su-dummy-d'
run skup info su-dummy-c
assert_line ' required by (installed): su-dummy-d'
assert_line 'required by: su-dummy-d'
run skup info bash
assert_line ' required by (installed): filesystem, glibc-common, redhat-rpm-config, rpm, su-dummy-d'
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" {
@@ -393,9 +490,58 @@ setup() { common_setup; }
@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'
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'
}