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()