diff --git a/src/blueapi/cli/scratch.py b/src/blueapi/cli/scratch.py index 2536be404..6fa01d83e 100644 --- a/src/blueapi/cli/scratch.py +++ b/src/blueapi/cli/scratch.py @@ -129,22 +129,16 @@ def scratch_install(*paths: Path, timeout: float = _DEFAULT_INSTALL_TIMEOUT) -> """ if not paths: return - args = [ - "uv", - "pip", - "install", - ] + LOGGER.info("Installing packages") for path in paths: _validate_directory(path) - args.extend(["-e", str(path)]) - - LOGGER.info("Installing packages") - process = Popen(args) - process.wait(timeout=timeout) - if process.returncode != 0: - raise RuntimeError( - f"Failed to install packages: Exit Code: {process.returncode}" - ) + args = ["uv", "pip", "install", "-e", str(path)] + process = Popen(args) + process.wait(timeout=timeout) + if process.returncode != 0: + raise RuntimeError( + f"Failed to install packages: Exit Code: {process.returncode}" + ) def _validate_root_directory(root_path: Path, required_gid: int | None) -> None: diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index b152e966d..8c4dc19cf 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -1,4 +1,5 @@ import inspect +import subprocess import time from asyncio import Queue from collections.abc import Generator @@ -884,3 +885,72 @@ def test_run_blocking_requires_auth( ): with pytest.raises(UnauthorisedAccessError): client_without_auth.run_blocking(small_task) + + +# Kept last: installs packages into the environment, so it must not run +# before (and potentially disturb) the tests above. +@pytest.mark.timeout(600) +@pytest.mark.parametrize( + "repos,expected_location_name,expected_version", + [ + pytest.param( + [("dodal", "2.6.2"), ("dodalv3.0.0", "3.0.0")], + "dodalv3.0.0", + "3.0.0", + id="v2-then-v3", + ), + pytest.param( + [("dodalv3.0.0", "3.0.0"), ("dodal", "2.6.2")], + "dodal", + "2.6.2", + id="v3-then-v2", + ), + ], +) +def test_setup_scratch_last_repo_wins_for_duplicate_package( + tmp_path: Path, + repos: list[tuple[str, str]], + expected_location_name: str, + expected_version: str, +): + """ + Regression test for https://github.com/DiamondLightSource/blueapi/issues/1590 + + Two scratch repositories both provide the dls-dodal package, pinned to + different tags. setup-scratch installs each editable in config order, so + the later entry must be what is left installed. + """ + scratch_root = tmp_path / "scratch" + scratch_root.mkdir() + overlay = tmp_path / "overlay.yaml" + repo_entries = "\n".join( + f""" - name: {name} + remote_url: https://github.com/DiamondLightSource/dodal.git + target_revision: "{revision}\"""" + for name, revision in repos + ) + overlay.write_text(f""" +scratch: + root: {scratch_root} + repositories: +{repo_entries} +""") + + result = subprocess.run( + ["uv", "run", "blueapi", "-c", str(overlay), "setup-scratch"], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stdout + result.stderr + + installed = subprocess.run( + ["uv", "run", "--no-sync", "uv", "pip", "show", "dls-dodal"], + capture_output=True, + text=True, + ) + assert ( + f"Editable project location: {scratch_root / expected_location_name}" + in installed.stdout + ) + assert "Name: dls-dodal" in installed.stdout + assert f"Version: {expected_version}" in installed.stdout