Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion HISTORY.asc
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
STABLE
------
== Fix `make results` refusing to create a new test's first expected output
`make verify-results` used to block `make results` anytime
`regression.diffs` existed, which would be the case when there were no
`test/expected/` files at all. Switch to actually examining
`regression.diffs` to determine if tests are broken. Note this is only in
effect when `PGXNTOOL_VERIFY_RESULTS_MODE=pgtap`.

== Add `PGXNTOOL_ENABLE_FS_INSTALL` and `PGXNTOOL_ENABLE_PGXN_INSTALL`
`test`/`verify-results` unconditionally filesystem-installed the extension
(via PGXS's `install`), and `installcheck` unconditionally auto-installed
Expand Down Expand Up @@ -61,7 +68,7 @@ silently swallowed too, making the file "pass" regardless of what happened.
`test/install/*.out` was never really compared against anything, and is
rewritten by every run, it's now gitignored -- stop committing it.

Issues fixed in this release: #87, #21, #55, #90, #97, #108
Issues fixed in this release: #87, #21, #55, #90, #97, #108, #119

2.3.0
-----
Expand Down
8 changes: 5 additions & 3 deletions README.asc
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,13 @@ Never run `make results` without first verifying the test changes are correct. T
==== verify-results safeguard
By default, `make results` will refuse to run if your tests are failing, so you can't accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by `PGXNTOOL_VERIFY_RESULTS_MODE`:

`pgtap` (the default):: Scans `test/results/*.out` for pgTAP `not ok` lines (ignoring `# TODO` items) and plan-count mismatches, then also checks `test/results/regression.diffs` for any output mismatch pgTAP itself wouldn't catch.
`diffs`:: Only checks whether `test/results/regression.diffs` exists.
`pgtap` (the default):: Scans `test/results/*.out` for pgTAP `not ok` lines (ignoring `# TODO` items) and plan-count mismatches, then also checks `test/regression.diffs` for any output mismatch pgTAP itself wouldn't catch. A test that has no expected output file yet is the one exception: it always shows up in `regression.diffs` (pgxntool creates an empty expected file for it, because `pg_regress` aborts on a missing one), so `make results` is allowed to create its first expected output — provided its own pgTAP output is clean and free of SQL errors. If a new test legitimately needs output containing a SQL error blessed, use `PGXNTOOL_ENABLE_VERIFY_RESULTS=no` for that one run.
`diffs`:: Only checks whether `test/regression.diffs` exists, so it can't create a new test's first expected output; use `pgtap` mode, or `PGXNTOOL_ENABLE_VERIFY_RESULTS=no` for that one run.

If tests are failing, `make results` prints an explanation of what failed and exits without touching `test/expected/`; fix the failures, then re-run `make results`.

It also refuses when `pg_regress` didn't finish at all — an unreachable server, an interrupt — since `test/results/` then still holds an earlier run's output and blessing it would make that stale output the new expected results.

To select a mode explicitly:
----
# In your Makefile
Expand Down Expand Up @@ -755,7 +757,7 @@ Default: `pgtap`. Controls how the <<_verify_results_safeguard,verify-results sa

==== pgtap

Scans `test/results/*.out` for pgTap `not ok` lines and plan mismatches, falling back to checking for `regression.diffs` too. Use this mode when your test suite uses pgTap.
Scans `test/results/*.out` for pgTap `not ok` lines and plan mismatches, falling back to checking `regression.diffs` too — except for a test that has no expected output yet, whose first expected file `make results` is allowed to create. Use this mode when your test suite uses pgTap.

==== diffs

Expand Down
11 changes: 7 additions & 4 deletions README.html
Original file line number Diff line number Diff line change
Expand Up @@ -1060,18 +1060,21 @@ <h4 id="_verify_results_safeguard"><a class="anchor" href="#_verify_results_safe
<dl>
<dt class="hdlist1"><code>pgtap</code> (the default)</dt>
<dd>
<p>Scans <code>test/results/*.out</code> for pgTAP <code>not ok</code> lines (ignoring <code># TODO</code> items) and plan-count mismatches, then also checks <code>test/results/regression.diffs</code> for any output mismatch pgTAP itself wouldn&#8217;t catch.</p>
<p>Scans <code>test/results/*.out</code> for pgTAP <code>not ok</code> lines (ignoring <code># TODO</code> items) and plan-count mismatches, then also checks <code>test/regression.diffs</code> for any output mismatch pgTAP itself wouldn&#8217;t catch. A test that has no expected output file yet is the one exception: it always shows up in <code>regression.diffs</code> (pgxntool creates an empty expected file for it, because <code>pg_regress</code> aborts on a missing one), so <code>make results</code> is allowed to create its first expected output — provided its own pgTAP output is clean and free of SQL errors. If a new test legitimately needs output containing a SQL error blessed, use <code>PGXNTOOL_ENABLE_VERIFY_RESULTS=no</code> for that one run.</p>
</dd>
<dt class="hdlist1"><code>diffs</code></dt>
<dd>
<p>Only checks whether <code>test/results/regression.diffs</code> exists.</p>
<p>Only checks whether <code>test/regression.diffs</code> exists, so it can&#8217;t create a new test&#8217;s first expected output; use <code>pgtap</code> mode, or <code>PGXNTOOL_ENABLE_VERIFY_RESULTS=no</code> for that one run.</p>
</dd>
</dl>
</div>
<div class="paragraph">
<p>If tests are failing, <code>make results</code> prints an explanation of what failed and exits without touching <code>test/expected/</code>; fix the failures, then re-run <code>make results</code>.</p>
</div>
<div class="paragraph">
<p>It also refuses when <code>pg_regress</code> didn&#8217;t finish at all — an unreachable server, an interrupt — since <code>test/results/</code> then still holds an earlier run&#8217;s output and blessing it would make that stale output the new expected results.</p>
</div>
<div class="paragraph">
<p>To select a mode explicitly:</p>
</div>
<div class="listingblock">
Expand Down Expand Up @@ -2040,7 +2043,7 @@ <h3 id="_pgxntool_verify_results_mode"><a class="anchor" href="#_pgxntool_verify
<div class="sect3">
<h4 id="_pgtap"><a class="anchor" href="#_pgtap"></a><a class="link" href="#_pgtap">8.8.1. pgtap</a></h4>
<div class="paragraph">
<p>Scans <code>test/results/*.out</code> for pgTap <code>not ok</code> lines and plan mismatches, falling back to checking for <code>regression.diffs</code> too. Use this mode when your test suite uses pgTap.</p>
<p>Scans <code>test/results/*.out</code> for pgTap <code>not ok</code> lines and plan mismatches, falling back to checking <code>regression.diffs</code> too — except for a test that has no expected output yet, whose first expected file <code>make results</code> is allowed to create. Use this mode when your test suite uses pgTap.</p>
</div>
</div>
<div class="sect3">
Expand Down Expand Up @@ -2134,7 +2137,7 @@ <h2 id="_copyright"><a class="anchor" href="#_copyright"></a><a class="link" hre
</div>
<div id="footer">
<div id="footer-text">
Last updated 2026-09-16 16:23:56 -0500
Last updated 2026-10-04 14:55:16 -0500
</div>
</div>
</body>
Expand Down
116 changes: 108 additions & 8 deletions verify-results-pgtap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# pgxntool/verify-results-pgtap.sh - Check pgtap results before 'make results'
#
# Scans pgtap output files for failures and plan mismatches, then checks
# regression.diffs as a fallback. Exits non-zero if any problems are found.
# regression.diffs as a fallback, ignoring entries for tests that have no
# expected output yet. Exits non-zero if any problems are found.
#
# Usage: verify-results-pgtap.sh TESTOUT
#
Expand Down Expand Up @@ -36,12 +37,111 @@ if [ $failed -ne 0 ]; then
exit 1
fi

# Also check regression.diffs (output mismatch even if pgtap all passed)
if [ -r "$TESTOUT/regression.diffs" ]; then
echo "ERROR: Tests are failing. Cannot run 'make results'."
echo "Fix test failures first, then run 'make results'."
echo
echo "See $TESTOUT/regression.diffs for details:"
cat "$TESTOUT/regression.diffs"
# Also check regression.diffs (output mismatch even if pgtap all passed).
#
# A test whose output matched ANY of its expected files - including a
# pg_regress _N.out alternate - is left out of regression.diffs entirely, so
Comment thread
jnasbyupgrade marked this conversation as resolved.
# every block in here is a test that matched none of them. The one exception
# is a test that has no expected output yet: pg_regress aborts outright on a
# missing expected file, so base.mk touches an empty test/expected/<name>.out
# for every test/sql/*.sql lacking one, and diffing actual output against that
# placeholder always reports a difference. Blocking on that is unwinnable -
# seeding that first expected file is precisely what `make results` is for -
# so those blocks are skipped here.
diffs="$TESTOUT/regression.diffs"

# pg_regress truncates regression.diffs at startup and deletes it again on a
# clean finish with nothing to report, so an empty one means it died before
# comparing anything - an unreachable server, an interrupt. Whatever is in
# results/ is then left over from some earlier run, and base.mk's `.IGNORE:
# installcheck` means `make results` gets here anyway, so blessing it would
# bless stale output.
if [ -e "$diffs" ] && [ ! -s "$diffs" ]; then
echo "ERROR: pg_regress did not complete. Cannot run 'make results'."
echo "$diffs is empty, so nothing was actually compared;"
echo "anything in $TESTOUT/results/ is left over from an earlier run."
exit 1
fi

if [ -s "$diffs" ]; then
# pg_regress heads each block with "diff <opts> <expected> <results>" (see
# results_differ() in pg_regress.c), then appends the diff itself. Only
# those headers and diff hunk headers can start with "diff " or "@@ "
# unprefixed; every line carrying file content is prefixed with ' ', '+',
# '-' or '\'. A block whose hunks all read "@@ -0,0 +N,M @@" was diffed
# against an empty expected file, i.e. that placeholder. Anything that
# fits none of those shapes is something this has no business
# interpreting, so it blocks the whole file.
#
# Hunk headers only exist in unified diffs. PostgreSQL 12 was the first
# to write them (11 and older use -C3 context diffs), so on those every
# block lands in the blocked pile - the same refusal `make results` gave
# before any of this existed.
classified=$(awk '
function flush( status) {
if (results == "") return
status = (hunks > 0 && unblessed) ? "unblessed" : "regression"
print status "\t" results
}
/^diff / { flush(); results = $NF; unblessed = 1; hunks = 0; next }
/^@@ / { hunks++; if ($0 !~ /^@@ -0,0 /) unblessed = 0; next }
/^[ +-]/ { next }
/^\\/ { next }
/^$/ { next }
{ unrecognized = 1 }
END { flush(); if (unrecognized) print "unrecognized\t" FILENAME }
' "$diffs")

blocked=
blessable=
while IFS="$(printf '\t')" read -r status file; do
case $status in
unblessed)
# An unblessed file holding a hard SQL error is not a passing test
# the pgtap scan above was merely quiet about: ON_ERROR_STOP aborts
# the script, so pgtap emits neither 'not ok' nor its plan-mismatch
# line. Blessing that would make the error the baseline.
#
# pg_regress feeds psql on stdin, so a failing statement in the
# test file itself reports as "ERROR: ..." at column 0, while one
# inside an \i'd file (setup.sql, finish.sql) reports as
# "psql:test/pgxntool/setup.sql:3: ERROR: ...". Matching either
# position, and psql's own two spaces after the severity, keeps a
# passing test whose description merely mentions "ERROR:" out of it.
if [ -r "$file" ] && ! grep -qE '(^|: )ERROR: ' "$file"; then
blessable="$blessable $file
"
continue
fi
;;
unrecognized)
blocked="$blocked (unrecognized content in $file)
"
continue
;;
esac
blocked="$blocked $file
"
done <<EOF
$classified
EOF

# Nothing recognizable in a non-empty regression.diffs: refuse to guess.
[ -n "$classified" ] || blocked=" (unrecognized $diffs content)
"

if [ -n "$blocked" ]; then
echo "ERROR: Tests are failing. Cannot run 'make results'."
echo "Fix test failures first, then run 'make results'."
echo
echo "Failing tests:"
printf '%s' "$blocked"
echo
echo "See $diffs for details:"
cat "$diffs"
exit 1
fi

echo "NOTE: these tests have no expected output yet; 'make results' will create it:"
printf '%s' "$blessable"
fi
Loading