From d62a875a4af13ecce481936dd2c275424ea72e2e Mon Sep 17 00:00:00 2001
From: jnasbyupgrade
Date: Mon, 12 Oct 2026 18:28:04 -0500
Subject: [PATCH 1/5] Fail `make test` on pgTap failures and plan problems in
actual output
`make test` failed only on `regression.diffs`, so a `not ok` line
blessed into an expected file passed, as did a test that died before
printing its plan, and `test/install` output was never checked at all.
Add `test/bin/check-tap-output.sh`, which fails a file with TAP output
on a `not ok` without `# TODO`, pgTap's "Looks like you planned" line,
a missing or duplicate `1..N` plan line, or a test count that differs
from the plan. Files without TAP output pass untouched.
- The `test` recipe runs it over `$(TESTOUT)/results/*.out` and the
`test/install` actual output, then still prints `regression.diffs`;
either failing fails the run. New `PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT`
(default `yes`) disables it. It lives in the recipe, not `TEST_DEPS`,
so `make PGXNTOOL_ENABLE_VERIFY_RESULTS=no results` still blesses.
- `verify-results-pgtap.sh` uses the same script for its TAP phase, so
`make results` gains the plan checks and the `test/install` scan.
- The `test/install` actual output is `$(TESTOUT)/install/.out`;
the base.mk comment now says so.
Part of #106.
Related changes in pgxntool-test:
- Unit tests for `check-tap-output.sh` and the new
`verify-results-pgtap.sh` behavior
- `make test` wiring tests, plus an end-to-end blessed `not ok` failure
Co-Authored-By: Claude Opus 5.5
---
CLAUDE.md | 4 +--
HISTORY.asc | 22 ++++++++++++
README.asc | 14 +++++---
README.html | 28 ++++++++++-----
base.mk | 63 ++++++++++++++++++++++++++++-----
test/bin/check-tap-output.sh | 68 ++++++++++++++++++++++++++++++++++++
verify-results-pgtap.sh | 35 ++++++-------------
7 files changed, 187 insertions(+), 47 deletions(-)
create mode 100755 test/bin/check-tap-output.sh
diff --git a/CLAUDE.md b/CLAUDE.md
index cca492c..696ef46 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -179,7 +179,7 @@ make pgxntool-sync # Update to latest pgxntool via git subtree pull
- Correct test dependency installation (`testdeps`, and `test-build` when enabled); `testdeps`' default `pgtap` prerequisite auto-installs pgtap via `pgxn install`, a no-op when `PGXNTOOL_ENABLE_PGXN_INSTALL=no`
- Extension is filesystem-installed before tests run (`install`), unless `PGXNTOOL_ENABLE_FS_INSTALL=no`
- Test comparison via `installcheck`, with diffs shown on failure
-- Safeguard checks that fail the run: the stale-expected-file check (orphaned or non-`.out` files in `test/expected/`, run after `pg_regress`), and, when `test/install` is in use, the check that every `test/install/*.sql` file sets `ON_ERROR_STOP`
+- Safeguard checks that fail the run: the pgTap output check (a `not ok` without `# TODO`, a missing or duplicate `1..N` plan, or a plan/test count mismatch in `test/results/*.out` or the `test/install` actual output -- even when the output matches its expected file; `PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT=no` disables it), the stale-expected-file check (orphaned or non-`.out` files in `test/expected/`, run after `pg_regress`), and, when `test/install` is in use, the check that every `test/install/*.sql` file sets `ON_ERROR_STOP`
Note: `make test` intentionally does *not* depend on `clean` — depending on `clean` caused problems with incremental/watch-based builds (see `base.mk`). If your tests need a clean build to pass, that's a sign of a missing dependency elsewhere, not something to fix by adding `clean` back.
@@ -207,7 +207,7 @@ When tests fail, examine the diff output carefully. The actual test output in `t
**Exceptions to the above** -- `test-build` and `test/install` (both optional, see `README.asc`) don't follow the `test/results` vs `test/expected` model:
- **test-build** runs first, in its own separate `pg_regress` pass over `test/build/*.sql`, and gates the main suite: if it fails, `test/install`/`test/sql` never run at all. It does compare actual vs expected normally (`test/build/results/` vs `test/build/expected/`) -- use `make results-build` to refresh its expected output, not `make results`.
-- **test/install** does NOT get a real diff at all: its actual output is written to the exact same file as its expected output, so a content difference can never fail the build, no matter what changed. The only thing that still fails the build is a hard SQL error, and only if the file has `ON_ERROR_STOP` set (directly, or via `\i test/pgxntool/psql.sql`) -- pgxntool checks for this by default, passing any file that includes `psql.sql` or has its own `\set`/`\unset ON_ERROR_STOP`, whatever the value. If a `test/install/*.sql` file is misbehaving, don't go looking for a diff; check whether it errored, and don't assume a stale-looking `.out` for it means anything.
+- **test/install** does NOT get a real diff at all: its actual output is written to the exact same file as its expected output, so a content difference can never fail the build, no matter what changed. Apart from pgTap failures (caught by the pgTap output check above), the only thing that still fails the build is a hard SQL error, and only if the file has `ON_ERROR_STOP` set (directly, or via `\i test/pgxntool/psql.sql`) -- pgxntool checks for this by default, passing any file that includes `psql.sql` or has its own `\set`/`\unset ON_ERROR_STOP`, whatever the value. If a `test/install/*.sql` file is misbehaving, don't go looking for a diff; check whether it errored, and don't assume a stale-looking `.out` for it means anything.
## Key Implementation Details
diff --git a/HISTORY.asc b/HISTORY.asc
index 3d588c0..1e0b13c 100644
--- a/HISTORY.asc
+++ b/HISTORY.asc
@@ -1,3 +1,25 @@
+STABLE
+------
+== Breaking changes
+
+=== `make test` now fails on pgTap failures in actual output
+`make test` used to fail only on `regression.diffs`, so a `not ok` line
+blessed into an expected file passed. It now also scans
+`test/results/*.out` and the `test/install` actual output, failing on a
+`not ok` without `# TODO`, a missing or duplicate `1..N` plan line, or a
+plan that doesn't match the number of tests run (a test that died partway
+through). Files with no TAP output are not checked. `make results` (via
+`verify-results` in its default `pgtap` mode) gains the same plan checks
+and the `test/install` scan. See `PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT` to
+disable the `make test` side. Part of #106.
+
+== New features
+
+=== Add `PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT`
+Set to `no` to stop `make test` from checking pgTap output (see above).
+`make results` still runs the check in `pgtap` mode; use
+`PGXNTOOL_ENABLE_VERIFY_RESULTS=no` to bypass it there.
+
2.4.0
-----
== Breaking changes
diff --git a/README.asc b/README.asc
index 92f132d..910003f 100644
--- a/README.asc
+++ b/README.asc
@@ -52,7 +52,9 @@ Warns if two `$(DOCS)` entries would install to the same filename, which would o
=== test
Runs your extension's test suite: filesystem-installs the extension (unless `PGXNTOOL_ENABLE_FS_INSTALL=no`) and runs it through PGXS's `installcheck`, first pulling in anything you've hooked into <<_testdeps>> and, if enabled, sanity-checking your test SQL via <<_test_build>>.
-`make test` also fails on two safeguard checks:
+`make test` also fails on these safeguard checks:
+
+- The pgTap output check, run after `pg_regress` finishes: fails if `test/results/*.out` or the <<_testinstall,test/install>> actual output has a `not ok` line without `# TODO`, a missing or duplicate `1..N` plan line, or a different number of tests than planned -- even when the output matches its expected file, so a blessed failure still fails. Files with no TAP output are not checked. See <<_pgxntool_enable_check_tap_output>>.
- The stale-expected-file check, run after `pg_regress` finishes: fails if `test/expected/` (or `test/build/expected/`) has a `.out` file with no matching `.sql`, or a file that isn't `.out`. See <<_pgxntool_enable_check_stale_expected>>.
- When <<_testinstall,test/install>> is in use, the `ON_ERROR_STOP` check: fails if any `test/install/*.sql` file neither includes `test/pgxntool/psql.sql` nor sets `ON_ERROR_STOP` itself. See <<_pgxntool_enable_test_install_error_stop_check>>.
@@ -68,7 +70,7 @@ Whether `test-build` runs is controlled by the `PGXNTOOL_ENABLE_TEST_BUILD` vari
NOTE: `test` intentionally does *not* depend on `clean` — that caused problems with incremental/watch-based builds. If your tests need a clean build to pass, that's a sign of a missing dependency elsewhere rather than something to fix by adding `clean` back.
-NOTE: `test` exits non-zero (after printing `regression.diffs`) if any test fails, so CI and other automation can rely on its exit code.
+NOTE: `test` exits non-zero (after printing any pgTap problems and `regression.diffs`) if any test fails, so CI and other automation can rely on its exit code.
NOTE: While you can still run `make installcheck` or any other valid PGXS make target directly, it's recommended to use `make test` when using pgxntool. The `test` target ensures proper test isolation and correct dependency installation.
@@ -206,7 +208,7 @@ Without `test/install`, each test file typically needs to run `CREATE EXTENSION`
**Key detail:** Install files and regular tests run in a single `pg_regress` invocation. This means the database is NOT dropped between install and test phases — state created by install files persists into the main test suite.
-WARNING: **`+test/install/*.out+` is never actually compared against anything.** Unlike every other test type pgxntool supports, the `test/install` directory's actual output is written to the exact same file as its expected output, so there is no diff — a wrong or changed result will never fail the build, no matter how much it changes. The *only* thing that still fails the build is a hard SQL error, and only if the file sets `ON_ERROR_STOP` (directly, or via `\i test/pgxntool/psql.sql`) — without it, psql prints the error, keeps going, and the file "passes" regardless. pgxntool enforces this by default (`_check-test-install-error-stop`, see `PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK` to disable), but that only guarantees errors are caught — it does not give you real output validation. If you need actual output comparison, put that logic in `test/sql` instead (or use pgTap assertions from within the install file itself).
+WARNING: **`+test/install/*.out+` is never actually compared against anything.** Unlike every other test type pgxntool supports, the `test/install` directory's actual output is written to the exact same file as its expected output, so there is no diff — a wrong or changed result will never fail the build, no matter how much it changes. Apart from pgTap failures (below), the *only* thing that still fails the build is a hard SQL error, and only if the file sets `ON_ERROR_STOP` (directly, or via `\i test/pgxntool/psql.sql`) — without it, psql prints the error, keeps going, and the file "passes" regardless. pgxntool enforces this by default (`_check-test-install-error-stop`, see `PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK` to disable), but that only guarantees errors are caught — it does not give you real output validation. If you need actual output comparison, put that logic in `test/sql` instead, or use pgTap assertions from within the install file itself: its actual output goes through the same pgTap output check as `test/sql` (see <<_pgxntool_enable_check_tap_output>>), so a `not ok` or plan problem there does fail `make test`.
==== Update & Upgrade (U&U) Testing
@@ -274,7 +276,7 @@ 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`, which must be one of these two values (case-insensitive; anything else is a hard `make` error):
-`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.
+`pgtap` (the default):: Scans `test/results/*.out` and the <<_testinstall,test/install>> actual output for pgTAP `not ok` lines (ignoring `# TODO` items), missing or duplicate plan lines, and plan-count mismatches (the same check `make test` runs; it applies here regardless of `PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT`), 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`.
@@ -826,6 +828,10 @@ Default: `yes`. Enables or disables the `_check-stale-expected` safeguard, which
This is also the supported pattern for disabling one specific optional pgxntool feature: a documented `PGXNTOOL_ENABLE_*` variable, gating both the target's registration into `TEST_DEPS`/etc. and (where applicable) the target's own definition. Cleanly replacing or overriding an arbitrary pgxntool-generated recipe with your own entirely is a separate, larger, and not-yet-decided design question -- see https://github.com/Postgres-Extensions/pgxntool/issues/30[issue #30] (two proposed approaches) -- and is *not* what this mechanism solves.
+=== PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT *
+
+Default: `yes`. Enables or disables the pgTap output check that `make test` runs after `pg_regress`: it fails on a `not ok` line without `# TODO`, a missing or duplicate `1..N` plan line, or a plan/test count mismatch in `test/results/*.out` or the <<_testinstall,test/install>> actual output. Files with no TAP output are not checked. This governs only `make test`; the <<_verify_results_safeguard,verify-results safeguard>>'s `pgtap` mode always runs the same check before `make results` (use `PGXNTOOL_ENABLE_VERIFY_RESULTS=no` to bypass it there).
+
=== PGXNTOOL_CHECK_EXPECTED_FILE_TYPES *
Default: `yes`. Sub-check of `_check-stale-expected`, independent of `PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED`: fails (with a distinct error message and exit code from the orphaned-`.out` check) if `test/expected/` (or `test/build/expected/`) contains any file that isn't `*.out`. Set to `no` to disable just this sub-check while leaving the orphaned-`.out` check active.
diff --git a/README.html b/README.html
index 10a92d7..8cea436 100644
--- a/README.html
+++ b/README.html
@@ -518,8 +518,9 @@ PGXNtool
8.14. PGXNTOOL_ENABLE_PGXN_INSTALL *
8.15. PGXNTOOL_ENABLE_VERIFY_RESULTS *
8.16. PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED *
-8.17. PGXNTOOL_CHECK_EXPECTED_FILE_TYPES *
-8.18. PGXNTOOL_NO_PGXS_INCLUDE
+8.17. PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT *
+8.18. PGXNTOOL_CHECK_EXPECTED_FILE_TYPES *
+8.19. PGXNTOOL_NO_PGXS_INCLUDE
9. General PostgreSQL Notes
@@ -633,11 +634,14 @@
-
make test also fails on two safeguard checks:
+
make test also fails on these safeguard checks:
-
+
The pgTap output check, run after pg_regress finishes: fails if test/results/*.out or the test/install actual output has a not ok line without # TODO, a missing or duplicate 1..N plan line, or a different number of tests than planned — even when the output matches its expected file, so a blessed failure still fails. Files with no TAP output are not checked. See PGXNTOOL_ENABLE_CHECK_TAP_OUTPUT *.
+
+-
The stale-expected-file check, run after pg_regress finishes: fails if test/expected/ (or test/build/expected/) has a .out file with no matching .sql, or a file that isn’t .out. See PGXNTOOL_ENABLE_CHECK_STALE_EXPECTED *.
-
@@ -683,7 +687,7 @@
-test exits non-zero (after printing regression.diffs) if any test fails, so CI and other automation can rely on its exit code.
+test exits non-zero (after printing any pgTap problems and regression.diffs) if any test fails, so CI and other automation can rely on its exit code.
|
@@ -926,7 +930,7 @@