218 lines
9.3 KiB
Bash
Executable File
218 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2026 Bryan Joshua Pedini
|
|
|
|
# rpm.sh - deterministic E2E fixture for RPM distros (dnf: Alma/Rocky/..., zypper: openSUSE)
|
|
# Copy of discovery/rpm/fixture.sh; the only changes are the SU_FIXTURE_ISOLATE,
|
|
# SU_FIXTURE_ORPHAN and SU_FIXTURE_B_AUTO knobs.
|
|
#
|
|
# Builds three dummy packages with rpmbuild, installs version 1.0 of each, publishes
|
|
# version 2.0 in a local file:// repo and registers that repo, so that the package
|
|
# manager reports exactly these upgrades:
|
|
#
|
|
# su-dummy-a 1.0-1 -> 2.0-1 (noarch, Requires: su-dummy-b)
|
|
# su-dummy-b 1.0-1 -> 2.0-1 (noarch)
|
|
# su-dummy-c 1.0-1 -> 2.0-1 (ARCH-SPECIFIC, so the ".x86_64" suffix path is exercised)
|
|
#
|
|
# Optional (SU_FIXTURE_EXTRAS=1):
|
|
# - installs su-dummy-d 1.0 (NOT upgradable): Requires su-dummy-a, /bin/sh (file dep),
|
|
# "su-dummy-b >= 1.0" (versioned), "(su-dummy-c if su-dummy-b)" (rich dep) and rpmlib(...)
|
|
# -> for reverse-dep / dependency-closure tests;
|
|
# - publishes su-dummy-r 1.0 (Requires su-dummy-s) and su-dummy-s 1.0 in the repo, NOT installed
|
|
# -> for install/remove/--clean-deps tests.
|
|
#
|
|
# Optional (SU_FIXTURE_ISOLATE=1): after the build tools are installed, move the distro's
|
|
# own repository (and, on zypper, service) definitions to /srv/su-disabled-repos, so that
|
|
# only the su-dummy repo is left: the upgradable list is exactly su-dummy-{a,b,c} and later
|
|
# refreshes/upgrades do not depend on the state of the real mirrors.
|
|
#
|
|
# Optional (SU_FIXTURE_ORPHAN=1): su-dummy-orphan 1.0 is installed as a dependency (of
|
|
# su-dummy-orphan-parent, installed through the package manager and then removed with rpm -e),
|
|
# so it is a pre-existing unneeded package (dnf repoquery --unneeded / zypper packages --unneeded).
|
|
#
|
|
# Optional (SU_FIXTURE_B_AUTO=1): su-dummy-b counts as installed as a dependency (dnf: `dnf mark
|
|
# remove`, zypper: /var/lib/zypp/AutoInstalled), like the pacman fixture's --asdeps.
|
|
#
|
|
# Re-runnable: re-running resets everything to the "1.0 installed, 2.0 available" state.
|
|
#
|
|
# Usage (inside the container, as root): bash fixture.sh
|
|
# docker run --rm -i -v <project>/discovery/rpm:/d:ro <image> bash /d/fixture.sh
|
|
set -euo pipefail
|
|
export LC_ALL=C
|
|
|
|
T0=$SECONDS
|
|
log() { printf '[fixture] %s\n' "$*" >&2; }
|
|
die() { printf '[fixture] ERROR: %s\n' "$*" >&2; exit 1; }
|
|
[ "$(id -u)" -eq 0 ] || die "must run as root"
|
|
|
|
# ---- 1. backend detection ---------------------------------------------------
|
|
BACKEND=
|
|
if command -v zypper >/dev/null 2>&1; then BACKEND=zypper
|
|
elif command -v dnf >/dev/null 2>&1; then BACKEND=dnf
|
|
elif command -v yum >/dev/null 2>&1; then BACKEND=yum
|
|
else die "neither zypper nor dnf/yum found"; fi
|
|
log "backend=$BACKEND"
|
|
|
|
TOP=/var/tmp/su-fixture # rpmbuild topdir
|
|
REPO=/var/lib/su-dummy-repo # file:// repo (only 2.0 packages)
|
|
SPEC_DIR=$TOP/SPECS
|
|
|
|
# ---- 2. build tools ---------------------------------------------------------
|
|
t=$SECONDS
|
|
case $BACKEND in
|
|
zypper) zypper --non-interactive --quiet install --no-recommends rpm-build createrepo_c >/dev/null ;;
|
|
dnf|yum) "$BACKEND" -y -q install --setopt=install_weak_deps=False rpm-build createrepo_c >/dev/null ;;
|
|
esac
|
|
command -v rpmbuild >/dev/null || die "rpmbuild missing after install"
|
|
command -v createrepo_c >/dev/null || die "createrepo_c missing after install"
|
|
log "build tools installed in $((SECONDS - t))s"
|
|
|
|
# ---- 2b. optional isolation: only the su-dummy repo stays configured ----------
|
|
if [ "${SU_FIXTURE_ISOLATE:-0}" = 1 ]; then
|
|
mkdir -p /srv/su-disabled-repos
|
|
case $BACKEND in
|
|
zypper) defs=(/etc/zypp/repos.d/*.repo /etc/zypp/services.d/*.service) ;;
|
|
dnf|yum) defs=(/etc/yum.repos.d/*.repo) ;;
|
|
esac
|
|
for f in "${defs[@]}"; do
|
|
[ -e "$f" ] || continue
|
|
case ${f##*/} in su-dummy.repo) continue ;; esac
|
|
mv "$f" "/srv/su-disabled-repos/${f##*/}"
|
|
done
|
|
log "ISOLATE: real repository definitions moved to /srv/su-disabled-repos"
|
|
fi
|
|
|
|
# ---- 3. build RPMs ----------------------------------------------------------
|
|
rm -rf "$TOP" "$REPO"
|
|
mkdir -p "$TOP"/{BUILD,BUILDROOT,RPMS,SRPMS,SOURCES,SPECS} "$REPO"
|
|
|
|
# mkspec <name> <version> <arch|noarch> <extra spec header lines...>
|
|
mkspec() {
|
|
local name=$1 ver=$2 arch=$3; shift 3
|
|
local f=$SPEC_DIR/$name-$ver.spec
|
|
{
|
|
echo "Name: $name"
|
|
echo "Version: $ver"
|
|
echo "Release: 1"
|
|
echo "Summary: skeptical-updater dummy $name (v$ver)"
|
|
echo "License: MIT"
|
|
[ "$arch" = noarch ] && echo "BuildArch: noarch"
|
|
local l; for l in "$@"; do echo "$l"; done
|
|
echo "%global debug_package %{nil}"
|
|
echo "%description"
|
|
echo "Dummy package $name version $ver for skeptical-updater tests."
|
|
echo ""
|
|
echo "Second paragraph of $name $ver (blank line above on purpose)."
|
|
echo "%install"
|
|
echo "mkdir -p %{buildroot}/usr/share/su-dummy"
|
|
echo "echo '$name $ver' > %{buildroot}/usr/share/su-dummy/$name"
|
|
echo "%files"
|
|
echo "/usr/share/su-dummy/$name"
|
|
} > "$f"
|
|
printf '%s\n' "$f"
|
|
}
|
|
|
|
build() { rpmbuild -bb --quiet --define "_topdir $TOP" "$1" >/dev/null 2>"$TOP/rpmbuild.err" \
|
|
|| { cat "$TOP/rpmbuild.err" >&2; die "rpmbuild failed for $1"; }; }
|
|
|
|
t=$SECONDS
|
|
for v in 1.0 2.0; do
|
|
build "$(mkspec su-dummy-a $v noarch 'Requires: su-dummy-b')"
|
|
build "$(mkspec su-dummy-b $v noarch)"
|
|
build "$(mkspec su-dummy-c $v arch)"
|
|
done
|
|
if [ "${SU_FIXTURE_EXTRAS:-0}" = 1 ]; then
|
|
build "$(mkspec su-dummy-s 1.0 noarch)" # published in the repo, NOT installed
|
|
build "$(mkspec su-dummy-r 1.0 noarch 'Requires: su-dummy-s')" # (used to test remove/--clean-deps)
|
|
build "$(mkspec su-dummy-d 1.0 noarch \
|
|
'Requires: su-dummy-a' \
|
|
'Requires: /bin/sh' \
|
|
'Requires: su-dummy-b >= 1.0' \
|
|
'Requires: (su-dummy-c if su-dummy-b)' \
|
|
'Requires: rpmlib(CompressedFileNames)')"
|
|
fi
|
|
if [ "${SU_FIXTURE_ORPHAN:-0}" = 1 ]; then
|
|
build "$(mkspec su-dummy-orphan 1.0 noarch)"
|
|
build "$(mkspec su-dummy-orphan-parent 1.0 noarch 'Requires: su-dummy-orphan')"
|
|
fi
|
|
log "rpmbuild done in $((SECONDS - t))s"
|
|
|
|
# ---- 4. install 1.0 (rpm directly: distro-agnostic, resets to 1.0 on re-run) ----
|
|
shopt -s nullglob
|
|
v1=( "$TOP"/RPMS/*/su-dummy-[abc]-1.0-1.*.rpm )
|
|
[ "${#v1[@]}" -eq 3 ] || die "expected 3 built 1.0 rpms, got ${#v1[@]}"
|
|
rpm -Uvh --oldpackage --replacepkgs "${v1[@]}" >/dev/null
|
|
if [ "${SU_FIXTURE_EXTRAS:-0}" = 1 ]; then
|
|
rpm -Uvh --oldpackage --replacepkgs "$TOP"/RPMS/noarch/su-dummy-d-1.0-1.*.rpm >/dev/null
|
|
fi
|
|
|
|
# ---- 5. publish 2.0 as a local repo -------------------------------------------
|
|
v2=( "$TOP"/RPMS/*/su-dummy-[abc]-2.0-1.*.rpm )
|
|
[ "${#v2[@]}" -eq 3 ] || die "expected 3 built 2.0 rpms, got ${#v2[@]}"
|
|
cp "${v2[@]}" "$REPO"/
|
|
if [ "${SU_FIXTURE_EXTRAS:-0}" = 1 ]; then cp "$TOP"/RPMS/noarch/su-dummy-[rs]-1.0-1.*.rpm "$REPO"/; fi
|
|
if [ "${SU_FIXTURE_ORPHAN:-0}" = 1 ]; then cp "$TOP"/RPMS/noarch/su-dummy-orphan*-1.0-1.*.rpm "$REPO"/; fi
|
|
createrepo_c --quiet "$REPO" >/dev/null
|
|
|
|
# ---- 6. register repo ---------------------------------------------------------
|
|
case $BACKEND in
|
|
zypper)
|
|
zypper --non-interactive --quiet removerepo su-dummy >/dev/null 2>&1 || true
|
|
zypper --non-interactive --quiet addrepo --no-gpgcheck --no-check --refresh "file://$REPO" su-dummy >/dev/null
|
|
t=$SECONDS
|
|
zypper --non-interactive --quiet refresh su-dummy >/dev/null
|
|
;;
|
|
dnf|yum)
|
|
cat > /etc/yum.repos.d/su-dummy.repo <<REPOEOF
|
|
[su-dummy]
|
|
name=skeptical-updater dummy packages
|
|
baseurl=file://$REPO
|
|
enabled=1
|
|
gpgcheck=0
|
|
repo_gpgcheck=0
|
|
metadata_expire=0
|
|
REPOEOF
|
|
t=$SECONDS
|
|
"$BACKEND" -q makecache --repo su-dummy >/dev/null
|
|
;;
|
|
esac
|
|
log "repo refresh in $((SECONDS - t))s"
|
|
|
|
# ---- 6b. optional pre-existing orphan: installed as a dependency, its parent removed ----------
|
|
if [ "${SU_FIXTURE_ORPHAN:-0}" = 1 ]; then
|
|
case $BACKEND in
|
|
zypper) zypper --non-interactive --quiet install su-dummy-orphan-parent >/dev/null ;;
|
|
dnf|yum) "$BACKEND" -y -q install su-dummy-orphan-parent >/dev/null 2>&1 ;;
|
|
esac
|
|
rpm -e su-dummy-orphan-parent
|
|
case $BACKEND in
|
|
zypper) unneeded=$(zypper --non-interactive --quiet packages --unneeded 2>/dev/null || true) ;;
|
|
dnf|yum) unneeded=$("$BACKEND" -q repoquery --unneeded 2>/dev/null || true) ;;
|
|
esac
|
|
rpm -q su-dummy-orphan >/dev/null || die "su-dummy-orphan is not installed"
|
|
printf '%s\n' "$unneeded" | grep -q su-dummy-orphan || die "su-dummy-orphan is not an unneeded package"
|
|
log "pre-existing orphan (unneeded): su-dummy-orphan"
|
|
fi
|
|
|
|
# ---- 6c. optional: su-dummy-b installed as a dependency (of su-dummy-a) ----------------------
|
|
if [ "${SU_FIXTURE_B_AUTO:-0}" = 1 ]; then
|
|
case $BACKEND in
|
|
zypper) grep -qx su-dummy-b /var/lib/zypp/AutoInstalled 2>/dev/null || echo su-dummy-b >>/var/lib/zypp/AutoInstalled ;;
|
|
dnf|yum) "$BACKEND" -q mark remove su-dummy-b >/dev/null ;;
|
|
esac
|
|
log "su-dummy-b marked as installed as a dependency"
|
|
fi
|
|
|
|
# ---- 7. verify: the three must be listed as upgradable --------------------------
|
|
want="su-dummy-a su-dummy-b su-dummy-c"
|
|
case $BACKEND in
|
|
zypper) listing=$(zypper --non-interactive --quiet list-updates 2>&1 || true) ;;
|
|
dnf|yum) listing=$("$BACKEND" -q check-update 2>&1 || true) ;;
|
|
esac
|
|
bad=0
|
|
for p in $want; do
|
|
printf '%s\n' "$listing" | grep -Eq "(^|[| ])$p([. ]|$)" || { log "NOT listed as upgradable: $p"; bad=1; }
|
|
done
|
|
if [ "$bad" -ne 0 ]; then printf '%s\n' "$listing" >&2; die "fixture verification failed"; fi
|
|
log "OK: su-dummy-{a,b,c} 1.0 installed and 2.0 offered by repo 'su-dummy' (total ${SECONDS}s, since start $((SECONDS - T0))s)"
|