fix(release): package Core migration runtime
This commit is contained in:
143
tests/test_wheel_runtime.py
Normal file
143
tests/test_wheel_runtime.py
Normal file
@@ -0,0 +1,143 @@
|
||||
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]
|
||||
access_repository = repository_root.parent / "govoplan-access"
|
||||
self.assertTrue(access_repository.is_dir(), "wheel runtime check requires the mandatory Access repository")
|
||||
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,
|
||||
)
|
||||
self._run(
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"wheel",
|
||||
"--no-deps",
|
||||
"--wheel-dir",
|
||||
str(wheel_directory),
|
||||
str(access_repository),
|
||||
cwd=temporary_root,
|
||||
)
|
||||
core_wheels = tuple(wheel_directory.glob("govoplan_core-*.whl"))
|
||||
access_wheels = tuple(wheel_directory.glob("govoplan_access-*.whl"))
|
||||
self.assertEqual(1, len(core_wheels))
|
||||
self.assertEqual(1, len(access_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]),
|
||||
str(access_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.assertIn("4f2a9c8e7b6d", 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()
|
||||
Reference in New Issue
Block a user