fix(dev): resolve stale editable packages together
This commit is contained in:
82
tests/test_python_environment_sync.py
Normal file
82
tests/test_python_environment_sync.py
Normal file
@@ -0,0 +1,82 @@
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -236,7 +236,7 @@ def build_install_plan(
|
||||
removed_paths = set(previous_inputs) - set(current_inputs)
|
||||
stale_requirements = requirements_key in changed_paths or requirements_key in removed_paths
|
||||
|
||||
commands: list[tuple[str, ...]] = []
|
||||
installs: list[tuple[str, ...]] = []
|
||||
warnings: list[str] = []
|
||||
|
||||
if stale_requirements:
|
||||
@@ -247,8 +247,7 @@ def build_install_plan(
|
||||
"requirements changed in a way that needs the full resolver.",
|
||||
(full_command,),
|
||||
)
|
||||
for install_args in delta.commands:
|
||||
commands.append((python, "-m", "pip", "install", *install_args))
|
||||
installs.extend(delta.commands)
|
||||
warnings.extend(delta.warnings)
|
||||
changed_paths.discard(requirements_key)
|
||||
removed_paths.discard(requirements_key)
|
||||
@@ -266,7 +265,7 @@ def build_install_plan(
|
||||
f"tracked input changed outside a known local project: {path}",
|
||||
(full_command,),
|
||||
)
|
||||
commands.append((python, "-m", "pip", "install", *entry.install_args))
|
||||
installs.append(entry.install_args)
|
||||
|
||||
if removed_paths:
|
||||
return InstallPlan(
|
||||
@@ -275,12 +274,19 @@ def build_install_plan(
|
||||
(full_command,),
|
||||
)
|
||||
|
||||
deduped_commands = tuple(dict.fromkeys(commands))
|
||||
if deduped_commands:
|
||||
deduped_installs = tuple(dict.fromkeys(installs))
|
||||
if deduped_installs:
|
||||
command = (
|
||||
python,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
*(argument for install_args in deduped_installs for argument in install_args),
|
||||
)
|
||||
return InstallPlan(
|
||||
"Selective Python environment sync",
|
||||
f"installing {len(deduped_commands)} stale local requirement(s).",
|
||||
deduped_commands,
|
||||
f"installing {len(deduped_installs)} stale local requirement(s) in one resolver transaction.",
|
||||
(command,),
|
||||
tuple(warnings),
|
||||
)
|
||||
return InstallPlan(
|
||||
|
||||
Reference in New Issue
Block a user