From 95e273fab6216acfc5c93afbac828c3486c38be1 Mon Sep 17 00:00:00 2001 From: Utkarash Singh Date: Sat, 6 Jun 2026 00:27:01 +0100 Subject: [PATCH] test(nix): wire pg_isolation_regress into the check harness Adds a way to run PostgreSQL isolation (concurrency) specs as part of the nix check harness, using the stock pg_isolation_regress + isolationtester that already ship in the build (no postgres build changes needed). A trivial placeholder spec proves the harness runs; real specs drop in alongside it. Ref: PSQL-1277 --- nix/checks.nix | 40 ++++++++++++++++++- nix/ext/pg_isolation_regress.nix | 29 ++++++++++++++ nix/packages/default.nix | 8 ++++ .../isolation/expected/sample_isolation.out | 20 ++++++++++ .../isolation/specs/sample_isolation.spec | 31 ++++++++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 nix/ext/pg_isolation_regress.nix create mode 100644 nix/tests/isolation/expected/sample_isolation.out create mode 100644 nix/tests/isolation/specs/sample_isolation.spec diff --git a/nix/checks.nix b/nix/checks.nix index ca5fb99ec0..b328ccb2cf 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -34,7 +34,7 @@ }: let pg_prove = pkgs.perlPackages.TAPParserSourceHandlerpgTAP; - inherit (self'.packages) pg_regress; + inherit (self'.packages) pg_regress pg_isolation_regress; getkey-script = pkgs.stdenv.mkDerivation { name = "pgsodium-getkey"; buildCommand = '' @@ -273,6 +273,11 @@ testList; sortedTestList = builtins.sort (a: b: a < b) filteredTestList; + + # Concurrency/isolation specs run via pg_isolation_regress (the stock + # PostgreSQL isolation tester). Specs live in tests/isolation/specs/, + # expected output in tests/isolation/expected/. Add new spec names here. + isolationSpecList = [ "sample_isolation" ]; in pkgs.writeShellApplication rec { name = "postgres-${pgpkg.version}-check-harness"; @@ -287,6 +292,7 @@ pgpkg pg_prove pg_regress + pg_isolation_regress procps start-postgres-server-bin which @@ -556,6 +562,37 @@ fi log info "pg_regress tests completed successfully" + # Isolation (concurrency) tests via pg_isolation_regress -- the stock + # PostgreSQL isolation tester shipped in the build. Reuses the same + # running server as pg_regress (--use-existing). These specs target + # core/heap + contrib concurrency behaviour. Skipped on: + # - CLI variants: portable build runs only a test subset. + # - orioledb ships its own isolation suite for its storage-engine + # concurrency semantics. + #shellcheck disable=SC2193 + if ${lib.boolToString isCliVariant}; then + log info "CLI variant detected - skipping isolation tests" + elif [[ "${pgpkg.version}" == *"_"* ]]; then + log info "orioledb variant detected - skipping isolation tests (orioledb has its own isolation suite)" + else + log info "Running pg_isolation_regress tests (${builtins.toString (builtins.length isolationSpecList)} specs)" + mkdir -p "$out/isolation_output" + if ! log_cmd pg_isolation_regress \ + --use-existing \ + --dbname=postgres \ + --inputdir=${./tests/isolation} \ + --outputdir="$out/isolation_output" \ + --host=localhost \ + --port=${pgPort} \ + --user=supabase_admin \ + ${builtins.concatStringsSep " " isolationSpecList} 2>&1; then + log error "pg_isolation_regress tests failed" + cat "$out/isolation_output/output_iso/regression.diffs" 2>/dev/null || cat "$out/isolation_output/regression.diffs" 2>/dev/null || true + exit 1 + fi + log info "pg_isolation_regress tests completed successfully" + fi + # Skip migrations tests for CLI variants (they may depend on extensions) if ${lib.boolToString isCliVariant}; then log info "CLI variant detected - skipping migrations tests" @@ -890,6 +927,7 @@ goss image-size-analyzer pg_regress + pg_isolation_regress pg-startup-profiler supabase-cli supascan diff --git a/nix/ext/pg_isolation_regress.nix b/nix/ext/pg_isolation_regress.nix new file mode 100644 index 0000000000..02e0e2289e --- /dev/null +++ b/nix/ext/pg_isolation_regress.nix @@ -0,0 +1,29 @@ +{ + lib, + stdenv, + postgresql, +}: + +stdenv.mkDerivation { + pname = "pg_isolation_regress"; + version = postgresql.version; + + phases = [ "installPhase" ]; + + # pg_isolation_regress and its helper isolationtester are built as part of the + # standard PostgreSQL source tree (src/test/isolation) and ship in the pgxs + # tree. pg_isolation_regress locates isolationtester relative to its own + # binary, so both must live in the same directory. + installPhase = '' + mkdir -p $out/bin + cp ${postgresql}/lib/pgxs/src/test/isolation/pg_isolation_regress $out/bin/ + cp ${postgresql}/lib/pgxs/src/test/isolation/isolationtester $out/bin/ + ''; + + meta = with lib; { + description = "Concurrent-isolation regression testing tool for PostgreSQL"; + homepage = "https://www.postgresql.org/"; + platforms = postgresql.meta.platforms; + license = licenses.postgresql; + }; +} diff --git a/nix/packages/default.nix b/nix/packages/default.nix index 7b1a6ea546..a452a9b784 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -18,6 +18,13 @@ postgresqlPackage = self'.packages."postgresql_${version}"; in pkgs.callPackage ../ext/pg_regress.nix { postgresql = postgresqlPackage; }; + # Function to create the pg_isolation_regress package + makePgIsolationRegress = + version: + let + postgresqlPackage = self'.packages."postgresql_${version}"; + in + pkgs.callPackage ../ext/pg_isolation_regress.nix { postgresql = postgresqlPackage; }; pgsqlSuperuser = "supabase_admin"; supascan-pkgs = pkgs.callPackage ./supascan.nix { inherit (pkgs) lib; @@ -67,6 +74,7 @@ pg-restore = pkgs.callPackage ./pg-restore.nix { psql_15 = self'.packages."psql_15/bin"; }; pg_prove = pkgs.perlPackages.TAPParserSourceHandlerpgTAP; pg_regress = makePgRegress activeVersion; + pg_isolation_regress = makePgIsolationRegress activeVersion; run-testinfra = pkgs.callPackage ./run-testinfra.nix { }; show-commands = pkgs.callPackage ./show-commands.nix { }; start-client = pkgs.callPackage ./start-client.nix { diff --git a/nix/tests/isolation/expected/sample_isolation.out b/nix/tests/isolation/expected/sample_isolation.out new file mode 100644 index 0000000000..28c72466cb --- /dev/null +++ b/nix/tests/isolation/expected/sample_isolation.out @@ -0,0 +1,20 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_begin s1_update s2_begin s2_read s1_commit s2_read s2_commit +step s1_begin: BEGIN; +step s1_update: UPDATE iso_sample SET val = val + 1 WHERE id = 1; +step s2_begin: BEGIN ISOLATION LEVEL READ COMMITTED; +step s2_read: SELECT val FROM iso_sample WHERE id = 1; +val +--- +100 +(1 row) + +step s1_commit: COMMIT; +step s2_read: SELECT val FROM iso_sample WHERE id = 1; +val +--- +101 +(1 row) + +step s2_commit: COMMIT; diff --git a/nix/tests/isolation/specs/sample_isolation.spec b/nix/tests/isolation/specs/sample_isolation.spec new file mode 100644 index 0000000000..4f4ff653de --- /dev/null +++ b/nix/tests/isolation/specs/sample_isolation.spec @@ -0,0 +1,31 @@ +# Sample isolation spec -- proves the pg_isolation_regress harness runs in CI. +# This is not a regression test for any particular fix; it just exercises the +# stock PostgreSQL isolation tester so real concurrency specs (e.g. MERGE +# serialization, postgres_fdw EvalPlanQual) can be dropped in alongside it. +# +# Scenario: session s2 reads a row under READ COMMITTED before and after a +# concurrent UPDATE+COMMIT by session s1 -- the second read observes the +# committed change. No step blocks. + +setup +{ + CREATE TABLE iso_sample (id int PRIMARY KEY, val int); + INSERT INTO iso_sample VALUES (1, 100); +} + +teardown +{ + DROP TABLE iso_sample; +} + +session s1 +step s1_begin { BEGIN; } +step s1_update { UPDATE iso_sample SET val = val + 1 WHERE id = 1; } +step s1_commit { COMMIT; } + +session s2 +step s2_begin { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s2_read { SELECT val FROM iso_sample WHERE id = 1; } +step s2_commit { COMMIT; } + +permutation s1_begin s1_update s2_begin s2_read s1_commit s2_read s2_commit