Skip to content

Commit a6eec87

Browse files
committed
feat: support multiple versions of the pg_tap extension
Build multiple versions of the pg_tap extension on different PostgreSQL versions. Add test for the extensions and their upgrade on PostgreSQL 15 and 17.
1 parent c57842f commit a6eec87

File tree

7 files changed

+319
-33
lines changed

7 files changed

+319
-33
lines changed

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,8 @@
13841384
devShell = devShells.default;
13851385
} // pkgs.lib.optionalAttrs (system == "aarch64-linux") {
13861386
inherit (basePackages) postgresql_15_debug postgresql_15_src postgresql_orioledb-17_debug postgresql_orioledb-17_src postgresql_17_debug postgresql_17_src;
1387+
} // pkgs.lib.optionalAttrs (system == "x86_64-linux") {
1388+
pgtap = import ./nix/ext/tests/pgtap.nix { inherit self; inherit pkgs; };
13871389
};
13881390

13891391
# Apps is a list of names of things that can be executed with 'nix run';

nix/ext/pgtap.nix

Lines changed: 117 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,122 @@
1-
{ lib, stdenv, fetchFromGitHub, postgresql, perl, perlPackages, which }:
2-
3-
stdenv.mkDerivation rec {
1+
{
2+
lib,
3+
stdenv,
4+
fetchFromGitHub,
5+
postgresql,
6+
perl,
7+
perlPackages,
8+
which,
9+
buildEnv,
10+
fetchpatch2,
11+
}:
12+
let
413
pname = "pgtap";
5-
version = "1.2.0";
614

7-
src = fetchFromGitHub {
8-
owner = "theory";
9-
repo = "pgtap";
10-
rev = "v${version}";
11-
hash = "sha256-lb0PRffwo6J5a6Hqw1ggvn0cW7gPZ02OEcLPi9ineI8=";
12-
};
15+
# Load version configuration from external file
16+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
17+
18+
# Filter versions compatible with current PostgreSQL version
19+
supportedVersions = lib.filterAttrs (
20+
_: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql
21+
) allVersions;
22+
23+
# Derived version information
24+
versions = lib.naturalSort (lib.attrNames supportedVersions);
25+
latestVersion = lib.last versions;
26+
numberOfVersions = builtins.length versions;
27+
packages = builtins.attrValues (
28+
lib.mapAttrs (name: value: build name value.hash) supportedVersions
29+
);
30+
repoOwner = "theory";
31+
repo = "${repoOwner}/${pname}";
32+
33+
# Build function for individual versions
34+
build =
35+
version: hash:
36+
stdenv.mkDerivation rec {
37+
inherit pname version;
38+
39+
src = fetchFromGitHub {
40+
owner = repoOwner;
41+
repo = pname;
42+
rev = "v${version}";
43+
inherit hash;
44+
};
45+
46+
nativeBuildInputs = [
47+
postgresql
48+
perl
49+
perlPackages.TAPParserSourceHandlerpgTAP
50+
which
51+
];
52+
53+
patches = lib.optionals (version == "1.3.3") [
54+
# Fix error in upgrade script from 1.2.0 to 1.3.3
55+
(fetchpatch2 {
56+
name = "pgtap-fix-upgrade-from-1.2.0-to-1.3.3.patch";
57+
url = "https://github.com/${repoOwner}/${pname}/pull/338.diff?full_index=1";
58+
hash = "sha256-AVRQyqCGoc0gcoMRWBJKMmUBjadGtWg7rvHmTq5rRpw=";
59+
})
60+
];
61+
62+
installPhase = ''
63+
runHook preInstall
64+
65+
mkdir -p $out/{lib,share/postgresql/extension}
66+
67+
# Create version-specific control file
68+
sed -e "/^default_version =/d" \
69+
-e "s|^module_pathname = .*|module_pathname = '$ext'|" \
70+
${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control
71+
72+
# Copy SQL file to install the specific version
73+
cp sql/${pname}--${version}.sql $out/share/postgresql/extension
74+
75+
if [[ -f src/pgtap.so ]]; then
76+
# Install the shared library with version suffix
77+
install -Dm755 src/pgtap.so $out/lib/${pname}-${version}${postgresql.dlSuffix}
78+
fi
79+
80+
# For the latest version, create default control file and symlink and copy SQL upgrade scripts
81+
if [[ "${version}" == "${latestVersion}" ]]; then
82+
{
83+
echo "default_version = '${version}'"
84+
cat $out/share/postgresql/extension/${pname}--${version}.control
85+
} > $out/share/postgresql/extension/${pname}.control
86+
cp sql/${pname}--*--*.sql $out/share/postgresql/extension
87+
elif [[ "${version}" == "1.3.1" ]]; then
88+
# 1.3.1 is the first and only version with a C extension
89+
ln -sfn ${pname}-${version}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix}
90+
fi
91+
'';
92+
93+
meta = with lib; {
94+
description = "A unit testing framework for PostgreSQL";
95+
longDescription = ''
96+
pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL.
97+
It includes a comprehensive collection of TAP-emitting assertion functions,
98+
as well as the ability to integrate with other TAP-emitting test frameworks.
99+
It can also be used in the xUnit testing style.
100+
'';
101+
homepage = "https://pgtap.org";
102+
inherit (postgresql.meta) platforms;
103+
license = licenses.mit;
104+
};
105+
};
106+
in
107+
buildEnv {
108+
name = pname;
109+
paths = packages;
110+
111+
pathsToLink = [
112+
"/lib"
113+
"/share/postgresql/extension"
114+
];
13115

14-
nativeBuildInputs = [ postgresql perl perlPackages.TAPParserSourceHandlerpgTAP which ];
15-
16-
installPhase = ''
17-
install -D {sql/pgtap--${version}.sql,pgtap.control} -t $out/share/postgresql/extension
18-
'';
19-
20-
meta = with lib; {
21-
description = "A unit testing framework for PostgreSQL";
22-
longDescription = ''
23-
pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL.
24-
It includes a comprehensive collection of TAP-emitting assertion functions,
25-
as well as the ability to integrate with other TAP-emitting test frameworks.
26-
It can also be used in the xUnit testing style.
27-
'';
28-
homepage = "https://pgtap.org";
29-
inherit (postgresql.meta) platforms;
30-
license = licenses.mit;
116+
passthru = {
117+
inherit versions numberOfVersions;
118+
pname = "${pname}-all";
119+
version =
120+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
31121
};
32122
}

nix/ext/tests/pgtap.nix

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{ self, pkgs }:
2+
let
3+
pname = "pgtap";
4+
inherit (pkgs) lib;
5+
installedExtension =
6+
postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all";
7+
versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions;
8+
postgresqlWithExtension =
9+
postgresql:
10+
let
11+
majorVersion = lib.versions.major postgresql.version;
12+
pkg = pkgs.buildEnv {
13+
name = "postgresql-${majorVersion}-${pname}";
14+
paths = [
15+
postgresql
16+
postgresql.lib
17+
(installedExtension majorVersion)
18+
];
19+
passthru = {
20+
inherit (postgresql) version psqlSchema;
21+
lib = pkg;
22+
withPackages = _: pkg;
23+
};
24+
nativeBuildInputs = [ pkgs.makeWrapper ];
25+
pathsToLink = [
26+
"/"
27+
"/bin"
28+
"/lib"
29+
];
30+
postBuild = ''
31+
wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
32+
wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib
33+
wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib
34+
'';
35+
};
36+
in
37+
pkg;
38+
in
39+
self.inputs.nixpkgs.lib.nixos.runTest {
40+
name = pname;
41+
hostPkgs = pkgs;
42+
nodes.server =
43+
{ config, ... }:
44+
{
45+
virtualisation = {
46+
forwardPorts = [
47+
{
48+
from = "host";
49+
host.port = 13022;
50+
guest.port = 22;
51+
}
52+
];
53+
};
54+
services.openssh = {
55+
enable = true;
56+
};
57+
58+
services.postgresql = {
59+
enable = true;
60+
package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
61+
};
62+
63+
specialisation.postgresql17.configuration = {
64+
services.postgresql = {
65+
package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17);
66+
};
67+
68+
systemd.services.postgresql-migrate = {
69+
serviceConfig = {
70+
Type = "oneshot";
71+
RemainAfterExit = true;
72+
User = "postgres";
73+
Group = "postgres";
74+
StateDirectory = "postgresql";
75+
WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}";
76+
};
77+
script =
78+
let
79+
oldPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
80+
newPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
81+
oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}";
82+
newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}";
83+
in
84+
''
85+
if [[ ! -d ${newDataDir} ]]; then
86+
install -d -m 0700 -o postgres -g postgres "${newDataDir}"
87+
${newPostgresql}/bin/initdb -D "${newDataDir}"
88+
${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \
89+
--old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin"
90+
else
91+
echo "${newDataDir} already exists"
92+
fi
93+
'';
94+
};
95+
96+
systemd.services.postgresql = {
97+
after = [ "postgresql-migrate.service" ];
98+
requires = [ "postgresql-migrate.service" ];
99+
};
100+
};
101+
102+
};
103+
testScript =
104+
{ nodes, ... }:
105+
let
106+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
107+
in
108+
''
109+
versions = {
110+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
111+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
112+
}
113+
114+
def run_sql(query):
115+
return server.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip()
116+
117+
def check_upgrade_path(pg_version):
118+
with subtest("Check ${pname} upgrade path"):
119+
firstVersion = versions[pg_version][0]
120+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'")
121+
run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{firstVersion}' CASCADE;""")
122+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
123+
assert installed_version == firstVersion, f"Expected ${pname} version {firstVersion}, but found {installed_version}"
124+
for version in versions[pg_version][1:]:
125+
run_sql(f"""ALTER EXTENSION ${pname} UPDATE TO '{version}';""")
126+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
127+
assert installed_version == version, f"Expected ${pname} version {version}, but found {installed_version}"
128+
129+
start_all()
130+
131+
server.wait_for_unit("multi-user.target")
132+
server.wait_for_unit("postgresql.service")
133+
134+
check_upgrade_path("15")
135+
136+
with subtest("Check ${pname} latest extension version"):
137+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION ${pname};'")
138+
server.succeed("sudo -u postgres psql -c 'CREATE EXTENSION ${pname} CASCADE;'")
139+
installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""")
140+
latestVersion = versions["15"][-1]
141+
assert f"${pname},{latestVersion}" in installed_extensions
142+
143+
with subtest("switch to postgresql 17"):
144+
server.succeed(
145+
"${pg17-configuration}/bin/switch-to-configuration test >&2"
146+
)
147+
148+
with subtest("Check ${pname} latest extension version after upgrade"):
149+
installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""")
150+
latestVersion = versions["17"][-1]
151+
assert f"${pname},{latestVersion}" in installed_extensions
152+
153+
check_upgrade_path("17")
154+
'';
155+
}

nix/ext/versions.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"pgtap": {
3+
"1.2.0": {
4+
"postgresql": [
5+
"15"
6+
],
7+
"hash": "sha256-lb0PRffwo6J5a6Hqw1ggvn0cW7gPZ02OEcLPi9ineI8="
8+
},
9+
"1.3.1": {
10+
"postgresql": [
11+
"15",
12+
"17"
13+
],
14+
"hash": "sha256-HOgCb1CCfsfbMbMMWuzFJ4B8CfVm9b0sI2zBY3/kqyI="
15+
},
16+
"1.3.3": {
17+
"postgresql": [
18+
"15",
19+
"17"
20+
],
21+
"hash": "sha256-YgvfLGF7pLVcCKD66NnWAydDxtoYHH1DpLiYTEKHJ0E="
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)