diff --git a/.gitignore b/.gitignore index 4b91d91d..9b5f0c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ results/ regression.diffs regression.out +.pgc_built_for_major diff --git a/test/harness_selftest.sh b/test/harness_selftest.sh index 614e5262..69ced26e 100755 --- a/test/harness_selftest.sh +++ b/test/harness_selftest.sh @@ -843,4 +843,59 @@ eval "$_realfree" check "premise: the real prober was restored, or every check after this lies" \ "$(pgc_port_free 1 && echo probing || echo stubbed)" "probing" + +# ---- an in-tree build must not reuse another major's objects (#536) --------- +# +# lib.sh builds in $PGC_SRCDIR with no clean and no record of which major the +# objects belong to. The MATRIX is not affected -- run_all_versions.sh cleans +# each per-major copy right after its cp -a, measured after #536 was filed +# claiming otherwise. This guard is for the single-suite path only. +check "premise: the build-stamp decision is exposed to be judged" \ + "$(type -t pgc_build_needs_clean)" "function" + +check "building the same major again needs no clean" \ + "$(pgc_build_needs_clean 18 18 yes)" "no" +check "building a DIFFERENT major needs a clean, which is the #536 case" \ + "$(pgc_build_needs_clean 18 19 yes)" "yes" +check "and in the other direction too" \ + "$(pgc_build_needs_clean 19 18 yes)" "yes" +check "an unparseable stamp cleans rather than guessing" \ + "$(pgc_build_needs_clean garbage 18 yes)" "yes" +check "and an empty WANT is refused rather than compared" \ + "$(pgc_build_needs_clean 18 "" yes)" "yes" + +# The case the end-to-end proof exposed. A tree built BY HAND leaves objects and +# NO stamp; reading that as "nothing to contaminate" let the first version stay +# silent on exactly the path it exists for. +check "objects with NO stamp are unknown provenance and must be cleaned" \ + "$(pgc_build_needs_clean "" 18 yes)" "yes" +check "but a tree with no objects at all needs nothing, stamp or not" \ + "$(pgc_build_needs_clean "" 18 no)" "no" + +_bmsg_unknown="$(pgc_build_stale_message "" 19)" +check "an unknown provenance is not reported as a major" \ + "$(grep -c 'PG?' <<<"$_bmsg_unknown")" "0" +check "and it says plainly that no major was recorded" \ + "$([ "$(grep -ci 'no recorded major' <<<"$_bmsg_unknown")" -ge 1 ] && echo yes || echo no)" "yes" + +# The stamp must be the bare major and nothing else, and this exercises LIB.SH'S +# WRITER rather than a copy of it. Two earlier versions of this check were +# useless: one wrote its own temp file with a correct printf and verified that, +# which cannot fail; the other grepped for the bad form with a pattern that +# matched the GOOD form, so it could never pass. Both were caught by the gate. +check "premise: the stamp writer is a function that can be exercised" \ + "$(type -t pgc_write_build_stamp)" "function" + +_stmp="$(mktemp)" +pgc_write_build_stamp "$_stmp" 19 +check "the stamp lib.sh writes is exactly the major" \ + "$(cat "$_stmp")" "19" +check "and it is 3 bytes, not an escaped literal" \ + "$(wc -c < "$_stmp" | tr -d ' ')" "3" +rm -f "$_stmp" + +check "the build path asks pgc_build_needs_clean rather than merely naming it" \ + "$([ "$(grep -c 'pgc_build_needs_clean "' "$TESTDIR/lib.sh")" -ge 1 ] && echo yes || echo no)" "yes" + + pgc_summary diff --git a/test/lib.sh b/test/lib.sh index f644c76a..f313244b 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -173,12 +173,25 @@ pgc_setup() { # installed .so and saw the same hash either side of a source change that could # not have produced it. if [ -z "${PGC_SKIP_BUILD:-}" ]; then + # Objects from another major link but do not load (#536). + _pgc_stamp="$PGC_SRCDIR/.pgc_built_for_major" + _pgc_had="$(cat "$_pgc_stamp" 2>/dev/null | tr -dc '0-9')" + _pgc_objs=no + [ -n "$(find "$PGC_SRCDIR/src" -maxdepth 1 -name '*.o' -print -quit 2>/dev/null)" ] && _pgc_objs=yes + if [ "$(pgc_build_needs_clean "$_pgc_had" "$PGC_MAJOR" "$_pgc_objs")" = yes ]; then + pgc_build_stale_message "$_pgc_had" "$PGC_MAJOR" + make -C "$PGC_SRCDIR" clean PG_CONFIG="$PGC_PG_CONFIG" >/dev/null 2>&1 || true + fi echo "-- building" if ! make -C "$PGC_SRCDIR" PG_CONFIG="$PGC_PG_CONFIG" >/dev/null; then echo "FATAL: the build failed, so there is nothing new to test" >&2 echo " (refusing to report checks against the previously installed .so)" >&2 exit 1 fi + # Stamped only after a build that succeeded. printf '%s\n', NOT '%s\\n': + # the doubled backslash writes the four bytes 1 9 \ n, which only worked + # because the reader strips non-digits. Caught in review, not by a test. + pgc_write_build_stamp "$_pgc_stamp" "$PGC_MAJOR" echo "-- installing" if ! make -C "$PGC_SRCDIR" install PG_CONFIG="$PGC_PG_CONFIG" >/dev/null; then echo "FATAL: the install failed, so the .so under test is not the one just built" >&2 @@ -451,6 +464,50 @@ pgc_start_failure_message() { fi } + +# ---- an in-tree build must not reuse another major's objects (#536) --------- +# +# lib.sh builds in $PGC_SRCDIR with no clean and no record of which major the +# objects belong to. One suite against pg18a then pg19a in the same tree links +# the first run's objects into the second .so, which fails to load with +# "undefined symbol: get_relation_info_hook": every cluster start dies and the +# suite reports eight retries with no cause. +# +# The MATRIX is not exposed -- run_all_versions.sh cleans each per-major copy +# right after its cp -a. Measured, after #536 was filed claiming otherwise. +# +# Objects present with NO stamp are unknown provenance and must be cleaned: that +# is what a hand-run `make PG_CONFIG=...` leaves, which is how anyone debugging +# builds and how every gate script here builds. +pgc_build_needs_clean() { + local have="${1:-}" want="${2:-}" objects="${3:-}" + + case "$want" in '' | *[!0-9]*) echo yes; return ;; esac + [ "$objects" = yes ] || { echo no; return; } + [ -z "$have" ] && { echo yes; return; } + case "$have" in *[!0-9]*) echo yes; return ;; esac + [ "$have" = "$want" ] && echo no || echo yes +} + +# The stamp writer, as a function so a check can exercise THE WRITER rather +# than a copy of it. It was written inline as printf '%s\\n' -- a doubled +# backslash inside single quotes -- which emits the four bytes `1 9 \ n`. That +# passed unnoticed because the reader does tr -dc '0-9' and strips the junk; a +# direct comparison against the major failed. Found in review, not by a check. +pgc_write_build_stamp() { + printf '%s\n' "${2:-}" > "${1:-/dev/null}" 2>/dev/null || true +} + +# An absent stamp is not "built for PG?" -- that asserts a provenance the code +# never recorded, which is the defect #537 was filed about. +pgc_build_stale_message() { + if [ -z "${1:-}" ]; then + printf -- '-- the tree holds objects with no recorded major and this run wants PG%s; cleaning first (#536)\n' "${2:-?}" + else + printf -- '-- the tree was last built for PG%s and this run wants PG%s; cleaning first (#536)\n' "$1" "${2:-?}" + fi +} + # ---- SQL helpers (run as root over TCP, trust auth) ------------------------ PGC_PSQL_BASE() {