diff --git a/flake.nix b/flake.nix index 18cd9ba13..da9a1196d 100644 --- a/flake.nix +++ b/flake.nix @@ -1384,6 +1384,8 @@ devShell = devShells.default; } // pkgs.lib.optionalAttrs (system == "aarch64-linux") { inherit (basePackages) postgresql_15_debug postgresql_15_src postgresql_orioledb-17_debug postgresql_orioledb-17_src postgresql_17_debug postgresql_17_src; + } // pkgs.lib.optionalAttrs (system == "x86_64-linux") { + pgtap = import ./nix/ext/tests/pgtap.nix { inherit self; inherit pkgs; }; }; # Apps is a list of names of things that can be executed with 'nix run'; diff --git a/nix/ext/pgtap.nix b/nix/ext/pgtap.nix index 159fec433..037498c29 100644 --- a/nix/ext/pgtap.nix +++ b/nix/ext/pgtap.nix @@ -1,32 +1,122 @@ -{ lib, stdenv, fetchFromGitHub, postgresql, perl, perlPackages, which }: - -stdenv.mkDerivation rec { +{ + lib, + stdenv, + fetchFromGitHub, + postgresql, + perl, + perlPackages, + which, + buildEnv, + fetchpatch2, +}: +let pname = "pgtap"; - version = "1.2.0"; - src = fetchFromGitHub { - owner = "theory"; - repo = "pgtap"; - rev = "v${version}"; - hash = "sha256-lb0PRffwo6J5a6Hqw1ggvn0cW7gPZ02OEcLPi9ineI8="; - }; + # Load version configuration from external file + allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname}; + + # Filter versions compatible with current PostgreSQL version + supportedVersions = lib.filterAttrs ( + _: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql + ) allVersions; + + # Derived version information + versions = lib.naturalSort (lib.attrNames supportedVersions); + latestVersion = lib.last versions; + numberOfVersions = builtins.length versions; + packages = builtins.attrValues ( + lib.mapAttrs (name: value: build name value.hash) supportedVersions + ); + repoOwner = "theory"; + repo = "${repoOwner}/${pname}"; + + # Build function for individual versions + build = + version: hash: + stdenv.mkDerivation rec { + inherit pname version; + + src = fetchFromGitHub { + owner = repoOwner; + repo = pname; + rev = "v${version}"; + inherit hash; + }; + + nativeBuildInputs = [ + postgresql + perl + perlPackages.TAPParserSourceHandlerpgTAP + which + ]; + + patches = lib.optionals (version == "1.3.3") [ + # Fix error in upgrade script from 1.2.0 to 1.3.3 + (fetchpatch2 { + name = "pgtap-fix-upgrade-from-1.2.0-to-1.3.3.patch"; + url = "https://github.com/${repoOwner}/${pname}/pull/338.diff?full_index=1"; + hash = "sha256-AVRQyqCGoc0gcoMRWBJKMmUBjadGtWg7rvHmTq5rRpw="; + }) + ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/{lib,share/postgresql/extension} + + # Create version-specific control file + sed -e "/^default_version =/d" \ + -e "s|^module_pathname = .*|module_pathname = '$ext'|" \ + ${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control + + # Copy SQL file to install the specific version + cp sql/${pname}--${version}.sql $out/share/postgresql/extension + + if [[ -f src/pgtap.so ]]; then + # Install the shared library with version suffix + install -Dm755 src/pgtap.so $out/lib/${pname}-${version}${postgresql.dlSuffix} + fi + + # For the latest version, create default control file and symlink and copy SQL upgrade scripts + if [[ "${version}" == "${latestVersion}" ]]; then + { + echo "default_version = '${version}'" + cat $out/share/postgresql/extension/${pname}--${version}.control + } > $out/share/postgresql/extension/${pname}.control + cp sql/${pname}--*--*.sql $out/share/postgresql/extension + elif [[ "${version}" == "1.3.1" ]]; then + # 1.3.1 is the first and only version with a C extension + ln -sfn ${pname}-${version}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix} + fi + ''; + + meta = with lib; { + description = "A unit testing framework for PostgreSQL"; + longDescription = '' + pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. + It includes a comprehensive collection of TAP-emitting assertion functions, + as well as the ability to integrate with other TAP-emitting test frameworks. + It can also be used in the xUnit testing style. + ''; + homepage = "https://pgtap.org"; + inherit (postgresql.meta) platforms; + license = licenses.mit; + }; + }; +in +buildEnv { + name = pname; + paths = packages; + + pathsToLink = [ + "/lib" + "/share/postgresql/extension" + ]; - nativeBuildInputs = [ postgresql perl perlPackages.TAPParserSourceHandlerpgTAP which ]; - - installPhase = '' - install -D {sql/pgtap--${version}.sql,pgtap.control} -t $out/share/postgresql/extension - ''; - - meta = with lib; { - description = "A unit testing framework for PostgreSQL"; - longDescription = '' - pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. - It includes a comprehensive collection of TAP-emitting assertion functions, - as well as the ability to integrate with other TAP-emitting test frameworks. - It can also be used in the xUnit testing style. - ''; - homepage = "https://pgtap.org"; - inherit (postgresql.meta) platforms; - license = licenses.mit; + passthru = { + inherit versions numberOfVersions; + pname = "${pname}-all"; + version = + "multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions); }; } diff --git a/nix/ext/tests/pgtap.nix b/nix/ext/tests/pgtap.nix new file mode 100644 index 000000000..ac79985ed --- /dev/null +++ b/nix/ext/tests/pgtap.nix @@ -0,0 +1,155 @@ +{ self, pkgs }: +let + pname = "pgtap"; + inherit (pkgs) lib; + installedExtension = + postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all"; + versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions; + postgresqlWithExtension = + postgresql: + let + majorVersion = lib.versions.major postgresql.version; + pkg = pkgs.buildEnv { + name = "postgresql-${majorVersion}-${pname}"; + paths = [ + postgresql + postgresql.lib + (installedExtension majorVersion) + ]; + passthru = { + inherit (postgresql) version psqlSchema; + lib = pkg; + withPackages = _: pkg; + }; + nativeBuildInputs = [ pkgs.makeWrapper ]; + pathsToLink = [ + "/" + "/bin" + "/lib" + ]; + postBuild = '' + wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib + wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib + wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib + ''; + }; + in + pkg; +in +self.inputs.nixpkgs.lib.nixos.runTest { + name = pname; + hostPkgs = pkgs; + nodes.server = + { config, ... }: + { + virtualisation = { + forwardPorts = [ + { + from = "host"; + host.port = 13022; + guest.port = 22; + } + ]; + }; + services.openssh = { + enable = true; + }; + + services.postgresql = { + enable = true; + package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15; + }; + + specialisation.postgresql17.configuration = { + services.postgresql = { + package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17); + }; + + systemd.services.postgresql-migrate = { + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "postgres"; + Group = "postgres"; + StateDirectory = "postgresql"; + WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}"; + }; + script = + let + oldPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15; + newPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17; + oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}"; + newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}"; + in + '' + if [[ ! -d ${newDataDir} ]]; then + install -d -m 0700 -o postgres -g postgres "${newDataDir}" + ${newPostgresql}/bin/initdb -D "${newDataDir}" + ${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \ + --old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin" + else + echo "${newDataDir} already exists" + fi + ''; + }; + + systemd.services.postgresql = { + after = [ "postgresql-migrate.service" ]; + requires = [ "postgresql-migrate.service" ]; + }; + }; + + }; + testScript = + { nodes, ... }: + let + pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17"; + in + '' + versions = { + "15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}], + "17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}], + } + + def run_sql(query): + return server.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip() + + def check_upgrade_path(pg_version): + with subtest("Check ${pname} upgrade path"): + firstVersion = versions[pg_version][0] + server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'") + run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{firstVersion}' CASCADE;""") + installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""") + assert installed_version == firstVersion, f"Expected ${pname} version {firstVersion}, but found {installed_version}" + for version in versions[pg_version][1:]: + run_sql(f"""ALTER EXTENSION ${pname} UPDATE TO '{version}';""") + installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""") + assert installed_version == version, f"Expected ${pname} version {version}, but found {installed_version}" + + start_all() + + server.wait_for_unit("multi-user.target") + server.wait_for_unit("postgresql.service") + + check_upgrade_path("15") + + with subtest("Check ${pname} latest extension version"): + server.succeed("sudo -u postgres psql -c 'DROP EXTENSION ${pname};'") + server.succeed("sudo -u postgres psql -c 'CREATE EXTENSION ${pname} CASCADE;'") + installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""") + latestVersion = versions["15"][-1] + assert f"${pname},{latestVersion}" in installed_extensions + + with subtest("switch to postgresql 17"): + server.succeed( + "${pg17-configuration}/bin/switch-to-configuration test >&2" + ) + + with subtest("Check ${pname} latest extension version after upgrade"): + installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""") + latestVersion = versions["17"][-1] + assert f"${pname},{latestVersion}" in installed_extensions + + check_upgrade_path("17") + ''; +} diff --git a/nix/ext/versions.json b/nix/ext/versions.json new file mode 100644 index 000000000..7cff78c4d --- /dev/null +++ b/nix/ext/versions.json @@ -0,0 +1,24 @@ +{ + "pgtap": { + "1.2.0": { + "postgresql": [ + "15" + ], + "hash": "sha256-lb0PRffwo6J5a6Hqw1ggvn0cW7gPZ02OEcLPi9ineI8=" + }, + "1.3.1": { + "postgresql": [ + "15", + "17" + ], + "hash": "sha256-HOgCb1CCfsfbMbMMWuzFJ4B8CfVm9b0sI2zBY3/kqyI=" + }, + "1.3.3": { + "postgresql": [ + "15", + "17" + ], + "hash": "sha256-YgvfLGF7pLVcCKD66NnWAydDxtoYHH1DpLiYTEKHJ0E=" + } + } +} diff --git a/nix/tests/expected/z_15_ext_interface.out b/nix/tests/expected/z_15_ext_interface.out index 540bee2e8..69827a398 100644 --- a/nix/tests/expected/z_15_ext_interface.out +++ b/nix/tests/expected/z_15_ext_interface.out @@ -2173,7 +2173,6 @@ order by pgtap | public | _pg_sv_type_array | oid[] | name[] pgtap | public | _prokind | p_oid oid | "char" pgtap | public | _query | text | text - pgtap | public | _quote_ident_like | text, text | text pgtap | public | _refine_vol | text | text pgtap | public | _relcomp | text, text, text, text, text | text pgtap | public | _relcomp | text, text, text, text | text @@ -2186,6 +2185,7 @@ order by pgtap | public | _returns | name, name[] | text pgtap | public | _returns | name | text pgtap | public | _returns | name, name | text + pgtap | public | _retval | text | text pgtap | public | _rexists | character[], name, name | boolean pgtap | public | _rexists | character, name | boolean pgtap | public | _rexists | character[], name | boolean @@ -2214,6 +2214,7 @@ order by pgtap | public | _type_func | "char", name, name, name[] | boolean pgtap | public | _type_func | "char", name, name[] | boolean pgtap | public | _type_func | "char", name, name | boolean + pgtap | public | _typename | name | text pgtap | public | _types_are | name, name[], text, character[] | text pgtap | public | _types_are | name[], text, character[] | text pgtap | public | _unalike | boolean, anyelement, text, text | text @@ -2283,10 +2284,12 @@ order by pgtap | public | col_is_null | schema_name name, table_name name, column_name name, description text | text pgtap | public | col_is_pk | name, name, name, text | text pgtap | public | col_is_pk | name, name[], text | text + pgtap | public | col_is_pk | name, name, name[] | text pgtap | public | col_is_pk | name, name[] | text pgtap | public | col_is_pk | name, name, text | text pgtap | public | col_is_pk | name, name, name[], text | text pgtap | public | col_is_pk | name, name | text + pgtap | public | col_is_pk | name, name, name | text pgtap | public | col_is_unique | name, name, name, text | text pgtap | public | col_is_unique | name, name[], text | text pgtap | public | col_is_unique | name, name, name[] | text @@ -2400,6 +2403,7 @@ order by pgtap | public | foreign_tables_are | name[], text | text pgtap | public | foreign_tables_are | name, name[], text | text pgtap | public | foreign_tables_are | name, name[] | text + pgtap | public | format_type_string | text | text pgtap | public | function_lang_is | name, name, name, text | text pgtap | public | function_lang_is | name, name, text | text pgtap | public | function_lang_is | name, name[], name, text | text @@ -2512,6 +2516,7 @@ order by pgtap | public | has_pk | name, name, text | text pgtap | public | has_pk | name, text | text pgtap | public | has_pk | name | text + pgtap | public | has_pk | name, name | text pgtap | public | has_relation | name, name, text | text pgtap | public | has_relation | name, text | text pgtap | public | has_relation | name | text @@ -5221,7 +5226,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(5051 rows) +(5056 rows) /* diff --git a/nix/tests/expected/z_17_ext_interface.out b/nix/tests/expected/z_17_ext_interface.out index f7750f849..e79bec6f6 100644 --- a/nix/tests/expected/z_17_ext_interface.out +++ b/nix/tests/expected/z_17_ext_interface.out @@ -2158,7 +2158,6 @@ order by pgtap | public | _pg_sv_type_array | oid[] | name[] pgtap | public | _prokind | p_oid oid | "char" pgtap | public | _query | text | text - pgtap | public | _quote_ident_like | text, text | text pgtap | public | _refine_vol | text | text pgtap | public | _relcomp | text, text, text, text, text | text pgtap | public | _relcomp | text, text, text, text | text @@ -2171,6 +2170,7 @@ order by pgtap | public | _returns | name, name[] | text pgtap | public | _returns | name | text pgtap | public | _returns | name, name | text + pgtap | public | _retval | text | text pgtap | public | _rexists | character[], name, name | boolean pgtap | public | _rexists | character, name | boolean pgtap | public | _rexists | character[], name | boolean @@ -2199,6 +2199,7 @@ order by pgtap | public | _type_func | "char", name, name, name[] | boolean pgtap | public | _type_func | "char", name, name[] | boolean pgtap | public | _type_func | "char", name, name | boolean + pgtap | public | _typename | name | text pgtap | public | _types_are | name, name[], text, character[] | text pgtap | public | _types_are | name[], text, character[] | text pgtap | public | _unalike | boolean, anyelement, text, text | text @@ -2268,10 +2269,12 @@ order by pgtap | public | col_is_null | schema_name name, table_name name, column_name name, description text | text pgtap | public | col_is_pk | name, name, name, text | text pgtap | public | col_is_pk | name, name[], text | text + pgtap | public | col_is_pk | name, name, name[] | text pgtap | public | col_is_pk | name, name[] | text pgtap | public | col_is_pk | name, name, text | text pgtap | public | col_is_pk | name, name, name[], text | text pgtap | public | col_is_pk | name, name | text + pgtap | public | col_is_pk | name, name, name | text pgtap | public | col_is_unique | name, name, name, text | text pgtap | public | col_is_unique | name, name[], text | text pgtap | public | col_is_unique | name, name, name[] | text @@ -2385,6 +2388,7 @@ order by pgtap | public | foreign_tables_are | name[], text | text pgtap | public | foreign_tables_are | name, name[], text | text pgtap | public | foreign_tables_are | name, name[] | text + pgtap | public | format_type_string | text | text pgtap | public | function_lang_is | name, name, name, text | text pgtap | public | function_lang_is | name, name, text | text pgtap | public | function_lang_is | name, name[], name, text | text @@ -2497,6 +2501,7 @@ order by pgtap | public | has_pk | name, name, text | text pgtap | public | has_pk | name, text | text pgtap | public | has_pk | name | text + pgtap | public | has_pk | name, name | text pgtap | public | has_relation | name, name, text | text pgtap | public | has_relation | name, text | text pgtap | public | has_relation | name | text @@ -4865,7 +4870,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(4708 rows) +(4713 rows) /* diff --git a/nix/tests/expected/z_orioledb-17_ext_interface.out b/nix/tests/expected/z_orioledb-17_ext_interface.out index f7750f849..e79bec6f6 100644 --- a/nix/tests/expected/z_orioledb-17_ext_interface.out +++ b/nix/tests/expected/z_orioledb-17_ext_interface.out @@ -2158,7 +2158,6 @@ order by pgtap | public | _pg_sv_type_array | oid[] | name[] pgtap | public | _prokind | p_oid oid | "char" pgtap | public | _query | text | text - pgtap | public | _quote_ident_like | text, text | text pgtap | public | _refine_vol | text | text pgtap | public | _relcomp | text, text, text, text, text | text pgtap | public | _relcomp | text, text, text, text | text @@ -2171,6 +2170,7 @@ order by pgtap | public | _returns | name, name[] | text pgtap | public | _returns | name | text pgtap | public | _returns | name, name | text + pgtap | public | _retval | text | text pgtap | public | _rexists | character[], name, name | boolean pgtap | public | _rexists | character, name | boolean pgtap | public | _rexists | character[], name | boolean @@ -2199,6 +2199,7 @@ order by pgtap | public | _type_func | "char", name, name, name[] | boolean pgtap | public | _type_func | "char", name, name[] | boolean pgtap | public | _type_func | "char", name, name | boolean + pgtap | public | _typename | name | text pgtap | public | _types_are | name, name[], text, character[] | text pgtap | public | _types_are | name[], text, character[] | text pgtap | public | _unalike | boolean, anyelement, text, text | text @@ -2268,10 +2269,12 @@ order by pgtap | public | col_is_null | schema_name name, table_name name, column_name name, description text | text pgtap | public | col_is_pk | name, name, name, text | text pgtap | public | col_is_pk | name, name[], text | text + pgtap | public | col_is_pk | name, name, name[] | text pgtap | public | col_is_pk | name, name[] | text pgtap | public | col_is_pk | name, name, text | text pgtap | public | col_is_pk | name, name, name[], text | text pgtap | public | col_is_pk | name, name | text + pgtap | public | col_is_pk | name, name, name | text pgtap | public | col_is_unique | name, name, name, text | text pgtap | public | col_is_unique | name, name[], text | text pgtap | public | col_is_unique | name, name, name[] | text @@ -2385,6 +2388,7 @@ order by pgtap | public | foreign_tables_are | name[], text | text pgtap | public | foreign_tables_are | name, name[], text | text pgtap | public | foreign_tables_are | name, name[] | text + pgtap | public | format_type_string | text | text pgtap | public | function_lang_is | name, name, name, text | text pgtap | public | function_lang_is | name, name, text | text pgtap | public | function_lang_is | name, name[], name, text | text @@ -2497,6 +2501,7 @@ order by pgtap | public | has_pk | name, name, text | text pgtap | public | has_pk | name, text | text pgtap | public | has_pk | name | text + pgtap | public | has_pk | name, name | text pgtap | public | has_relation | name, name, text | text pgtap | public | has_relation | name, text | text pgtap | public | has_relation | name | text @@ -4865,7 +4870,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(4708 rows) +(4713 rows) /*