153 lines
6.2 KiB
Python
153 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
META_ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPT = META_ROOT / "tools" / "repo" / "sync-python-environment.py"
|
|
|
|
|
|
def load_sync_module():
|
|
spec = importlib.util.spec_from_file_location("sync_python_environment", SCRIPT)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError(f"Could not load {SCRIPT}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
class PythonEnvironmentSyncTests(unittest.TestCase):
|
|
def test_stale_local_projects_share_one_resolver_transaction(self) -> None:
|
|
sync = load_sync_module()
|
|
with tempfile.TemporaryDirectory(prefix="govoplan-python-sync-") as directory:
|
|
root = Path(directory)
|
|
requirements = root / "requirements-dev.txt"
|
|
requirements.write_text("-e ./govoplan-core\n-e ./govoplan-access\n", encoding="utf-8")
|
|
for project in ("govoplan-core", "govoplan-access"):
|
|
project_root = root / project
|
|
project_root.mkdir()
|
|
(project_root / "pyproject.toml").write_text(
|
|
f'[project]\nname = "{project}"\nversion = "0.1.10"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
local_requirements = sync.local_requirement_entries(requirements)
|
|
fingerprint = sync.build_fingerprint(
|
|
requirements=requirements,
|
|
python="/test/venv/bin/python",
|
|
local_requirements=local_requirements,
|
|
)
|
|
requirements_digest = hashlib.sha256(requirements.read_bytes()).hexdigest()
|
|
previous = {
|
|
"version": sync.STAMP_VERSION,
|
|
"python": "/test/venv/bin/python",
|
|
"inputs": [
|
|
{"path": str(requirements), "sha256": requirements_digest},
|
|
*(
|
|
{"path": entry.pyproject, "sha256": "stale"}
|
|
for entry in local_requirements
|
|
),
|
|
],
|
|
"requirements_entries": [
|
|
entry.as_dict() for entry in sync.parse_requirement_entries(requirements)
|
|
],
|
|
}
|
|
|
|
plan = sync.build_install_plan(
|
|
previous=previous,
|
|
fingerprint=fingerprint,
|
|
requirements=requirements,
|
|
python="/test/venv/bin/python",
|
|
local_requirements=local_requirements,
|
|
force=False,
|
|
)
|
|
|
|
self.assertEqual(plan.mode, "Selective Python environment sync")
|
|
self.assertIn("one resolver transaction", plan.reason)
|
|
self.assertEqual(len(plan.commands), 1)
|
|
command = plan.commands[0]
|
|
self.assertEqual(command[:5], ("/test/venv/bin/python", "-m", "pip", "install", "-e"))
|
|
self.assertEqual(command.count("-e"), 2)
|
|
self.assertIn(str(root / "govoplan-core"), command)
|
|
self.assertIn(str(root / "govoplan-access"), command)
|
|
|
|
def test_missing_editable_distribution_is_not_hidden_by_current_stamp(self) -> None:
|
|
sync = load_sync_module()
|
|
with tempfile.TemporaryDirectory(prefix="govoplan-python-sync-") as directory:
|
|
root = Path(directory)
|
|
project_root = root / "govoplan-probe-missing"
|
|
project_root.mkdir()
|
|
(project_root / "pyproject.toml").write_text(
|
|
"\n".join(
|
|
(
|
|
"[project]",
|
|
'name = "govoplan-probe-definitely-not-installed"',
|
|
'version = "0.1.0"',
|
|
"",
|
|
'[project.entry-points."govoplan.modules"]',
|
|
'probe_missing = "govoplan_probe.backend.manifest:get_manifest"',
|
|
"",
|
|
)
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
requirements = root / "requirements-dev.txt"
|
|
requirements.write_text("-e ./govoplan-probe-missing\n", encoding="utf-8")
|
|
entries = sync.local_requirement_entries(requirements)
|
|
|
|
validation = sync.validate_local_installations(sys.executable, entries)
|
|
plan = sync.build_environment_repair_plan(
|
|
python=sys.executable,
|
|
stale_requirements=validation.stale_requirements,
|
|
)
|
|
|
|
self.assertFalse(validation.current)
|
|
self.assertEqual(validation.stale_requirements, entries)
|
|
self.assertIn("distribution is not installed", validation.issues[0])
|
|
self.assertEqual(plan.mode, "Selective Python environment repair")
|
|
self.assertEqual(plan.commands[0][-2:], ("-e", str(project_root)))
|
|
|
|
def test_declared_module_entry_points_are_part_of_environment_validation(self) -> None:
|
|
sync = load_sync_module()
|
|
with tempfile.TemporaryDirectory(prefix="govoplan-python-sync-") as directory:
|
|
root = Path(directory)
|
|
project_root = root / "govoplan-probe"
|
|
project_root.mkdir()
|
|
(project_root / "pyproject.toml").write_text(
|
|
"\n".join(
|
|
(
|
|
"[project]",
|
|
'name = "govoplan-probe"',
|
|
'version = "0.1.0"',
|
|
"",
|
|
'[project.entry-points."govoplan.modules"]',
|
|
'probe = "govoplan_probe.backend.manifest:get_manifest"',
|
|
"",
|
|
)
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
requirements = root / "requirements-dev.txt"
|
|
requirements.write_text("-e ./govoplan-probe\n", encoding="utf-8")
|
|
|
|
expectations = sync.local_installation_expectations(
|
|
sync.local_requirement_entries(requirements)
|
|
)
|
|
|
|
self.assertEqual(len(expectations), 1)
|
|
self.assertEqual(expectations[0].distribution, "govoplan-probe")
|
|
self.assertEqual(
|
|
expectations[0].entry_points,
|
|
(("govoplan.modules", "probe", "govoplan_probe.backend.manifest:get_manifest"),),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|