On Wed, Jan 08, 2025 at 03:21:42PM +0100, Helmut Grohne wrote:
Since I was failing to get this right earlier, I figured I could as well write some tests. As a result, I'm attaching a shell script. It
constructs a few demo packages and attempts a hopefully exhaustive combination of using them and dpkg-divert by writing its experiments to
the working directory. Running it should be fairly boring. If you happen
to review it, I appreciate a response.
Despite multiple requests to actually attach my tests, nobody seems to
have read them and spot the missing test cases. And of course they were failing.
The case of upgrading from a single, aliased diversion to a duplicated diversion was not covered. In this case, we want the canonicalized
diversion target to be used by the canonicalized diversion and
therefore, the aliased diversion must change its diversion target.
Failure to do so results in file loss and/or left-over files.
I'm attaching an updated version that tests the transition from aliased diversion to duplicated diversion both in the case of the divertee being
absent and present. It's a lot of branches now, but I don't quite see
how we could do it with less.
Again, I appreciate someone actually reading the test cases and telling
me where I am wrong such that I don't have to redo too many redone
patches. At least the one I submitted for bfh-container fails an
additional test case.
Helmut
#!/bin/sh# shellcheck disable=SC2317 # Our way of running test cases confuses shellcheck.set -e# Create three Debian packages:# * m18base.deb emulates base-files and installs /lib and /usr/lib.# * m18dummy_1.deb installs /lib/m18dummy.# *
m18dummy_2.deb installs /usr/lib/m18dummy.rm -Rf root m18base m18dummyrm -f m18base.deb m18dummy_1.deb m18dummy_2.debmkdir -p m18base/DEBIAN m18base/usr/libln -s usr/lib m18base/libcat >m18base/DEBIAN/control <<EOFPackage: m18baseArchitecture:
allEssential: yesVersion: 1Maintainer: Dummy <
dummy@example.com>Description: emulate base-filesEOFdpkg-deb --root-owner-group -b m18base m18base.debmkdir -p m18dummy/DEBIAN m18dummy/libcat >m18dummy/DEBIAN/control <<EOFPackage: m18dummy
Architecture: allVersion: 1Maintainer: Dummy <
dummy@exmaple.com>Description: test packageEOFecho "m18dummy 1" >m18dummy/lib/m18dummydpkg-deb --root-owner-group -b m18dummy m18dummy_1.debmkdir m18dummy/usrmv m18dummy/lib m18dummy/usr/libecho "
m18dummy 2" >m18dummy/usr/lib/m18dummysed -i -e '/^Version:/s/1/2/' m18dummy/DEBIAN/controldpkg-deb --root-owner-group -b m18dummy m18dummy_2.deb# We are testing dpkg-divert in a non-root chroot located here.# This works as long as all maintainer
scripts honour DPKG_ROOT.INST_ROOT=rootsudo_dpkg() { PATH="/sbin:$PATH" dpkg --root "$INST_ROOT" --log "$INST_ROOT/var/log/dpkg.log" --force-not-root --force-script-chrootless "$@"}install_aliased() { sudo_dpkg -i m18dummy_1.deb}install_
canonical() { sudo_dpkg -i m18dummy_2.deb}remove_package() { sudo_dpkg -r m18dummy}setup_root() { rm -Rf "$INST_ROOT" mkdir -p "$INST_ROOT/var/log" sudo_dpkg -i m18base.deb}# Proposed diversion duplication mechanism## Q1: Should
duplicated diversions have aliased diversion targets?# A1: No! While dpkg is smart enough to not clobber undiverted files when# moving them from / to /usr, this safety does not apply when the file is# diverted. Instead such files reliably get lost.
Diversion targets must never# alias.## Q2: Can dpkg -S be used to determine whether --rename should be used?# A2: No! Before removing a diversion, dpkg -S will succeed just because# the file has been diverted regardless of whether some package
installed it.## Q3: Does it become any simpler if the diverted file is assumed canonical# before diversion removal?# A3: Yes! Pass --no-rename for aliased and --rename for canonical and see# far below. This requires that the diverting package
depends (with a version)# on the canonicalized divertee. It is not sufficient to declare Breaks with# the aliased version as removing it results in an inconsistency.## Q4: Why can I use --rename for diversion removal?# A4: Because only one of the
diversion targets exists (as a result of A1) and# the other rename will not have any effect.add_old_diversion() { # A simple diversion of the aliased path only as was done pre-trixie. dpkg-divert --root "$INST_ROOT" --add --rename /lib/m18dummy
echo "diverted old" > "$INST_ROOT/usr/lib/m18dummy"}add_diversions() { if test "$(dpkg-divert --root "$INST_ROOT" --truename /usr/lib/m18dummy)" != /usr/lib/m18dummy; then # Canonically diverted already. Not renaming. : elif test "$(dpkg-divert -
-root "$INST_ROOT" --truename /lib/m18dummy)" = /lib/m18dummy.distrib; then # Aliased diversion with earlier target. Rename target. dpkg-divert --root "$INST_ROOT" --remove --no-rename /lib/m18dummy if test -e "$INST_ROOT/lib/m18dummy.distrib" -o -
h "$INST_ROOT/lib/m18dummy.distrib"; then mv "$INST_ROOT/lib/m18dummy.distrib" "$INST_ROOT/lib/m18dummy.distrib.usr-is-merged" fi elif test "$(dpkg-divert --root "$INST_ROOT" --truename /lib/m18dummy)" != /lib/m18dummy; then # Aliased diversion.
Not renaming. : elif dpkg --root "$INST_ROOT" -S /usr/lib/m18dummy >/dev/null 2>&1; then # Not diverted. Installed as canonical. Rename. mv "$INST_ROOT/usr/lib/m18dummy" "$INST_ROOT/usr/lib/m18dummy.distrib" elif dpkg --root "$INST_ROOT" -S /lib/
m18dummy >/dev/null 2>&1; then # Not diverted. Installed as aliased. Rename. mv "$INST_ROOT/lib/m18dummy" "$INST_ROOT/lib/m18dummy.distrib.usr-is-merged" elif test -e "$INST_ROOT/usr/lib/m18dummy" -o -h "$INST_ROOT/usr/lib/m18dummy"; then # Not
diverted nor installed, but present. echo "attempting to dpkg-divert unowned file" exit 1 fi dpkg-divert --root "$INST_ROOT" --add --no-rename --divert /lib/m18dummy.distrib.usr-is-merged /lib/m18dummy dpkg-divert --root "$INST_ROOT" --add --no-
rename /usr/lib/m18dummy echo diverted > "$INST_ROOT/usr/lib/m18dummy"}remove_diversions() { if test "$(dpkg-divert --root "$INST_ROOT" --truename /usr/lib/m18dummy)" != /usr/lib/m18dummy || test "$(dpkg-divert --root "$INST_ROOT" --truename /lib/
m18dummy)" != /lib/m18dummy; then rm "$INST_ROOT/usr/lib/m18dummy" fi # Only one of the diversion targets should exist. Hence we may rename both. dpkg-divert --root "$INST_ROOT" --remove --rename /usr/lib/m18dummy dpkg-divert --root "$INST_ROOT" --
remove --rename /lib/m18dummy}test_fail() { echo "$*" 1>&2 ls -la "$INST_ROOT/lib/" dpkg-divert --root "$INST_ROOT" --list dpkg --root "$INST_ROOT" --verify exit 1}# Assert that the file "$INST_ROOT$1" contains "$2".assert_file_contains() {
if ! test -e "$INST_ROOT$1"; then test_fail "expected $1 to contain $2, but it does not exist" fi if ! cmp - "$INST_ROOT$1" <<EOF$2EOF then test_fail "FAIL.expected $1 to contain $2, but it conatsin $(cat "$1")" fi}# Assert that "$INST_ROOT$
1" does not exist.assert_file_absent() { if test -e "$INST_ROOT$1"; then test_fail "FAIL. expected $1 to be absent, but it exists" fi}# Assert that all is well:# * dpkg's database matches reality.# * Files contain what we expect them to
contain.# * No left-over files from earlier diversions.validate_common() { bad="$(dpkg --root "$INST_ROOT" --verify)" if test -n "$bad"; then test_fail "dpkg --verify has non-empty output $bad" fi test -h "$INST_ROOT/lib" || test_fail "/lib
vanished" bad="$(find "$INST_ROOT" ! \( \ \( -path "$INST_ROOT/var" -prune \) -o \ -path "$INST_ROOT" -o \ -path "$INST_ROOT/lib" -o \ -path "$INST_ROOT/usr" -o \ -path "$INST_ROOT/usr/lib" -o \ -path "$INST_ROOT/usr/lib/m18dummy" -o \ -
path "$INST_ROOT/usr/lib/m18dummy.distrib" -o \ -path "$INST_ROOT/usr/lib/m18dummy.distrib.usr-is-merged" \) )" if test -n "$bad"; then test_fail "unexpected files $bad" fi}validate_absent() { validate_common assert_file_absent /usr/lib/
m18dummy assert_file_absent /usr/lib/m18dummy.distrib assert_file_absent /usr/lib/m18dummy.distrib.usr-is-merged}validate_absent_diverted() { validate_common assert_file_contains /usr/lib/m18dummy "diverted" assert_file_absent /usr/lib/m18dummy.
distrib assert_file_absent /usr/lib/m18dummy.distrib.usr-is-merged}validate_absent_diverted_old() { validate_common assert_file_contains /usr/lib/m18dummy "diverted old" assert_file_absent /usr/lib/m18dummy.distrib assert_file_absent /usr/lib/
m18dummy.distrib.usr-is-merged}validate_aliased() { validate_common assert_file_contains /usr/lib/m18dummy "m18dummy 1" assert_file_absent /usr/lib/m18dummy.distrib assert_file_absent /usr/lib/m18dummy.distrib.usr-is-merged}validate_aliased_
diverted() { validate_common assert_file_contains /usr/lib/m18dummy "diverted" assert_file_absent /usr/lib/m18dummy.distrib assert_file_contains /usr/lib/m18dummy.distrib.usr-is-merged "m18dummy 1"}validate_aliased_diverted_old() { validate_common
assert_file_contains /usr/lib/m18dummy "diverted old" assert_file_contains /usr/lib/m18dummy.distrib "m18dummy 1" assert_file_absent /usr/lib/m18dummy.distrib.usr-is-merged}validate_canonical() { validate_common assert_file_contains /usr/lib/
m18dummy "m18dummy 2" assert_file_absent /usr/lib/m18dummy.distrib assert_file_absent /usr/lib/m18dummy.distrib.usr-is-merged}validate_canonical_diverted() { validate_common assert_file_contains /usr/lib/m18dummy "diverted" assert_file_contains /
usr/lib/m18dummy.distrib "m18dummy 2" assert_file_absent /usr/lib/m18dummy.distrib.usr-is-merged}testcase_absent() { validate_absent add_diversions validate_absent_diverted remove_diversions validate_absent}testcase_absent_duplicate() {
validate_absent add_old_diversion validate_absent_diverted_old add_diversions validate_absent_diverted}testcase_install_aliased() { validate_absent add_diversions validate_absent_diverted install_aliased validate_aliased_diverted remove_
diversions validate_aliased}testcase_installed_aliased() { validate_absent install_aliased validate_aliased add_diversions validate_aliased_diverted remove_diversions validate_aliased}testcase_installed_aliased_duplicate() { validate_
absent install_aliased validate_aliased add_old_diversion validate_aliased_diverted_old add_diversions validate_aliased_diverted}testcase_install_canonical() { validate_absent add_diversions validate_absent_diverted install_canonical
validate_canonical_diverted remove_diversions validate_canonical}testcase_installed_canonical() { validate_absent install_canonical validate_canonical add_diversions validate_canonical_diverted remove_diversions validate_canonical}testcase_
upgrade_aliased() { validate_absent install_aliased validate_aliased add_diversions validate_aliased_diverted install_aliased validate_aliased_diverted remove_diversions validate_aliased}testcase_upgrade_canonical() { validate_absent
install_canonical validate_canonical add_diversions validate_canonical_diverted install_canonical validate_canonical_diverted remove_diversions validate_canonical}testcase_upgrade_move() { validate_absent install_aliased validate_aliased
add_diversions validate_aliased_diverted install_canonical validate_canonical_diverted remove_diversions validate_canonical}testcase_remove_aliased() { validate_absent install_aliased validate_aliased add_diversions validate_aliased_diverted
remove_package validate_absent_diverted remove_diversions validate_absent}testcase_remove_canonical() { validate_absent install_canonical validate_canonical add_diversions validate_canonical_diverted remove_package validate_absent_diverted
remove_diversions validate_absent}testcase_absent_diverted_idempotent() { validate_absent add_diversions validate_absent_diverted add_diversions validate_absent_diverted remove_diversions validate_absent remove_diversions validate_absent}
testcase_aliased_diverted_idempotent() { validate_absent install_aliased validate_aliased add_diversions validate_aliased_diverted add_diversions validate_aliased_diverted remove_diversions validate_aliased remove_diversions validate_aliased}
testcase_canonical_diverted_idempotent() { validate_absent install_canonical validate_canonical add_diversions validate_canonical_diverted add_diversions validate_canonical_diverted remove_diversions validate_canonical remove_diversions
validate_canonical}for testcase in testcase_absent testcase_absent_duplicate testcase_install_aliased testcase_installed_aliased testcase_installed_aliased_duplicate testcase_install_canonical testcase_installed_canonical testcase_upgrade_aliased
testcase_upgrade_canonical testcase_upgrade_move testcase_remove_aliased testcase_remove_canonical testcase_absent_diverted_idempotent testcase_aliased_diverted_idempotent testcase_canonical_diverted_idempotent; do echo "$testcase" setup_root "$
testcase" echo OKdone# Simplified implementation when assuming that the diverted file becomes# canonical quickly after creating the diversion.add_diversions() { dpkg-divert --root "$INST_ROOT" --add --no-rename --divert /lib/m18dummy.distrib.usr-
is-merged /lib/m18dummy dpkg-divert --root "$INST_ROOT" --add --rename /usr/lib/m18dummy echo diverted > "$INST_ROOT/usr/lib/m18dummy"}remove_diversions() { if test "$(dpkg-divert --root "$INST_ROOT" --truename /usr/lib/m18dummy)" != /usr/lib/
m18dummy || test "$(dpkg-divert --root "$INST_ROOT" --truename /lib/m18dummy)" != /lib/m18dummy; then rm "$INST_ROOT/usr/lib/m18dummy" fi dpkg-divert --root "$INST_ROOT" --remove --rename /usr/lib/m18dummy dpkg-divert --root "$INST_ROOT" --remove -
-no-rename /lib/m18dummy}testcase_upgrade_move_relaxed() { validate_absent install_aliased validate_aliased add_diversions # temporarily does not validate, dependency of diverter on divertee required. install_canonical validate_canonical_
diverted remove_diversions validate_canonical}for testcase in testcase_absent testcase_install_canonical testcase_installed_canonical testcase_upgrade_canonical testcase_upgrade_move_relaxed testcase_remove_canonical testcase_absent_diverted_
idempotent testcase_canonical_diverted_idempotent; do echo "$testcase" setup_root "$testcase" echo OKdonefor testcase in testcase_absent_duplicate testcase_installed_aliased testcase_installed_aliased testcase_installed_aliased_duplicate testcase_
upgrade_aliased testcase_upgrade_move testcase_remove_aliased testcase_aliased_diverted_idempotent; do echo "expecting failure from $testcase" setup_root if ( "$testcase"; ); then echo "unexpected success from $testcase" exit 1 fi echo OKdone
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)