Skip to content

Commit 25f6fb6

Browse files
committed
feat: use a reworked generic nixos test for wrappers extension
1 parent 99b1a13 commit 25f6fb6

File tree

4 files changed

+286
-162
lines changed

4 files changed

+286
-162
lines changed

nix/checks.nix

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,11 @@
308308
postgresql_17_src
309309
;
310310
}
311-
// pkgs.lib.optionalAttrs (system == "x86_64-linux") {
312-
wrappers = import ./ext/tests/wrappers.nix {
311+
// pkgs.lib.optionalAttrs (system == "x86_64-linux") (
312+
import ./ext/tests {
313313
inherit self;
314314
inherit pkgs;
315-
};
316-
devShell = self'.devShells.default;
317-
};
315+
}
316+
);
318317
};
319318
}

nix/ext/tests/default.nix

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
{ self, pkgs }:
2+
let
3+
testsDir = ./.;
4+
testFiles = builtins.attrNames (builtins.readDir testsDir);
5+
nixFiles = builtins.filter (
6+
name: builtins.match ".*\\.nix$" name != null && name != "default.nix"
7+
) testFiles;
8+
extTest =
9+
extension_name:
10+
let
11+
pname = extension_name;
12+
inherit (pkgs) lib;
13+
installedExtension =
14+
postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all";
15+
versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions;
16+
postgresqlWithExtension =
17+
postgresql:
18+
let
19+
majorVersion = lib.versions.major postgresql.version;
20+
pkg = pkgs.buildEnv {
21+
name = "postgresql-${majorVersion}-${pname}";
22+
paths = [
23+
postgresql
24+
postgresql.lib
25+
(installedExtension majorVersion)
26+
];
27+
passthru = {
28+
inherit (postgresql) version psqlSchema;
29+
lib = pkg;
30+
withPackages = _: pkg;
31+
};
32+
nativeBuildInputs = [ pkgs.makeWrapper ];
33+
pathsToLink = [
34+
"/"
35+
"/bin"
36+
"/lib"
37+
];
38+
postBuild = ''
39+
wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
40+
wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib
41+
wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib
42+
'';
43+
};
44+
in
45+
pkg;
46+
in
47+
self.inputs.nixpkgs.lib.nixos.runTest {
48+
name = pname;
49+
hostPkgs = pkgs;
50+
nodes.server =
51+
{ config, ... }:
52+
{
53+
virtualisation = {
54+
forwardPorts = [
55+
{
56+
from = "host";
57+
host.port = 13022;
58+
guest.port = 22;
59+
}
60+
];
61+
};
62+
services.openssh = {
63+
enable = true;
64+
};
65+
66+
services.postgresql = {
67+
enable = true;
68+
package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
69+
enableTCPIP = true;
70+
initialScript = pkgs.writeText "init-postgres-with-password" ''
71+
CREATE USER test WITH PASSWORD 'secret';
72+
'';
73+
authentication = ''
74+
host test postgres samenet scram-sha-256
75+
'';
76+
};
77+
78+
networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ];
79+
80+
specialisation.postgresql17.configuration = {
81+
services.postgresql = {
82+
package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17);
83+
};
84+
85+
systemd.services.postgresql-migrate = {
86+
serviceConfig = {
87+
Type = "oneshot";
88+
RemainAfterExit = true;
89+
User = "postgres";
90+
Group = "postgres";
91+
StateDirectory = "postgresql";
92+
WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}";
93+
};
94+
script =
95+
let
96+
oldPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
97+
newPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
98+
oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}";
99+
newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}";
100+
in
101+
''
102+
if [[ ! -d ${newDataDir} ]]; then
103+
install -d -m 0700 -o postgres -g postgres "${newDataDir}"
104+
${newPostgresql}/bin/initdb -D "${newDataDir}"
105+
${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \
106+
--old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin"
107+
else
108+
echo "${newDataDir} already exists"
109+
fi
110+
'';
111+
};
112+
113+
systemd.services.postgresql = {
114+
after = [ "postgresql-migrate.service" ];
115+
requires = [ "postgresql-migrate.service" ];
116+
};
117+
};
118+
};
119+
testScript =
120+
{ nodes, ... }:
121+
let
122+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
123+
in
124+
''
125+
versions = {
126+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
127+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
128+
}
129+
extension_name = "${pname}"
130+
support_upgrade = True
131+
pg17_configuration = "${pg17-configuration}"
132+
133+
${builtins.readFile ./lib.py}
134+
135+
start_all()
136+
137+
server.wait_for_unit("multi-user.target")
138+
server.wait_for_unit("postgresql.service")
139+
140+
test = PostgresExtensionTest(server, extension_name, versions, support_upgrade)
141+
142+
with subtest("Check upgrade path with postgresql 15"):
143+
test.check_upgrade_path("15")
144+
145+
last_version = None
146+
with subtest("Check the install of the last version of the extension"):
147+
last_version = test.check_install_last_version("15")
148+
149+
with subtest("switch to postgresql 17"):
150+
server.succeed(
151+
f"{pg17_configuration}/bin/switch-to-configuration test >&2"
152+
)
153+
154+
with subtest("Check last version of the extension after upgrade"):
155+
test.assert_version_matches(last_version)
156+
157+
with subtest("Check upgrade path with postgresql 17"):
158+
test.check_upgrade_path("17")
159+
'';
160+
};
161+
in
162+
builtins.listToAttrs (
163+
map (file: {
164+
name = "ext-" + builtins.replaceStrings [ ".nix" ] [ "" ] file;
165+
value = import (testsDir + "/${file}") { inherit self pkgs; };
166+
}) nixFiles
167+
)
168+
// builtins.listToAttrs (
169+
map (extName: {
170+
name = "ext-${extName}";
171+
value = extTest extName;
172+
}) [ "wrappers" ]
173+
)

nix/ext/tests/lib.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""PostgreSQL extension testing framework for multi-version compatibility.
2+
3+
This module provides a test framework for PostgreSQL extensions that need to be
4+
tested across multiple PostgreSQL versions and extension versions. It handles
5+
installation, upgrades, and version verification of PostgreSQL extensions.
6+
"""
7+
8+
from typing import Sequence, Mapping
9+
from test_driver.machine import Machine
10+
11+
Versions = Mapping[str, Sequence[str]]
12+
13+
class PostgresExtensionTest(object):
14+
def __init__(self, vm: Machine, extension_name: str, versions: Versions, support_upgrade: bool = True):
15+
"""Initialize the PostgreSQL extension test framework.
16+
17+
Args:
18+
vm: Test machine instance for executing commands
19+
extension_name: Name of the PostgreSQL extension to test
20+
versions: Mapping of PostgreSQL versions to available extension versions
21+
support_upgrade: Whether the extension supports in-place upgrades
22+
"""
23+
self.vm = vm
24+
self.extension_name = extension_name
25+
self.versions = versions
26+
self.support_upgrade = support_upgrade
27+
28+
def run_sql(self, query: str) -> str:
29+
return self.vm.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip()
30+
31+
def drop_extension(self):
32+
self.run_sql(f"DROP EXTENSION IF EXISTS {self.extension_name};")
33+
34+
def install_extension(self, version: str):
35+
self.run_sql(f"""CREATE EXTENSION {self.extension_name} WITH VERSION '{version}' CASCADE;""")
36+
# Verify version was installed correctly
37+
self.assert_version_matches(version)
38+
39+
def update_extension(self, version: str):
40+
self.run_sql(f"""ALTER EXTENSION {self.extension_name} UPDATE TO '{version}';""")
41+
# Verify version was installed correctly
42+
self.assert_version_matches(version)
43+
44+
def get_installed_version(self) -> str:
45+
"""Get the currently installed version of the extension.
46+
47+
Returns:
48+
Version string of the currently installed extension,
49+
or empty string if extension is not installed
50+
"""
51+
return self.run_sql(f"""SELECT extversion FROM pg_extension WHERE extname = '{self.extension_name}';""")
52+
53+
def assert_version_matches(self, expected_version: str):
54+
"""Check if the installed version matches the expected version.
55+
56+
Args:
57+
expected_version: Expected version string to verify against
58+
59+
Raises:
60+
AssertionError: If the installed version does not match the expected version
61+
"""
62+
installed_version = self.get_installed_version()
63+
assert installed_version == expected_version, f"Expected version {expected_version}, but found {installed_version}"
64+
65+
def check_upgrade_path(self, pg_version):
66+
"""Test the complete upgrade path for a PostgreSQL version.
67+
68+
This method tests all available extension versions for a given PostgreSQL
69+
version, either through in-place upgrades or reinstallation depending on
70+
the support_upgrade setting.
71+
72+
Args:
73+
pg_version: PostgreSQL version to test (e.g., "14", "15")
74+
75+
Raises:
76+
ValueError: If no versions are available for the specified PostgreSQL version
77+
AssertionError: If version installation or upgrade fails
78+
"""
79+
available_versions = self.versions.get(pg_version, [])
80+
if not available_versions:
81+
raise ValueError(f"No versions available for PostgreSQL version {pg_version}")
82+
83+
# Install and verify first version
84+
firstVersion = available_versions[0]
85+
self.drop_extension()
86+
self.install_extension(firstVersion)
87+
88+
# Test remaining versions
89+
for version in available_versions[1:]:
90+
if self.support_upgrade:
91+
self.update_extension(version)
92+
else:
93+
self.drop_extension()
94+
self.install_extension(version)
95+
96+
97+
def check_install_last_version(self, pg_version: str) -> str:
98+
"""Test if the install of the last version of the extension works for a given PostgreSQL version.
99+
100+
Args:
101+
pg_version: PostgreSQL version to check (e.g., "14", "15")
102+
"""
103+
available_versions = self.versions.get(pg_version, [])
104+
if not available_versions:
105+
raise ValueError(f"No versions available for PostgreSQL version {pg_version}")
106+
last_version = available_versions[-1]
107+
self.drop_extension()
108+
self.install_extension(last_version)
109+
return last_version

0 commit comments

Comments
 (0)