diff --git a/template/test/install/create_install_marker.out b/template/test/install/create_install_marker.out deleted file mode 100644 index 9f0dea5..0000000 --- a/template/test/install/create_install_marker.out +++ /dev/null @@ -1 +0,0 @@ -CREATE TABLE pgxntool_install_marker AS SELECT 'alive'::text AS marker; diff --git a/template/test/install/create_install_marker.sql b/template/test/install/create_install_marker.sql index 9f0dea5..ad8fa03 100644 --- a/template/test/install/create_install_marker.sql +++ b/template/test/install/create_install_marker.sql @@ -1 +1,2 @@ +\set ON_ERROR_STOP on CREATE TABLE pgxntool_install_marker AS SELECT 'alive'::text AS marker; diff --git a/test/lib/dist-expected-files.txt b/test/lib/dist-expected-files.txt index 3726f89..f3b9a31 100644 --- a/test/lib/dist-expected-files.txt +++ b/test/lib/dist-expected-files.txt @@ -54,7 +54,6 @@ test/expected/pgxntool-test.out test/expected/verify_install_marker.out test/install/ test/install/.gitignore -test/install/create_install_marker.out test/install/create_install_marker.sql test/pgxntool test/sql/ @@ -90,6 +89,7 @@ pgxntool/WHAT_IS_THIS pgxntool/test/ pgxntool/test/bin/ pgxntool/test/bin/check-stale-expected.sh +pgxntool/test/bin/check-test-install-error-stop.sh pgxntool/test/deps.sql pgxntool/test/pgxntool/ pgxntool/test/pgxntool/finish.sql diff --git a/test/standard/check-test-install-error-stop-script.bats b/test/standard/check-test-install-error-stop-script.bats new file mode 100644 index 0000000..be5f8a9 --- /dev/null +++ b/test/standard/check-test-install-error-stop-script.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats + +# Test: check-test-install-error-stop.sh - pure script-logic unit tests +# +# These tests exercise test/bin/check-test-install-error-stop.sh directly +# against a bare scratch directory -- no foundation environment, no `make`, +# no PostgreSQL. They cover the script's own pass/fail logic: direct +# ON_ERROR_STOP, sourcing test/pgxntool/psql.sql, and files with neither. +# +# Tests that need real Make integration (TEST_DEPS wiring, +# PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK=no skipping the target) stay +# in make-test.bats. + +load ../lib/helpers +load ../lib/assertions + +setup_file() { + setup_topdir + load_test_env "check-test-install-error-stop-script" +} + +setup() { + load_test_env "check-test-install-error-stop-script" + export SCRIPT="$PGXNREPO/test/bin/check-test-install-error-stop.sh" + + # Fresh, empty scratch directory per test -- no foundation/TEST_REPO needed. + export TESTDIR="$BATS_TEST_TMPDIR/testdir" + mkdir -p "$TESTDIR/install" +} + +@test "check-test-install-error-stop.sh: passes when a file sets ON_ERROR_STOP directly" { + printf '\\set ON_ERROR_STOP on\nCREATE TABLE foo AS SELECT 1;\n' > "$TESTDIR/install/foo.sql" + + run "$SCRIPT" "$TESTDIR" + assert_success +} + +@test "check-test-install-error-stop.sh: passes when a file sources test/pgxntool/psql.sql" { + printf '\\i test/pgxntool/psql.sql\nCREATE TABLE foo AS SELECT 1;\n' > "$TESTDIR/install/foo.sql" + + run "$SCRIPT" "$TESTDIR" + assert_success +} + +@test "check-test-install-error-stop.sh: fails when a file has neither" { + printf 'CREATE TABLE foo AS SELECT 1;\n' > "$TESTDIR/install/foo.sql" + + run "$SCRIPT" "$TESTDIR" + assert_failure_with_status 1 + assert_contains "$output" "foo.sql" + assert_contains "$output" "ON_ERROR_STOP" +} + +@test "check-test-install-error-stop.sh: reports every offending file, not just the first" { + printf 'CREATE TABLE foo AS SELECT 1;\n' > "$TESTDIR/install/foo.sql" + printf 'CREATE TABLE bar AS SELECT 1;\n' > "$TESTDIR/install/bar.sql" + printf '\\set ON_ERROR_STOP on\nCREATE TABLE baz AS SELECT 1;\n' > "$TESTDIR/install/baz.sql" + + run "$SCRIPT" "$TESTDIR" + assert_failure_with_status 1 + assert_contains "$output" "foo.sql" + assert_contains "$output" "bar.sql" +} + +@test "check-test-install-error-stop.sh: passes on an empty test/install/ directory" { + run "$SCRIPT" "$TESTDIR" + assert_success +} + +@test "check-test-install-error-stop.sh: requires exactly one argument" { + run "$SCRIPT" + assert_failure + + run "$SCRIPT" "$TESTDIR" extra + assert_failure +} diff --git a/test/standard/make-test.bats b/test/standard/make-test.bats index 4b93eb5..e177bda 100755 --- a/test/standard/make-test.bats +++ b/test/standard/make-test.bats @@ -193,6 +193,41 @@ EOF assert_not_contains "$output" "does not exist" } +# Test: test-build must gate the full suite, not run after it (issue #108) +# +# TEST_DEPS lists `check-stale-expected test-build install installcheck` as +# independent, unordered prerequisites of `test` (see the issue #79 section +# above for why position in TEST_DEPS is not an ordering guarantee). +# check-stale-expected's own `check-stale-expected: installcheck` edge pulls +# in installcheck (and, via `installcheck: install`, install) while Make is +# still resolving check-stale-expected -- before Make ever reaches the +# separately-listed `test-build` prerequisite. So the full pg_regress suite +# actually runs before test-build's own sanity check, even though test-build +# exists to catch build errors before the full suite runs. An explicit +# `installcheck: test-build` edge (added only when +# PGXNTOOL_ENABLE_TEST_BUILD=yes) is the fix, mirroring the `installcheck: +# install` and `check-stale-expected: installcheck` edges already in place. + +@test "test-build's recipe runs before installcheck's pg_regress invocation (issue #108)" { + # No PostgreSQL needed: the recipe order in `make -n test` output is enough + # to prove the dependency edge is (or isn't) real, same approach as the + # check-stale-expected ordering test below. + run make -n test PGXNTOOL_ENABLE_TEST_BUILD=yes + assert_success + + local build_line pg_regress_line + build_line=$(echo "$output" | grep -n "run-test-build.sh" | head -1 | cut -d: -f1) + # Exclude test-build's own pg_regress invocation (identified by + # --outputdir=test/build, same convention as the check-stale-expected + # ordering test below) to isolate the main suite's invocation. + pg_regress_line=$(echo "$output" | grep -n "pg_regress " | grep -v -- '--outputdir=test/build' | tail -1 | cut -d: -f1) + + [ -n "$build_line" ] || error "run-test-build.sh invocation not found in 'make -n test' output" + [ -n "$pg_regress_line" ] || error "no pg_regress invocation found in 'make -n test' output" + [ "$build_line" -lt "$pg_regress_line" ] || \ + error "test-build (dry-run line $build_line) must come before installcheck's pg_regress (line $pg_regress_line) -- test-build is meant to gate the full suite, not run after it" +} + # Test: check-stale-expected (issue #14) # # `make test` never caught a stale test/expected/*.out left behind after a @@ -333,6 +368,60 @@ EOF assert_success } +# Test: check-test-install-error-stop (issue #97) +# +# test/install/*.sql files never get a real diff (see the IMPORTANT note in +# base.mk's test/install section) -- ON_ERROR_STOP is the only thing that +# still turns a hard error into a build failure. check-test-install-error-stop +# is a pure static scan enforcing that every test/install/*.sql file sets it. +# The script's own pass/fail decision logic is covered directly in +# check-test-install-error-stop-script.bats; this file covers only base.mk's +# wiring: does `make test` actually invoke the script, and can it be disabled. + +@test "check-test-install-error-stop passes on clean template state" { + # End-to-end smoke check that the real template stays in the passing state + # CLAUDE.md's Template Requirements section requires, through the real + # recipe and real script. + run make check-test-install-error-stop + assert_success +} + +@test "check-test-install-error-stop recipe invokes the script with TESTDIR" { + run make -n check-test-install-error-stop + assert_success + assert_contains "$output" "test/bin/check-test-install-error-stop.sh test" +} + +@test "PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK=no: make test never invokes the script" { + skip_if_no_postgres + + # Same proof pattern as check-stale-expected's disable test above: point + # _CHECK_TEST_INSTALL_ERROR_STOP_SCRIPT at a stub that only touches a + # marker file and fails. If the marker never appears, the script was + # genuinely never invoked, not merely tolerated. + local marker="$BATS_TEST_TMPDIR/check-test-install-error-stop-invoked" + local stub_script + stub_script=$(make_stub_script check-test-install-error-stop-stub 1 "" "$marker") + + run make test PGXNTOOL_ENABLE_TEST_INSTALL_ERROR_STOP_CHECK=no _CHECK_TEST_INSTALL_ERROR_STOP_SCRIPT="$stub_script" + assert_success + assert_file_not_exists "$marker" +} + +@test "make correctly propagates check-test-install-error-stop.sh's exit status and output" { + local stub_script + stub_script=$(make_stub_script fail-stub 5 "STUB SENTINEL MESSAGE") + + run make check-test-install-error-stop _CHECK_TEST_INSTALL_ERROR_STOP_SCRIPT="$stub_script" + assert_failure + assert_contains "$output" "STUB SENTINEL MESSAGE" + + stub_script=$(make_stub_script pass-stub 0) + + run make check-test-install-error-stop _CHECK_TEST_INSTALL_ERROR_STOP_SCRIPT="$stub_script" + assert_success +} + # ============================================================================ # verify-results / make results (issues #14, #49) # ============================================================================ @@ -490,4 +579,61 @@ EOF rm -f test/results/pgtap_plan.out } +# ============================================================================ +# build-results (issue #108) +# ============================================================================ +# +# build-results mirrors `make results`, but for test-build's separate +# pg_regress pass: bless test/build/'s actual output as the new expected +# output. Unlike `make results`, it refuses to bless any file whose actual +# output contains an "ERROR:" line -- see base.mk's build-results comment +# for why that's essential (blessing an errored build as the new baseline +# would defeat the entire point of test-build gating the main suite). + +@test "build-results refreshes a stale test/build/expected/*.out" { + skip_if_no_postgres + + # Corrupt the committed expected output (stale, not a real build break). + echo "-- deliberately stale expected output" >> test/build/expected/build_check.out + + run git status --porcelain test/build/expected/build_check.out + [ -n "$output" ] || error "corruption didn't register as a git modification" + + run make build-results + assert_success + + # The template's build_check.sql is deterministic, so a correct refresh + # must land back on exactly the committed content. + run git status --porcelain test/build/expected/build_check.out + [ -z "$output" ] || error "build_check.out doesn't match the committed baseline after build-results: $output" +} + +@test "build-results skips a file whose actual output contains ERROR:, but still refreshes clean files" { + skip_if_no_postgres + + # Re-corrupt build_check.out (clean, no ERROR) so this test can prove it + # still gets refreshed even though a *different* file in the same run + # fails and blocks the overall exit status. + echo "-- deliberately stale expected output" >> test/build/expected/build_check.out + + # Force simple_build_test.sql to error. + echo "SELECT * FROM definitely_nonexistent_table;" >> test/build/simple_build_test.sql + + run make build-results + assert_failure + assert_contains "$output" "simple_build_test.out" + assert_contains "$output" "cp test/build/results/simple_build_test.out test/build/expected/simple_build_test.out" + + # The clean file still got refreshed despite the other file's skip. + run git status --porcelain test/build/expected/build_check.out + [ -z "$output" ] || error "build_check.out should have been refreshed despite simple_build_test.sql's failure: $output" + + # The errored file's expected output was left untouched, not blessed with + # error content. + run git status --porcelain test/build/expected/simple_build_test.out + [ -z "$output" ] || error "simple_build_test.out should have been left untouched, not blessed: $output" + + git checkout -- test/build/simple_build_test.sql +} + # vi: expandtab sw=2 ts=2