diff --git a/docs/changelog/3327.bugfix.rst b/docs/changelog/3327.bugfix.rst new file mode 100644 index 000000000..dbad98cf6 --- /dev/null +++ b/docs/changelog/3327.bugfix.rst @@ -0,0 +1,2 @@ +Fix and test the string spec for the ``sys.executable`` interpreter (introduced in :pull:`3325`) +- by :user:`hroncok` diff --git a/src/tox/tox_env/python/api.py b/src/tox/tox_env/python/api.py index d27ca45d9..e231dcda4 100644 --- a/src/tox/tox_env/python/api.py +++ b/src/tox/tox_env/python/api.py @@ -157,6 +157,14 @@ def extract_base_python(cls, env_name: str) -> str | None: return next(iter(candidates)) return None + @classmethod + def _python_spec_for_sys_executable(cls) -> PythonSpec: + implementation = sys.implementation.name + version = sys.version_info + bits = "64" if sys.maxsize > 2**32 else "32" + string_spec = f"{implementation}{version.major}{version.minor}-{bits}" + return PythonSpec.from_string_spec(string_spec) + @classmethod def _validate_base_python( cls, @@ -172,8 +180,7 @@ def _validate_base_python( if spec_base.path is not None: path = Path(spec_base.path).absolute() if str(spec_base.path) == sys.executable: - ver, is_64 = sys.version_info, sys.maxsize != 2**32 - spec_base = PythonSpec.from_string_spec(f"{sys.implementation}{ver.major}{ver.minor}-{is_64}") + spec_base = cls._python_spec_for_sys_executable() else: spec_base = cls.python_spec_for_path(path) if any( diff --git a/tests/tox_env/python/test_python_api.py b/tests/tox_env/python/test_python_api.py index 8690cc838..b809e0f40 100644 --- a/tests/tox_env/python/test_python_api.py +++ b/tests/tox_env/python/test_python_api.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys +from types import SimpleNamespace from typing import TYPE_CHECKING, Callable from unittest.mock import patch @@ -289,3 +290,29 @@ def test_usedevelop_with_nonexistent_basepython(tox_project: ToxProjectCreator) project = tox_project({"tox.ini": ini}) result = project.run() assert result.code == 0 + + +@pytest.mark.parametrize( + ("impl", "major", "minor", "arch"), + [ + ("cpython", 3, 12, 64), + ("pypy", 3, 9, 32), + ], +) +def test_python_spec_for_sys_executable(impl: str, major: int, minor: int, arch: int, mocker: MockerFixture) -> None: + version_info = SimpleNamespace(major=major, minor=minor, micro=5, releaselevel="final", serial=0) + implementation = SimpleNamespace( + name=impl, + cache_tag=f"{impl}-{major}{minor}", + version=version_info, + hexversion=..., + _multiarch=..., + ) + mocker.patch.object(sys, "version_info", version_info) + mocker.patch.object(sys, "implementation", implementation) + mocker.patch.object(sys, "maxsize", 2**arch // 2 - 1) + spec = Python._python_spec_for_sys_executable() # noqa: SLF001 + assert spec.implementation == impl + assert spec.major == major + assert spec.minor == minor + assert spec.architecture == arch