diff --git a/pyproject.toml b/pyproject.toml index b64fa8fa..595662e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [project] name = "dataops-testgen" -version = "5.70.2" +version = "5.70.3" description = "DataKitchen's Data Quality DataOps TestGen" authors = [ { "name" = "DataKitchen, Inc.", "email" = "info@datakitchen.io" }, @@ -92,7 +92,10 @@ dependencies = [ [project.optional-dependencies] standalone = [ - "pixeltable-pgserver>=0.5.1", + # Upper bound guards against a future release dropping a bundled PostgreSQL + # major that existing data dirs were initialized with (see start_server in + # common/standalone_postgres.py). Revisit deliberately before relaxing. + "pixeltable-pgserver>=0.6.0,<0.7.0", ] dev = [ diff --git a/testgen/common/standalone_postgres.py b/testgen/common/standalone_postgres.py index be27e774..bc12efcf 100644 --- a/testgen/common/standalone_postgres.py +++ b/testgen/common/standalone_postgres.py @@ -22,6 +22,7 @@ STANDALONE_MODE_ENV_VAR = "TG_STANDALONE_MODE" HOME_DIR_ENV_VAR = "TG_TESTGEN_HOME" STANDALONE_URI_ENV_VAR = "_TG_STANDALONE_URI" +NEW_INSTALL_POSTGRES_VERSION = 18 # Stored as ``project_host`` in the demo-DB connection row so that the actual # host/port — which can change across sessions on Windows (pgserver picks a @@ -84,8 +85,13 @@ def start_server(data_dir: Path | None = None) -> None: data_dir = get_home_dir() / "pgdata" data_dir.mkdir(parents=True, exist_ok=True) - LOG.info("Starting embedded PostgreSQL (data: %s) ...", data_dir) - _server = pgserver.get_server(data_dir) + # pgserver bundles multiple PostgreSQL majors and defaults to its newest. + # Honor the major that initialized an existing data dir so upgrades never fail with a version mismatch. + existing_version = pgserver.pgdata_version(data_dir) + postgres_version = existing_version if existing_version is not None else NEW_INSTALL_POSTGRES_VERSION + + LOG.info("Starting embedded PostgreSQL (data: %s, pg%s) ...", data_dir, postgres_version) + _server = pgserver.get_server(data_dir, postgres_version=postgres_version) LOG.info("Embedded PostgreSQL ready: %s", _server.get_uri()) _reinitialize_orm_engine() diff --git a/tests/unit/common/test_standalone_postgres.py b/tests/unit/common/test_standalone_postgres.py new file mode 100644 index 00000000..35c106fe --- /dev/null +++ b/tests/unit/common/test_standalone_postgres.py @@ -0,0 +1,48 @@ +"""Unit tests for embedded-PostgreSQL version selection in standalone mode.""" + +import sys +from unittest import mock + +import pytest + +from testgen.common import standalone_postgres + + +@pytest.fixture +def fake_pgserver(): + """Mock the ``pixeltable_pgserver`` module imported inside ``start_server``.""" + fake = mock.MagicMock() + fake.get_server.return_value.get_uri.return_value = "postgresql:///db?host=/tmp/sock" + with mock.patch.dict(sys.modules, {"pixeltable_pgserver": fake}): + yield fake + + +@pytest.fixture(autouse=True) +def reset_module_state(): + """Isolate the module-level server singleton and skip real ORM/atexit side effects.""" + standalone_postgres._server = None + with ( + mock.patch.object(standalone_postgres, "_reinitialize_orm_engine"), + mock.patch("atexit.register"), + ): + yield + standalone_postgres._server = None + + +def test_fresh_data_dir_uses_new_install_version(fake_pgserver, tmp_path): + fake_pgserver.pgdata_version.return_value = None + + standalone_postgres.start_server(tmp_path / "pgdata") + + assert fake_pgserver.get_server.call_args.kwargs["postgres_version"] == ( + standalone_postgres.NEW_INSTALL_POSTGRES_VERSION + ) + + +def test_existing_data_dir_honors_on_disk_version(fake_pgserver, tmp_path): + # A cluster initialized by an older bundled major must keep running on it. + fake_pgserver.pgdata_version.return_value = 16 + + standalone_postgres.start_server(tmp_path / "pgdata") + + assert fake_pgserver.get_server.call_args.kwargs["postgres_version"] == 16