Files
skeptical-updater/tests/unit/interactive.bats
T
bryanpedini 71d7f87aa3 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.
2026-10-10 13:32:42 +02:00

364 lines
14 KiB
Bash

#!/usr/bin/env bats
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2026 Bryan Joshua Pedini
# The interactive grammar (stdin is the command channel; no TTY): apt backend, fixture rows
# [1] su-dummy-a [2] su-dummy-b [3] su-dummy-c (su-dummy-a depends on su-dummy-b).
load test_helper
setup() { common_setup; }
UPGRADE='apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o DPkg::Lock::Timeout=60 --with-new-pkgs upgrade'
@test "the session shows the list and the prompt help; q quits with exit 0, nothing upgraded" {
run skup --no-refresh <<<'q'
assert_success
assert_line --index 0 'Backend: apt (Debian GNU/Linux 12 (bookworm))'
assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:'
assert_line ' [1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line '<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever'
assert_line '> q'
assert_line 'Quit: nothing was upgraded.'
log_lacks '^apt-get -y'
log_lacks '^apt-mark (un)?hold'
}
@test "an empty line proceeds: the native upgrade runs, exit 0" {
run skup <<<''
assert_success
assert_line 'Excluded from this run: none'
assert_line 'Upgrade finished.'
log_has 'apt-get update -qq -o APT::Update::Error-Mode=any'
log_has "$UPGRADE"
}
@test "a whitespace-only line also proceeds" {
run skup --no-refresh <<<' '
assert_success
log_has "$UPGRADE"
}
@test "the exit status of the native upgrade is passed through" {
FAKE_APT_UPGRADE_RC=100 run skup --no-refresh <<<''
assert_failure 100
assert_output --partial 'the upgrade failed: apt exited with status 100'
}
@test "EOF aborts with exit 2 without upgrading" {
run skup --no-refresh <<<'1?'
assert_failure 2
assert_output --partial 'end of input: aborted, nothing was upgraded'
log_lacks '^apt-get -y'
}
@test "EOF straight away (empty stdin) also aborts with exit 2" {
run skup --no-refresh </dev/null
assert_failure 2
log_lacks '^apt-get -y'
}
@test "a last line without a newline is still executed before EOF" {
run skup --no-refresh < <(printf '2-')
assert_failure 2
assert_line 'excluded from this run: su-dummy-b'
}
@test "N? prints the description, the install reason and the installed packages that depend on it" {
run skup --no-refresh <<<$'1?\n2?\nq'
assert_success
assert_output --partial "$(printf '%s\n' '> 1?' \
'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests' \
'reason: explicitly installed' \
'required by: nothing' \
'> 2?' \
'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests' \
'reason: installed as a dependency' \
'required by: su-dummy-a')"
refute_output --partial 'required by (installed)'
}
@test "N? on a new dependency (old version -) says 'not installed' without asking the package manager" {
FAKE_APT_SIM=real run skup --no-refresh <<<$'9?\nq'
assert_success
assert_line --regexp '^ \[9\] su-dummy-newdep +- +-> +1\.0 +su-fixture:su-repo$'
assert_output --partial "$(printf '%s\n' '> 9?' \
'su-dummy-newdep: (no description available: unknown package?)' \
'reason: not installed' \
'required by: nothing')"
log_lacks '^apt-mark show'
log_lacks '^dpkg-query -W -f \$\{db:Status-Abbrev\} '
}
@test "N?? prints the native full info" {
run skup --no-refresh <<<$'1??\nq'
assert_success
assert_line 'Package: su-dummy-a'
assert_line 'Depends: su-dummy-b (>= 2.0)'
log_has 'apt-cache show --no-all-versions su-dummy-a'
}
@test "N- toggles the exclusion and the '-' marker" {
run skup --no-refresh <<<$'2-\nl\n2-\nl\n2-\n'
assert_success
assert_line 'excluded from this run: su-dummy-b'
assert_line 're-included: su-dummy-b'
assert_line '-[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'Excluded from this run: su-dummy-b'
log_has 'apt-mark hold su-dummy-b'
log_has 'apt-mark unhold su-dummy-b'
}
@test "Nx asks y/N on the same stdin; y purges, the row leaves, numbers stay stable" {
run skup --no-refresh <<<$'3x\ny\nl\n3?\nq'
assert_success
assert_line 'Purge su-dummy-c now? [y/N] y'
assert_line 'purged: su-dummy-c'
log_has 'apt-get -y purge su-dummy-c'
log_lacks 'autoremove --purge'
assert_line '2 upgradable, 0 trusted (hidden, use --all to show), 2 to review:'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
[[ $(grep -c '^ \[3\] su-dummy-c' <<<"$output") -eq 1 ]]
assert_output --partial 'no package [3] in the list (l to redraw)'
}
@test "Nx answered with N, n, empty or anything else does not purge" {
for answer in N n '' maybe; do
: >"$FAKE_LOG"
run skup --no-refresh <<<"3x"$'\n'"$answer"$'\nq'
assert_success
assert_line 'not purged: su-dummy-c'
log_lacks '^apt-get -y'
done
}
@test "Nx: a failing purge keeps the row" {
FAKE_APT_PURGE_RC=100 run skup --no-refresh <<<$'3x\nyes\nl\nq'
assert_success
assert_output --partial 'purge of su-dummy-c failed (apt exited 100); the row stays in the list'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
}
@test "Nx: EOF at the confirmation aborts with exit 2 and purges nothing" {
run skup --no-refresh <<<'3x'
assert_failure 2
log_lacks '^apt-get -y'
}
@test "Nx on a package other installed packages depend on is refused, like pacman -Rns; the row stays" {
run skup --no-refresh <<<$'2x\nl\nq'
assert_success
assert_line 'not purged: su-dummy-b is required by: su-dummy-a - purge the dependents first'
refute_output --partial '[y/N]'
assert_line '3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:'
[[ $(grep -c '^ \[2\] su-dummy-b' <<<"$output") -eq 2 ]]
log_lacks '^apt-get -y'
}
@test "Nx removes the target with the dependencies only it needed, listed in the prompt" {
FAKE_APT_AUTO_ORPHANS=su-dummy-a:su-dummy-b run skup --no-refresh <<<$'1x\ny\nl\nq'
assert_success
assert_line 'Purge su-dummy-a now? This also removes: su-dummy-b [y/N] y'
assert_line 'purged: su-dummy-a su-dummy-b'
assert_line '1 upgradable, 0 trusted (hidden, use --all to show), 1 to review:'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
}
@test "Nx: rows the purge took with it unannounced leave the list too (re-listed afterwards)" {
FAKE_APT_PURGE_ALSO=su-dummy-a run skup --no-refresh <<<$'3x\ny\nl\nq'
assert_success
assert_line ' (su-dummy-a is no longer upgradable, removed from the list)'
assert_line '1 upgradable, 0 trusted (hidden, use --all to show), 1 to review:'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
}
@test "Nx on a new dependency (old version -, not installed) is refused" {
FAKE_APT_SIM=real run skup --no-refresh <<<$'9x\nq'
assert_success
assert_line 'su-dummy-newdep is not installed (the upgrade would install it as a new dependency): nothing to purge; use 9- to exclude it from this run.'
refute_output --partial '[y/N]'
log_lacks 'purge'
}
@test "Nx refused by the package manager asks nothing" {
FAKE_APT_SIM_PURGE_RC=100 run skup --no-refresh <<<$'3x\nq'
assert_success
assert_output --partial 'E: Unable to correct problems, you have held broken packages.'
assert_output --partial 'cannot purge su-dummy-c: apt refuses (see above); nothing was removed'
refute_output --partial '[y/N]'
log_lacks '^apt-get -y'
}
@test "purging the last packages ends the session cleanly (exit 0)" {
export FAKE_APT_AUTO_ORPHANS=su-dummy-a:su-dummy-b
run skup --no-refresh <<<$'3x\ny\n1x\ny'
assert_success
assert_line 'purged: su-dummy-c'
assert_line 'purged: su-dummy-a su-dummy-b'
assert_line 'Nothing left to upgrade.'
log_lacks "^apt-get -y -o"
}
@test "purging every listed row keeps the session while trusted (hidden) rows remain" {
write_state "$HEADER" "apt${T}su-dummy-c${T}user${T}2026-01-01T00:00:00Z"
FAKE_APT_AUTO_ORPHANS=su-dummy-a:su-dummy-b run skup --no-refresh <<<$'1x\ny\nl\nq'
assert_success
assert_line '1 upgradable, 1 trusted (hidden, use --all to show), 0 to review:'
assert_line 'Quit: nothing was upgraded.'
}
@test "N+ trusts with the closure: rows leave, numbers stay, the hidden count grows" {
run skup --no-refresh <<<$'1+\nl\nq'
assert_success
assert_line 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
assert_line '3 upgradable, 2 trusted (hidden, use --all to show), 1 to review:'
assert_line ' [3] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
# [1] and [2] only appear in the first listing
[[ $(grep -c '^ \[1\] su-dummy-a' <<<"$output") -eq 1 ]]
[[ $(grep -c '^ \[2\] su-dummy-b' <<<"$output") -eq 1 ]]
run cut -f1-3 "$STATE_FILE"
assert_line "apt${T}su-dummy-a${T}user"
assert_line "apt${T}su-dummy-b${T}dep:su-dummy-a"
}
@test "trusting a package re-includes the excluded rows that this trust hides" {
run skup --no-refresh <<<$'2-\n1+\n\n'
assert_success
assert_line 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
assert_line ' (su-dummy-b is trusted now: no longer excluded from this run)'
assert_line 'Excluded from this run: none'
log_lacks '^apt-mark hold'
}
@test "with --all a trusted row stays visible, so its exclusion stays too" {
run skup --no-refresh --all <<<$'2-\n1+\n\n'
assert_success
refute_output --partial 'no longer excluded'
assert_line 'Excluded from this run: su-dummy-b'
}
@test "trusted packages are hidden with a count; numbering covers the listed rows only" {
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
run skup --no-refresh <<<$'1?\nq'
assert_success
assert_line '3 upgradable, 1 trusted (hidden, use --all to show), 2 to review:'
assert_line ' [1] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-c 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'su-dummy-b: dummy package su-dummy-b for skeptical-updater tests'
}
@test "--all lists trusted packages too, marked with '='" {
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
run skup --no-refresh --all <<<$'2+\nl\nq'
assert_success
assert_line '3 upgradable, 1 trusted (marked with =), 2 to review:'
assert_line '=[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'trusted su-dummy-b'
assert_line '3 upgradable, 2 trusted (marked with =), 1 to review:'
assert_line '=[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
}
@test "trusted packages are upgraded silently (never excluded)" {
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z"
run skup --no-refresh <<<''
assert_success
assert_line 'Excluded from this run: none'
log_lacks '^apt-mark hold'
log_has "$UPGRADE"
}
@test "everything trusted: 0 to review, the prompt is still offered" {
write_state "$HEADER" "apt${T}su-dummy-a${T}user${T}2026-01-01T00:00:00Z" \
"apt${T}su-dummy-b${T}user${T}2026-01-01T00:00:00Z" "apt${T}su-dummy-c${T}user${T}2026-01-01T00:00:00Z"
run skup --no-refresh <<<''
assert_success
assert_line '3 upgradable, 3 trusted (hidden, use --all to show), 0 to review:'
log_has "$UPGRADE"
}
@test "*+ trusts every listed package" {
run skup --no-refresh <<<$'*+\nl\nq'
assert_success
assert_line 'trusted su-dummy-a (+1 dependency: su-dummy-b)'
assert_line 'trusted su-dummy-b'
assert_line 'trusted su-dummy-c'
assert_line '3 upgradable, 3 trusted (hidden, use --all to show), 0 to review:'
run cut -f2,3 "$STATE_FILE"
assert_line "su-dummy-b${T}user"
}
@test "*- excludes every listed package; *- again re-includes them" {
run skup --no-refresh <<<$'*-\nl\n*-\nl\n*-\n'
assert_success
assert_line 'excluded all 3 listed packages from this run'
assert_line 're-included all 3 listed packages'
assert_line '-[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo'
assert_line 'Excluded from this run: su-dummy-a su-dummy-b su-dummy-c'
log_has 'apt-mark hold su-dummy-a su-dummy-b su-dummy-c'
}
@test "several tokens on one line run in order" {
run skup --no-refresh <<<$'1? 2- 3?\nl\nq'
assert_success
assert_line 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests'
assert_line 'excluded from this run: su-dummy-b'
assert_line 'su-dummy-c: dummy package su-dummy-c for skeptical-updater tests'
assert_line '-[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
}
@test "an unknown token prints an error, runs nothing from that line and re-prompts" {
run skup --no-refresh <<<$'2- bogus\nl\n1+ 3y\nq'
assert_success
assert_output --partial "unknown command 'bogus' (h for help)"
assert_output --partial "unknown command '3y' (h for help)"
assert_line ' [2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo'
refute_output --partial 'excluded from this run'
[[ ! -e $STATE_FILE ]]
}
@test "numbers that are not in the list are rejected" {
run skup --no-refresh <<<$'9?\n0-\n4x\nq'
assert_success
assert_output --partial 'no package [9] in the list (l to redraw)'
assert_output --partial 'no package [0] in the list (l to redraw)'
assert_output --partial 'no package [4] in the list (l to redraw)'
log_lacks '^apt-get -y'
}
@test "h and ? print the help" {
run skup --no-refresh <<<$'h\n?\nq'
assert_success
[[ $(grep -c 'N?? full native info' <<<"$output") -eq 2 ]]
assert_line 'Several tokens can be given on one line, e.g. "1? 3- 4+".'
}
@test "l redraws the list" {
run skup --no-refresh <<<$'l\nq'
assert_success
[[ $(grep -c '^3 upgradable' <<<"$output") -eq 2 ]]
}
@test "q in the middle of a line stops there (later tokens never run)" {
run skup --no-refresh <<<$'1? q 3x'
assert_success
assert_line 'su-dummy-a: dummy package su-dummy-a for skeptical-updater tests'
refute_output --partial 'Purge'
}
@test "nothing to upgrade: no prompt, exit 0" {
FAKE_APT_SIM=none run skup --no-refresh </dev/null
assert_success
assert_line 'Nothing to upgrade.'
refute_output --partial '<enter> upgrade'
}
@test "held (kept back) rows can be reviewed like the others" {
echo su-dummy-c >"$FAKE_DIR/holds"
FAKE_APT_SIM=held run skup --no-refresh <<<$'3?\nq'
assert_success
assert_line ' [3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]'
assert_line 'su-dummy-c: dummy package su-dummy-c for skeptical-updater tests'
}