from __future__ import annotations import json import os from pathlib import Path import subprocess import sys import sysconfig import tempfile import unittest class WheelRuntimeTests(unittest.TestCase): def test_built_wheel_contains_and_runs_core_migrations_without_source_checkout(self) -> None: repository_root = Path(__file__).resolve().parents[1] with tempfile.TemporaryDirectory(prefix="govoplan-core-wheel-") as directory: temporary_root = Path(directory) wheel_directory = temporary_root / "wheels" wheel_directory.mkdir() self._run( sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(wheel_directory), str(repository_root), cwd=temporary_root, ) core_wheels = tuple(wheel_directory.glob("govoplan_core-*.whl")) self.assertEqual(1, len(core_wheels)) virtual_environment = temporary_root / "venv" self._run(sys.executable, "-m", "venv", str(virtual_environment), cwd=temporary_root) venv_python = virtual_environment / "bin" / "python" self._run( str(venv_python), "-m", "pip", "install", "--no-deps", str(core_wheels[0]), cwd=temporary_root, ) database_path = temporary_root / "wheel-runtime.db" probe = self._run( str(venv_python), "-c", self._probe_script(), str(database_path), cwd=temporary_root, env={ **os.environ, # Reuse only third-party test dependencies from the parent # environment. Editable .pth files are not processed from # PYTHONPATH, so Core itself can only come from the wheel. "PYTHONPATH": sysconfig.get_path("purelib"), "GOVOPLAN_CORE_SOURCE_ROOT": "", "GOVOPLAN_ENABLED_MODULES": "", }, ) result = json.loads(probe.stdout) installed_package = Path(result["package_file"]) runtime_root = Path(result["runtime_root"]) self.assertTrue(installed_package.is_relative_to(virtual_environment)) self.assertTrue(runtime_root.is_relative_to(virtual_environment)) self.assertNotEqual(repository_root, runtime_root) self.assertTrue((runtime_root / "alembic.ini").is_file()) self.assertTrue((runtime_root / "alembic" / "env.py").is_file()) self.assertEqual(["c91f0a72be34"], result["heads"]) self.assertIn("core_scopes", result["tables"]) self.assertIn("core_system_settings", result["tables"]) @staticmethod def _run(*command: str, cwd: Path, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]: result = subprocess.run( command, cwd=cwd, env=env, check=False, capture_output=True, text=True, ) if result.returncode: raise AssertionError( f"Command failed ({result.returncode}): {' '.join(command)}\n" f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" ) return result @staticmethod def _probe_script() -> str: return """ import json from pathlib import Path import sys from alembic import command from alembic.script import ScriptDirectory from sqlalchemy import create_engine, inspect import govoplan_core from govoplan_core.db.migrations import _repo_root, alembic_config database_url = f"sqlite:///{Path(sys.argv[1])}" config = alembic_config(database_url=database_url, enabled_modules=()) command.upgrade(config, "heads") script = ScriptDirectory.from_config(config) engine = create_engine(database_url) try: with engine.connect() as connection: tables = sorted(inspect(connection).get_table_names()) finally: engine.dispose() print(json.dumps({ "package_file": govoplan_core.__file__, "runtime_root": str(_repo_root()), "heads": sorted(script.get_heads()), "tables": tables, })) """ if __name__ == "__main__": unittest.main()