diff --git a/src/tox_uv/_venv.py b/src/tox_uv/_venv.py index 9503032..29093fb 100644 --- a/src/tox_uv/_venv.py +++ b/src/tox_uv/_venv.py @@ -182,13 +182,13 @@ def create_python_env(self) -> None: elif (base.major, base.minor) == sys.version_info[:2] and (sys.implementation.name.lower() == imp): version_spec = sys.executable else: - uv_imp = "" if (imp and imp == "cpython") else imp + uv_imp = imp or "" if not base.major: - version_spec = f"{uv_imp or ''}" + version_spec = uv_imp elif not base.minor: - version_spec = f"{uv_imp or ''}{base.major}" + version_spec = f"{uv_imp}{base.major}" else: - version_spec = f"{uv_imp or ''}{base.major}.{base.minor}" + version_spec = f"{uv_imp}{base.major}.{base.minor}" cmd: list[str] = [self.uv, "venv", "-p", version_spec, "--allow-existing"] if self.options.verbosity > 3: # noqa: PLR2004 diff --git a/tests/test_tox_uv_venv.py b/tests/test_tox_uv_venv.py index 2466f3d..9f13fd4 100644 --- a/tests/test_tox_uv_venv.py +++ b/tests/test_tox_uv_venv.py @@ -97,6 +97,42 @@ def test_uv_venv_spec_pypy( assert f"-p {expected_uv_pypy} " in stdout +@pytest.mark.parametrize( + ("implementation", "expected_implementation", "expected_name"), + [ + ("", "cpython", "cpython"), + ("py", "cpython", "cpython"), + ("pypy", "pypy", "pypy"), + ], +) +def test_uv_venv_spec_full_implementation( + tox_project: ToxProjectCreator, + implementation: str, + expected_implementation: str, + expected_name: str, +) -> None: + """Validate that Python implementations are explicitly passed to uv's `-p` argument. + + This test ensures that uv searches for the target Python implementation and version, + even if another implementation -- with the same language version -- + is found on the path first. + + This prevents a regression to a bug that occurred when PyPy 3.10 was on the PATH + and tox was invoked with `tox -e py3.10`: + uv was invoked with `-p 3.10` and found PyPy 3.10, not CPython 3.10. + """ + + project = tox_project({}) + result = project.run("run", "-vve", f"{implementation}9.99") + + # Verify that uv was invoked with the full Python implementation and version. + assert f" -p {expected_implementation}9.99 " in result.out + + # Verify that uv interpreted the `-p` argument as a Python spec, not an executable. + # This confirms that tox-uv is passing recognizable, usable `-p` arguments to uv. + assert f"no interpreter found for {expected_name} 9.99" in result.err.lower() + + @pytest.fixture def other_interpreter_exe() -> pathlib.Path: # pragma: no cover """Returns an interpreter executable path that is not the exact same as `sys.executable`.