From 7579db95343984c4d93c18b2ab4a37290239f9fc Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 11 Jun 2026 07:43:45 +0530 Subject: [PATCH 1/4] Add staged-install (DESTDIR) downstream autopkgtest harness --- .github/workflows/build.yml | 30 +++++- Makefile.am | 100 ++++++++++++++++++ ci/autopkgtest/debian-tests/control | 12 +++ ci/autopkgtest/debian-tests/libppd-2-dev | 71 +++++++++++++ .../debian-tests/libppd-2-ppd-handling | 20 ++++ ci/autopkgtest/run.sh | 91 ++++++++++++++++ libppd.pc.in | 2 +- 7 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 ci/autopkgtest/debian-tests/control create mode 100755 ci/autopkgtest/debian-tests/libppd-2-dev create mode 100755 ci/autopkgtest/debian-tests/libppd-2-ppd-handling create mode 100755 ci/autopkgtest/run.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6c9ce03b..06ce9b2f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,7 +103,9 @@ jobs: zlib1g-dev \ ghostscript \ poppler-utils \ - mupdf-tools + mupdf-tools \ + file \ + proot - name: Build & test libppd (native) if: matrix.use-qemu == false @@ -111,7 +113,9 @@ jobs: set -ex cd "$REPO_DIR" ./autogen.sh - ./configure + # --enable-ppdc-utils: the downstream libppd-2-dev autopkgtest needs + # the staged `ppdc` to compile its test.drv into a PPD. + ./configure --enable-ppdc-utils make -j$(nproc) V=1 make check V=1 VERBOSE=1 || { echo "==== test-suite.log ====" @@ -123,6 +127,16 @@ jobs: exit 1 } + - name: Autopkgtest (DESTDIR staging, native) + if: matrix.use-qemu == false + run: | + set -ex + cd "$REPO_DIR" + # Full downstream suite: libppd-2-dev (proot-free) + + # libppd-2-ppd-handling (proot redirects /usr/bin/testppd and + # /usr/share/ppd into the staging tree). + make test-autopkgtest V=1 + # ----------------------------------------------------------------------- # EMULATED LEG (armhf via QEMU armv7, riscv64 via QEMU) # ----------------------------------------------------------------------- @@ -166,12 +180,14 @@ jobs: zlib1g-dev \ ghostscript \ poppler-utils \ - mupdf-tools + mupdf-tools \ + file run: | set -ex cd /workspace ./autogen.sh - ./configure + # --enable-ppdc-utils: needed by the libppd-2-dev autopkgtest. + ./configure --enable-ppdc-utils make -j$(nproc) V=1 make check V=1 VERBOSE=1 || { echo "==== test-suite.log ====" @@ -183,6 +199,12 @@ jobs: exit 1 } + # Downstream autopkgtest, proot-free leg only: nested proot under + # QEMU-user emulation is unreliable, so the absolute-path PPD- + # handling test runs on the native legs. The dev compile/link/run + # test runs here. + make test-autopkgtest-dev V=1 + # ----------------------------------------------------------------------- # ARTIFACT UPLOAD (all four legs, only on failure) # ----------------------------------------------------------------------- diff --git a/Makefile.am b/Makefile.am index 16a10f0de..cee5a3e54 100644 --- a/Makefile.am +++ b/Makefile.am @@ -367,4 +367,104 @@ ppdpo_CFLAGS = \ distclean-local: rm -rf *.cache *~ +clean-local: + rm -rf $(CIROOT) + SUBDIRS = + +# ============================================================ +# Staged-install downstream autopkgtests (DESTDIR staging) +# ============================================================ +# Install the just-built tree into an ephemeral root ($(CIROOT)) with +# `make install DESTDIR=`, top up the bits that `make install` does not ship +# but the downstream Debian autopkgtests require, fix up the staged .pc, then +# run the unmodified downstream scripts against that tree. +CIROOT = $(abs_top_builddir)/_ciroot + +EXTRA_DIST += \ + ci/autopkgtest/run.sh \ + ci/autopkgtest/debian-tests/control \ + ci/autopkgtest/debian-tests/libppd-2-dev \ + ci/autopkgtest/debian-tests/libppd-2-ppd-handling + +# Fix A: check_PROGRAMS are build-only under Automake and are never copied by +# `make install`. The downstream PPD-handling test executes the test driver +# as an *installed* binary (/usr/bin/testppd), so build it and stage it into +# $(CIROOT)$(bindir). Uses libtool --mode=install so the binary is relinked +# cleanly against $(libdir) (LD_LIBRARY_PATH then forces the staged library). +STAGED_TEST_BINARIES = testppd + +install-test-programs: $(STAGED_TEST_BINARIES) + $(MKDIR_P) "$(CIROOT)$(bindir)" + @for p in $(STAGED_TEST_BINARIES); do \ + echo " STAGE $$p -> $(CIROOT)$(bindir)/$$p"; \ + $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) "$$p" "$(CIROOT)$(bindir)/$$p" || exit 1; \ + done + +# Fix C (data seeding): the downstream PPD-handling test copies its PPDs from +# the installed $(datadir)/ppd/testppd directory, which upstream `make install` +# does not ship. Seed every test PPD the current testppd binary opens +# (test.ppd, test2.ppd, sbo_test.ppd) so the unmodified script finds them. +STAGED_TEST_PPDS = \ + ppd/test.ppd \ + ppd/test2.ppd \ + ppd/sbo_test.ppd + +install-test-data: + $(MKDIR_P) "$(CIROOT)$(datadir)/ppd/testppd" + @for f in $(STAGED_TEST_PPDS); do \ + echo " STAGE $$f -> $(CIROOT)$(datadir)/ppd/testppd/"; \ + $(INSTALL_DATA) "$(srcdir)/$$f" "$(CIROOT)$(datadir)/ppd/testppd/" || exit 1; \ + done + +# Build everything, DESTDIR-install it, top up the test-only programs/data, +# then rewrite the staged libppd.pc prefix so a downstream consumer resolves +# THIS build (not a system copy) while the system dependency .pc files +# (libcupsfilters, cups) keep resolving normally. +stage-ciroot: all + rm -rf "$(CIROOT)" + $(MAKE) $(AM_MAKEFLAGS) install DESTDIR="$(CIROOT)" + $(MAKE) $(AM_MAKEFLAGS) install-test-programs install-test-data + @pc="$(CIROOT)$(libdir)/pkgconfig/libppd.pc"; \ + if [ -f "$$pc" ]; then \ + sed -i.bak -E "s#^(prefix|exec_prefix|libdir|includedir)=(/.*)#\1=$(CIROOT)\2#" "$$pc"; \ + rm -f "$$pc.bak"; \ + echo "stage-ciroot: rewrote staged-tree paths in $$pc"; \ + fi + +# Common environment for the wrapper. PPDC_DATADIR / CUPS_DATADIR point ppdc +# at the staged *.defs so `ppdc test.drv` resolves / +# from the staging tree. +# NB: libppd configures with a split prefix (prefix=/ but exec_prefix=/usr and +# everything installs under /usr), so the staging root the wrapper points at is +# $(CIROOT)$(exec_prefix), not $(CIROOT)$(prefix). +CIROOT_ENV = \ + CIROOT="$(CIROOT)" \ + CIPREFIX="$(exec_prefix)" \ + TOP_BUILDDIR="$(abs_top_builddir)" \ + PPDC_DATADIR="$(CIROOT)$(datadir)/ppdc" \ + CUPS_DATADIR="$(CIROOT)$(datadir)" + +# Absolute-path binds for the PPD-handling test (Fix C). Only these two host +# paths are overlaid onto /usr; the rest of /usr is untouched. +CIROOT_BINDS = \ + $(CIROOT)$(bindir)/testppd:/usr/bin/testppd \ + $(CIROOT)$(datadir)/ppd:/usr/share/ppd + +# Proot-free leg: the dev compile/link/run test needs no /usr redirection. +test-autopkgtest-dev: stage-ciroot + $(CIROOT_ENV) \ + $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-dev + +# Proot leg: the PPD-handling test hardcodes /usr/bin/testppd and +# /usr/share/ppd/testppd; run it under proot with the staged binds. +test-autopkgtest-ppd: stage-ciroot + $(CIROOT_ENV) \ + AUTOPKGTEST_BINDS="$(CIROOT_BINDS)" \ + $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-ppd-handling + +# Full downstream suite (used on native CI legs and locally). +test-autopkgtest: test-autopkgtest-dev test-autopkgtest-ppd + +.PHONY: stage-ciroot install-test-programs install-test-data \ + test-autopkgtest test-autopkgtest-dev test-autopkgtest-ppd diff --git a/ci/autopkgtest/debian-tests/control b/ci/autopkgtest/debian-tests/control new file mode 100644 index 000000000..f39e44b77 --- /dev/null +++ b/ci/autopkgtest/debian-tests/control @@ -0,0 +1,12 @@ +Tests: libppd-2-dev +Depends: build-essential, + libcups2-dev, + libcupsfilters-dev, + libppd-dev, + pkg-config, + libppd-utils, +Restrictions: allow-stderr + +Tests: libppd-2-ppd-handling +Depends: libppd-tests +Restrictions: allow-stderr diff --git a/ci/autopkgtest/debian-tests/libppd-2-dev b/ci/autopkgtest/debian-tests/libppd-2-dev new file mode 100755 index 000000000..962b2fee1 --- /dev/null +++ b/ci/autopkgtest/debian-tests/libppd-2-dev @@ -0,0 +1,71 @@ +#!/bin/sh +# autopkgtest check: Build and run a program against libppd, to verify +# that the headers and pkg-config file are installed correctly +# (C) 2012 Canonical Ltd. +# (C) 2018-2019 Simon McVittie +# (C) 2023 Till Kamppeter +# Authors: Martin Pitt, Simon McVittie, Till Kamppeter + +set -eux + +package=libppd +WORKDIR="$(mktemp -d)" +export HOME="$WORKDIR" +export XDG_RUNTIME_DIR="$WORKDIR" +trap 'cd /; rm -rf "$WORKDIR"' 0 INT QUIT ABRT PIPE TERM + +if [ -n "${DEB_HOST_GNU_TYPE:-}" ]; then + CROSS_COMPILE="$DEB_HOST_GNU_TYPE-" +else + CROSS_COMPILE= +fi + +cd "$WORKDIR" +cat < test.c +// All header files of libppd API +#include +#include +#include + +int main(int argc, char *argv[]) +{ + // Actually use something from the library, so that it gets actually linked + cf_filter_universal_parameter_t universal_parameters; + memset(&universal_parameters, 0, sizeof(cf_filter_universal_parameter_t)); + return (ppdFilterCUPSWrapper(argc, argv, ppdFilterUniversal, + &universal_parameters, NULL)); +} +EOF + +cat < test.drv +#include +#include +Font * +Manufacturer "Test" +ModelName "TestPrinter" +Version 1.0 +Filter image/urf 100 - +*MediaSize Letter +MediaSize A4 +ColorDevice true +ColorModel Gray/Grayscale w chunky 0 +*ColorModel RGB/Color rgb chunky 0 +*Resolution - 8 0 0 0 "600dpi/600 DPI" +Resolution - 8 0 0 0 "1200dpi/1200 DPI" +PCFileName "test.ppd" +EOF + +ppdc test.drv +echo "create test PPD ($package): OK" +export PPD=./ppd/test.ppd +export CONTENT_TYPE=text/plain +export FINAL_CONTENT_TYPE=application/vnd.cups-pdf +# Deliberately word-splitting pkg-config's output: +# shellcheck disable=SC2046 +${CROSS_COMPILE}g++ -o "${package}-test" test.c $(${CROSS_COMPILE}pkg-config --cflags --libs "$package") $(${CROSS_COMPILE}pkg-config --cflags --libs "libcupsfilters") +echo "build ($package): OK" +[ -x "${package}-test" ] +echo -en 'Test1\nTest2\nTest3\n' | ./${package}-test 1 1 1 1 'PageSize=A4 Resolution=1200dpi' > test.pdf +echo "run ($package): OK" +file test.pdf | grep -q 'PDF document' +echo "check output file ($package): OK" diff --git a/ci/autopkgtest/debian-tests/libppd-2-ppd-handling b/ci/autopkgtest/debian-tests/libppd-2-ppd-handling new file mode 100755 index 000000000..49d5d3db6 --- /dev/null +++ b/ci/autopkgtest/debian-tests/libppd-2-ppd-handling @@ -0,0 +1,20 @@ +#!/bin/sh +# autopkgtest check: Run "make check" test program "testppd" using libppd +# installed on the system. +# (C) 2012 Canonical Ltd. +# (C) 2023 Till Kamppeter +# Authors: Martin Pitt, Till Kamppeter + +set -eux + +package=libppd +WORKDIR="$(mktemp -d)" +export HOME="$WORKDIR" +export XDG_RUNTIME_DIR="$WORKDIR" +trap 'cd /; rm -rf "$WORKDIR"' 0 INT QUIT ABRT PIPE TERM + +# Copy test PPD files into right place +cp -r /usr/share/ppd/testppd ppd +# Run the test program +/usr/bin/testppd +echo "run test program on system's $package: OK" diff --git a/ci/autopkgtest/run.sh b/ci/autopkgtest/run.sh new file mode 100755 index 000000000..6e3ccb678 --- /dev/null +++ b/ci/autopkgtest/run.sh @@ -0,0 +1,91 @@ +#!/bin/sh +# ci/autopkgtest/run.sh +# +# Universal DESTDIR-staging wrapper for the downstream Debian autopkgtests. +# Points PATH / LD_LIBRARY_PATH / PKG_CONFIG_PATH at the staged install tree +# ($CIROOT) produced by `make stage-ciroot`, then runs the unmodified +# downstream scripts vendored under ci/autopkgtest/debian-tests/. +# +# For libppd the PPD-handling test invokes the installed test binary and test +# data through *absolute* paths (/usr/bin/testppd, /usr/share/ppd/testppd). +# Those are redirected into the staging tree, without root and without +# touching the host /usr, via proot bind mounts requested through +# AUTOPKGTEST_BINDS (Fix C). +# +# Env in: +# CIROOT staging root (default: $PWD/_ciroot) +# CIPREFIX configured prefix (default: /usr) +# TOP_BUILDDIR build tree (default: $PWD) +# AUTOPKGTEST_BINDS optional space-separated "host:guest" proot binds for +# scripts that read installed binaries/data via absolute +# paths. Each pair is surgically overlaid so the rest of +# the host /usr (compiler, system libs) stays intact. +# Any extra exported variables (e.g. PPDC_DATADIR, CUPS_DATADIR) are passed +# straight through to the test scripts and survive the proot re-exec. +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +TESTS_DIR="$SCRIPT_DIR/debian-tests" + +: "${CIROOT:=$PWD/_ciroot}" +: "${CIPREFIX:=/usr}" +: "${TOP_BUILDDIR:=$PWD}" + +if [ ! -d "$CIROOT" ]; then + echo "run.sh: staging root not found: $CIROOT (run 'make stage-ciroot' first)" >&2 + exit 1 +fi + +ROOT="$CIROOT$CIPREFIX" +MULTIARCH=$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null \ + || gcc -dumpmachine 2>/dev/null || echo "") + +PATH="$ROOT/bin:$ROOT/sbin:$TOP_BUILDDIR:$TOP_BUILDDIR/.libs:$PATH" +LD_LIBRARY_PATH="$ROOT/lib${MULTIARCH:+:$ROOT/lib/$MULTIARCH}:$TOP_BUILDDIR/.libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +PKG_CONFIG_PATH="$ROOT/lib/pkgconfig${MULTIARCH:+:$ROOT/lib/$MULTIARCH/pkgconfig}:$ROOT/share/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +export PATH LD_LIBRARY_PATH PKG_CONFIG_PATH + +# Surgical /usr redirection (Fix C): re-exec the whole run under proot with the +# requested binds so unmodified scripts that hardcode /usr/... paths resolve +# into the staging tree. Only the named paths are overlaid; everything else +# (gcc/g++, system libcupsfilters, the dynamic loader) keeps resolving from the +# real host filesystem. +if [ -n "${AUTOPKGTEST_BINDS:-}" ] && [ -z "${_UNDER_PROOT:-}" ]; then + if command -v proot >/dev/null 2>&1; then + binds="" + for pair in $AUTOPKGTEST_BINDS; do binds="$binds -b $pair"; done + _UNDER_PROOT=1; export _UNDER_PROOT + # shellcheck disable=SC2086 + exec proot $binds "$0" "$@" + fi + echo "run.sh: AUTOPKGTEST_BINDS set but 'proot' is not installed" >&2 + echo "run.sh: install proot, or run the proot-free target (test-autopkgtest-dev)" >&2 + exit 1 +fi + +if [ "$#" -eq 0 ]; then + echo "run.sh: usage: run.sh [test-name...]" >&2 + exit 2 +fi + +rc=0 +for name in "$@"; do + script="$TESTS_DIR/$name" + if [ ! -f "$script" ]; then + echo "run.sh: no such test: $script" >&2 + rc=1 + continue + fi + chmod +x "$script" 2>/dev/null || true + workdir=$(mktemp -d) + echo "=== autopkgtest: $name (CIROOT=$CIROOT, prefix=$CIPREFIX) ===" + if ( cd "$workdir" && "$script" ); then + echo "=== PASS: $name ===" + else + rc=$? + echo "=== FAIL: $name (exit $rc) ===" >&2 + rc=1 + fi + rm -rf "$workdir" +done +exit $rc diff --git a/libppd.pc.in b/libppd.pc.in index 481a293ff..d4acaf474 100644 --- a/libppd.pc.in +++ b/libppd.pc.in @@ -9,4 +9,4 @@ Version: @VERSION@ Libs: -L${libdir} -lppd Libs.private: @CUPS_LIBS@ -Cflags: -I${includedir}/ppd +Cflags: -I${includedir} -I${includedir}/ppd From a2b3944e1e597db100088a726761b9dcc75725cf Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 11 Jun 2026 07:53:40 +0530 Subject: [PATCH 2/4] autopkgtest: use mount --bind for /usr redirection, proot as fallback --- ci/autopkgtest/run.sh | 82 +++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/ci/autopkgtest/run.sh b/ci/autopkgtest/run.sh index 6e3ccb678..d3d5dc52f 100755 --- a/ci/autopkgtest/run.sh +++ b/ci/autopkgtest/run.sh @@ -8,20 +8,27 @@ # # For libppd the PPD-handling test invokes the installed test binary and test # data through *absolute* paths (/usr/bin/testppd, /usr/share/ppd/testppd). -# Those are redirected into the staging tree, without root and without -# touching the host /usr, via proot bind mounts requested through -# AUTOPKGTEST_BINDS (Fix C). +# Those are redirected into the staging tree, surgically and reversibly, via +# AUTOPKGTEST_BINDS (Fix C). Two backends are supported, chosen automatically: +# +# * mount --bind - when running as root or with passwordless sudo +# (CI native runners). Real kernel bind mounts: robust +# on every architecture, torn down on exit. +# * proot - fallback when unprivileged (local dev): no root, no +# host changes. (proot is ptrace-based and unreliable +# under QEMU emulation and on some kernels, hence it is +# only the fallback.) # # Env in: # CIROOT staging root (default: $PWD/_ciroot) # CIPREFIX configured prefix (default: /usr) # TOP_BUILDDIR build tree (default: $PWD) -# AUTOPKGTEST_BINDS optional space-separated "host:guest" proot binds for -# scripts that read installed binaries/data via absolute -# paths. Each pair is surgically overlaid so the rest of -# the host /usr (compiler, system libs) stays intact. +# AUTOPKGTEST_BINDS optional space-separated "host:guest" binds for scripts +# that read installed binaries/data via absolute paths. +# Only the named paths are overlaid; the rest of the host +# /usr (compiler, system libs) stays intact. # Any extra exported variables (e.g. PPDC_DATADIR, CUPS_DATADIR) are passed -# straight through to the test scripts and survive the proot re-exec. +# straight through to the test scripts. set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) @@ -45,22 +52,59 @@ LD_LIBRARY_PATH="$ROOT/lib${MULTIARCH:+:$ROOT/lib/$MULTIARCH}:$TOP_BUILDDIR/.lib PKG_CONFIG_PATH="$ROOT/lib/pkgconfig${MULTIARCH:+:$ROOT/lib/$MULTIARCH/pkgconfig}:$ROOT/share/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" export PATH LD_LIBRARY_PATH PKG_CONFIG_PATH -# Surgical /usr redirection (Fix C): re-exec the whole run under proot with the -# requested binds so unmodified scripts that hardcode /usr/... paths resolve -# into the staging tree. Only the named paths are overlaid; everything else -# (gcc/g++, system libcupsfilters, the dynamic loader) keeps resolving from the -# real host filesystem. -if [ -n "${AUTOPKGTEST_BINDS:-}" ] && [ -z "${_UNDER_PROOT:-}" ]; then - if command -v proot >/dev/null 2>&1; then +# --------------------------------------------------------------------------- +# Surgical /usr redirection (Fix C) +# --------------------------------------------------------------------------- +BIND_UNMOUNT="" +BIND_RMDIRS="" +cleanup_binds() { + # Unmount in reverse and remove any placeholder targets we created. + for m in $BIND_UNMOUNT; do $BIND_SUDO umount "$m" 2>/dev/null || true; done + for d in $BIND_RMDIRS; do $BIND_SUDO rm -rf "$d" 2>/dev/null || true; done +} + +if [ -n "${AUTOPKGTEST_BINDS:-}" ] && [ -z "${_REDIR_DONE:-}" ]; then + # Pick a privilege escalator for mount --bind, if available. + BIND_SUDO="" + have_priv=0 + if [ "$(id -u)" = 0 ]; then + have_priv=1 + elif command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then + BIND_SUDO="sudo" + have_priv=1 + fi + + if [ "$have_priv" = 1 ]; then + trap cleanup_binds EXIT INT TERM + for pair in $AUTOPKGTEST_BINDS; do + src=${pair%%:*} + dst=${pair#*:} + if [ -d "$src" ]; then + if [ ! -e "$dst" ]; then $BIND_SUDO mkdir -p "$dst"; BIND_RMDIRS="$dst $BIND_RMDIRS"; fi + else + if [ ! -e "$dst" ]; then + $BIND_SUDO mkdir -p "$(dirname "$dst")" + $BIND_SUDO touch "$dst" + BIND_RMDIRS="$dst $BIND_RMDIRS" + fi + fi + $BIND_SUDO mount --bind "$src" "$dst" + BIND_UNMOUNT="$dst $BIND_UNMOUNT" + echo "run.sh: bound $src -> $dst (mount --bind)" + done + _REDIR_DONE=1 + elif command -v proot >/dev/null 2>&1; then binds="" for pair in $AUTOPKGTEST_BINDS; do binds="$binds -b $pair"; done - _UNDER_PROOT=1; export _UNDER_PROOT + _REDIR_DONE=1; export _REDIR_DONE + echo "run.sh: redirecting via proot (unprivileged fallback)" # shellcheck disable=SC2086 exec proot $binds "$0" "$@" + else + echo "run.sh: AUTOPKGTEST_BINDS set but neither root/passwordless-sudo" >&2 + echo "run.sh: (for mount --bind) nor proot is available." >&2 + exit 1 fi - echo "run.sh: AUTOPKGTEST_BINDS set but 'proot' is not installed" >&2 - echo "run.sh: install proot, or run the proot-free target (test-autopkgtest-dev)" >&2 - exit 1 fi if [ "$#" -eq 0 ]; then From 9db65abdf58f402eb07e0c5b938997bf427bf594 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 11 Jun 2026 08:03:04 +0530 Subject: [PATCH 3/4] autopkgtest: print aggregate PASS/FAIL summary for downstream tests --- Makefile.am | 18 ++++++++++++------ ci/autopkgtest/run.sh | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Makefile.am b/Makefile.am index cee5a3e54..d7ca8cd9e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -451,20 +451,26 @@ CIROOT_BINDS = \ $(CIROOT)$(bindir)/testppd:/usr/bin/testppd \ $(CIROOT)$(datadir)/ppd:/usr/share/ppd -# Proot-free leg: the dev compile/link/run test needs no /usr redirection. +# Full downstream suite in a single wrapper invocation, so the run produces one +# aggregate PASS/FAIL summary across both tests. Both run under the /usr binds; +# the dev test never touches the bound paths, so the binds are a no-op for it. +test-autopkgtest: stage-ciroot + $(CIROOT_ENV) \ + AUTOPKGTEST_BINDS="$(CIROOT_BINDS)" \ + $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-dev libppd-2-ppd-handling + +# Proot/bind-free leg: the dev compile/link/run test needs no /usr redirection +# (used on the QEMU-emulated CI legs, where nested redirection is unreliable). test-autopkgtest-dev: stage-ciroot $(CIROOT_ENV) \ $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-dev -# Proot leg: the PPD-handling test hardcodes /usr/bin/testppd and -# /usr/share/ppd/testppd; run it under proot with the staged binds. +# PPD-handling test only, under the staged /usr binds (handy for debugging the +# redirection path in isolation). test-autopkgtest-ppd: stage-ciroot $(CIROOT_ENV) \ AUTOPKGTEST_BINDS="$(CIROOT_BINDS)" \ $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-ppd-handling -# Full downstream suite (used on native CI legs and locally). -test-autopkgtest: test-autopkgtest-dev test-autopkgtest-ppd - .PHONY: stage-ciroot install-test-programs install-test-data \ test-autopkgtest test-autopkgtest-dev test-autopkgtest-ppd diff --git a/ci/autopkgtest/run.sh b/ci/autopkgtest/run.sh index d3d5dc52f..ac81601ac 100755 --- a/ci/autopkgtest/run.sh +++ b/ci/autopkgtest/run.sh @@ -113,10 +113,18 @@ if [ "$#" -eq 0 ]; then fi rc=0 +total=0 +n_pass=0 +n_fail=0 +results="" for name in "$@"; do + total=$((total + 1)) script="$TESTS_DIR/$name" if [ ! -f "$script" ]; then echo "run.sh: no such test: $script" >&2 + n_fail=$((n_fail + 1)) + results="$results +FAIL: $name (not found)" rc=1 continue fi @@ -125,11 +133,32 @@ for name in "$@"; do echo "=== autopkgtest: $name (CIROOT=$CIROOT, prefix=$CIPREFIX) ===" if ( cd "$workdir" && "$script" ); then echo "=== PASS: $name ===" + n_pass=$((n_pass + 1)) + results="$results +PASS: $name" else - rc=$? - echo "=== FAIL: $name (exit $rc) ===" >&2 + ec=$? + echo "=== FAIL: $name (exit $ec) ===" >&2 + n_fail=$((n_fail + 1)) + results="$results +FAIL: $name (exit $ec)" rc=1 fi rm -rf "$workdir" done + +# --------------------------------------------------------------------------- +# Aggregate summary (mirrors the Automake `make check` test-suite summary) +# --------------------------------------------------------------------------- +echo "============================================================================" +echo "Downstream autopkgtest summary" +echo "============================================================================" +printf '# TOTAL: %d\n' "$total" +printf '# PASS: %d\n' "$n_pass" +printf '# FAIL: %d\n' "$n_fail" +echo "----------------------------------------------------------------------------" +# Per-test breakdown (strip the leading blank line from the accumulator). +printf '%s\n' "$results" | sed '/^$/d' +echo "============================================================================" + exit $rc From 6c09159ab0493498cb7fe1c99debfd139e1a0161 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 12 Jun 2026 08:44:46 +0530 Subject: [PATCH 4/4] ci: run both downstream autopkgtests on all architectures via env-var path overrides --- .github/workflows/build.yml | 20 ++--- Makefile.am | 26 +++--- .../debian-tests/libppd-2-ppd-handling | 11 ++- ci/autopkgtest/run.sh | 80 ++----------------- 4 files changed, 36 insertions(+), 101 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 06ce9b2f9..0718cdee9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -104,8 +104,7 @@ jobs: ghostscript \ poppler-utils \ mupdf-tools \ - file \ - proot + file - name: Build & test libppd (native) if: matrix.use-qemu == false @@ -132,9 +131,10 @@ jobs: run: | set -ex cd "$REPO_DIR" - # Full downstream suite: libppd-2-dev (proot-free) + - # libppd-2-ppd-handling (proot redirects /usr/bin/testppd and - # /usr/share/ppd into the staging tree). + # Full downstream suite: libppd-2-dev (compile/link/run) + + # libppd-2-ppd-handling. Both point at the staged tree via + # environment overrides, so no privilege or path redirection is + # needed. make test-autopkgtest V=1 # ----------------------------------------------------------------------- @@ -199,11 +199,11 @@ jobs: exit 1 } - # Downstream autopkgtest, proot-free leg only: nested proot under - # QEMU-user emulation is unreliable, so the absolute-path PPD- - # handling test runs on the native legs. The dev compile/link/run - # test runs here. - make test-autopkgtest-dev V=1 + # Full downstream autopkgtest suite. Both tests resolve the + # staged build tree through environment overrides (no absolute + # paths, no privilege, no bind mounts), so the same suite that + # runs on the native legs runs unchanged under QEMU emulation. + make test-autopkgtest V=1 # ----------------------------------------------------------------------- # ARTIFACT UPLOAD (all four legs, only on failure) diff --git a/Makefile.am b/Makefile.am index d7ca8cd9e..f06c5ece6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -434,7 +434,10 @@ stage-ciroot: all # Common environment for the wrapper. PPDC_DATADIR / CUPS_DATADIR point ppdc # at the staged *.defs so `ppdc test.drv` resolves / -# from the staging tree. +# from the staging tree. LIBPPD_TESTPPD / LIBPPD_PPDDIR point the PPD-handling +# test at the staged binary and data instead of the system /usr paths, so it +# needs no absolute-path redirection (no root, no proot) and runs identically +# on native and QEMU-emulated architectures. # NB: libppd configures with a split prefix (prefix=/ but exec_prefix=/usr and # everything installs under /usr), so the staging root the wrapper points at is # $(CIROOT)$(exec_prefix), not $(CIROOT)$(prefix). @@ -443,33 +446,24 @@ CIROOT_ENV = \ CIPREFIX="$(exec_prefix)" \ TOP_BUILDDIR="$(abs_top_builddir)" \ PPDC_DATADIR="$(CIROOT)$(datadir)/ppdc" \ - CUPS_DATADIR="$(CIROOT)$(datadir)" - -# Absolute-path binds for the PPD-handling test (Fix C). Only these two host -# paths are overlaid onto /usr; the rest of /usr is untouched. -CIROOT_BINDS = \ - $(CIROOT)$(bindir)/testppd:/usr/bin/testppd \ - $(CIROOT)$(datadir)/ppd:/usr/share/ppd + CUPS_DATADIR="$(CIROOT)$(datadir)" \ + LIBPPD_TESTPPD="$(CIROOT)$(bindir)/testppd" \ + LIBPPD_PPDDIR="$(CIROOT)$(datadir)/ppd/testppd" # Full downstream suite in a single wrapper invocation, so the run produces one -# aggregate PASS/FAIL summary across both tests. Both run under the /usr binds; -# the dev test never touches the bound paths, so the binds are a no-op for it. +# aggregate PASS/FAIL summary across both tests. Neither test needs privilege +# or path redirection, so this is identical on every architecture. test-autopkgtest: stage-ciroot $(CIROOT_ENV) \ - AUTOPKGTEST_BINDS="$(CIROOT_BINDS)" \ $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-dev libppd-2-ppd-handling -# Proot/bind-free leg: the dev compile/link/run test needs no /usr redirection -# (used on the QEMU-emulated CI legs, where nested redirection is unreliable). +# Single-test convenience targets (handy for debugging one case in isolation). test-autopkgtest-dev: stage-ciroot $(CIROOT_ENV) \ $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-dev -# PPD-handling test only, under the staged /usr binds (handy for debugging the -# redirection path in isolation). test-autopkgtest-ppd: stage-ciroot $(CIROOT_ENV) \ - AUTOPKGTEST_BINDS="$(CIROOT_BINDS)" \ $(SHELL) $(srcdir)/ci/autopkgtest/run.sh libppd-2-ppd-handling .PHONY: stage-ciroot install-test-programs install-test-data \ diff --git a/ci/autopkgtest/debian-tests/libppd-2-ppd-handling b/ci/autopkgtest/debian-tests/libppd-2-ppd-handling index 49d5d3db6..f3c19c452 100755 --- a/ci/autopkgtest/debian-tests/libppd-2-ppd-handling +++ b/ci/autopkgtest/debian-tests/libppd-2-ppd-handling @@ -13,8 +13,15 @@ export HOME="$WORKDIR" export XDG_RUNTIME_DIR="$WORKDIR" trap 'cd /; rm -rf "$WORKDIR"' 0 INT QUIT ABRT PIPE TERM +# Location of the installed test program and its PPD data. Default to the +# system paths used when the package is installed (the normal Debian +# autopkgtest case); allow a staged build tree to override them so the test +# assumes no absolute paths and runs unprivileged on every architecture. +: "${LIBPPD_TESTPPD:=/usr/bin/testppd}" +: "${LIBPPD_PPDDIR:=/usr/share/ppd/testppd}" + # Copy test PPD files into right place -cp -r /usr/share/ppd/testppd ppd +cp -r "$LIBPPD_PPDDIR" ppd # Run the test program -/usr/bin/testppd +"$LIBPPD_TESTPPD" echo "run test program on system's $package: OK" diff --git a/ci/autopkgtest/run.sh b/ci/autopkgtest/run.sh index ac81601ac..616438fe5 100755 --- a/ci/autopkgtest/run.sh +++ b/ci/autopkgtest/run.sh @@ -6,29 +6,18 @@ # ($CIROOT) produced by `make stage-ciroot`, then runs the unmodified # downstream scripts vendored under ci/autopkgtest/debian-tests/. # -# For libppd the PPD-handling test invokes the installed test binary and test -# data through *absolute* paths (/usr/bin/testppd, /usr/share/ppd/testppd). -# Those are redirected into the staging tree, surgically and reversibly, via -# AUTOPKGTEST_BINDS (Fix C). Two backends are supported, chosen automatically: -# -# * mount --bind - when running as root or with passwordless sudo -# (CI native runners). Real kernel bind mounts: robust -# on every architecture, torn down on exit. -# * proot - fallback when unprivileged (local dev): no root, no -# host changes. (proot is ptrace-based and unreliable -# under QEMU emulation and on some kernels, hence it is -# only the fallback.) +# The downstream scripts take environment overrides (e.g. LIBPPD_TESTPPD, +# LIBPPD_PPDDIR) that default to the system /usr paths but can be redirected +# into the staging tree. That keeps every test free of absolute-path +# assumptions, so the wrapper needs no privilege and no bind mounts and runs +# identically on native and QEMU-emulated architectures. # # Env in: # CIROOT staging root (default: $PWD/_ciroot) # CIPREFIX configured prefix (default: /usr) # TOP_BUILDDIR build tree (default: $PWD) -# AUTOPKGTEST_BINDS optional space-separated "host:guest" binds for scripts -# that read installed binaries/data via absolute paths. -# Only the named paths are overlaid; the rest of the host -# /usr (compiler, system libs) stays intact. -# Any extra exported variables (e.g. PPDC_DATADIR, CUPS_DATADIR) are passed -# straight through to the test scripts. +# Any extra exported variables (e.g. PPDC_DATADIR, CUPS_DATADIR, +# LIBPPD_TESTPPD, LIBPPD_PPDDIR) are passed straight through to the scripts. set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) @@ -52,61 +41,6 @@ LD_LIBRARY_PATH="$ROOT/lib${MULTIARCH:+:$ROOT/lib/$MULTIARCH}:$TOP_BUILDDIR/.lib PKG_CONFIG_PATH="$ROOT/lib/pkgconfig${MULTIARCH:+:$ROOT/lib/$MULTIARCH/pkgconfig}:$ROOT/share/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" export PATH LD_LIBRARY_PATH PKG_CONFIG_PATH -# --------------------------------------------------------------------------- -# Surgical /usr redirection (Fix C) -# --------------------------------------------------------------------------- -BIND_UNMOUNT="" -BIND_RMDIRS="" -cleanup_binds() { - # Unmount in reverse and remove any placeholder targets we created. - for m in $BIND_UNMOUNT; do $BIND_SUDO umount "$m" 2>/dev/null || true; done - for d in $BIND_RMDIRS; do $BIND_SUDO rm -rf "$d" 2>/dev/null || true; done -} - -if [ -n "${AUTOPKGTEST_BINDS:-}" ] && [ -z "${_REDIR_DONE:-}" ]; then - # Pick a privilege escalator for mount --bind, if available. - BIND_SUDO="" - have_priv=0 - if [ "$(id -u)" = 0 ]; then - have_priv=1 - elif command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then - BIND_SUDO="sudo" - have_priv=1 - fi - - if [ "$have_priv" = 1 ]; then - trap cleanup_binds EXIT INT TERM - for pair in $AUTOPKGTEST_BINDS; do - src=${pair%%:*} - dst=${pair#*:} - if [ -d "$src" ]; then - if [ ! -e "$dst" ]; then $BIND_SUDO mkdir -p "$dst"; BIND_RMDIRS="$dst $BIND_RMDIRS"; fi - else - if [ ! -e "$dst" ]; then - $BIND_SUDO mkdir -p "$(dirname "$dst")" - $BIND_SUDO touch "$dst" - BIND_RMDIRS="$dst $BIND_RMDIRS" - fi - fi - $BIND_SUDO mount --bind "$src" "$dst" - BIND_UNMOUNT="$dst $BIND_UNMOUNT" - echo "run.sh: bound $src -> $dst (mount --bind)" - done - _REDIR_DONE=1 - elif command -v proot >/dev/null 2>&1; then - binds="" - for pair in $AUTOPKGTEST_BINDS; do binds="$binds -b $pair"; done - _REDIR_DONE=1; export _REDIR_DONE - echo "run.sh: redirecting via proot (unprivileged fallback)" - # shellcheck disable=SC2086 - exec proot $binds "$0" "$@" - else - echo "run.sh: AUTOPKGTEST_BINDS set but neither root/passwordless-sudo" >&2 - echo "run.sh: (for mount --bind) nor proot is available." >&2 - exit 1 - fi -fi - if [ "$#" -eq 0 ]; then echo "run.sh: usage: run.sh [test-name...]" >&2 exit 2