-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support Windows CPython interpreters distributed by non-standard orgs (…
…#2504) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
- Loading branch information
1 parent
8cef45f
commit 5a53614
Showing
6 changed files
with
222 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Discover CPython implementations distributed on Windows by any organization - by :user:`faph`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from contextlib import contextmanager | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
|
||
@pytest.fixture() | ||
def _mock_registry(mocker): | ||
from virtualenv.discovery.windows.pep514 import winreg | ||
|
||
loc, glob = {}, {} | ||
mock_value_str = (Path(__file__).parent / "winreg-mock-values.py").read_text() | ||
exec(mock_value_str, glob, loc) | ||
enum_collect = loc["enum_collect"] | ||
value_collect = loc["value_collect"] | ||
key_open = loc["key_open"] | ||
hive_open = loc["hive_open"] | ||
|
||
def _enum_key(key, at): | ||
key_id = key.value if isinstance(key, Key) else key | ||
result = enum_collect[key_id][at] | ||
if isinstance(result, OSError): | ||
raise result | ||
return result | ||
|
||
mocker.patch.object(winreg, "EnumKey", side_effect=_enum_key) | ||
|
||
def _query_value_ex(key, value_name): | ||
key_id = key.value if isinstance(key, Key) else key | ||
result = value_collect[key_id][value_name] | ||
if isinstance(result, OSError): | ||
raise result | ||
return result | ||
|
||
mocker.patch.object(winreg, "QueryValueEx", side_effect=_query_value_ex) | ||
|
||
class Key: | ||
def __init__(self, value): | ||
self.value = value | ||
|
||
def __enter__(self): | ||
return self | ||
|
||
def __exit__(self, exc_type, exc_val, exc_tb): # noqa: U100 | ||
return None | ||
|
||
@contextmanager | ||
def _open_key_ex(*args): | ||
if len(args) == 2: | ||
key, value = args | ||
key_id = key.value if isinstance(key, Key) else key | ||
result = Key(key_open[key_id][value]) # this needs to be something that can be with-ed, so let's wrap it | ||
elif len(args) == 4: | ||
result = hive_open[args] | ||
else: | ||
raise RuntimeError | ||
value = result.value if isinstance(result, Key) else result | ||
if isinstance(value, OSError): | ||
raise value | ||
yield result | ||
|
||
mocker.patch.object(winreg, "OpenKeyEx", side_effect=_open_key_ex) | ||
mocker.patch("os.path.exists", return_value=True) | ||
|
||
|
||
def _mock_pyinfo(major, minor, arch, exe): | ||
"""Return PythonInfo objects with essential metadata set for the given args""" | ||
from virtualenv.discovery.py_info import PythonInfo, VersionInfo | ||
|
||
info = PythonInfo() | ||
info.base_prefix = str(Path(exe).parent) | ||
info.executable = info.original_executable = info.system_executable = exe | ||
info.implementation = "CPython" | ||
info.architecture = arch | ||
info.version_info = VersionInfo(major, minor, 0, "final", 0) | ||
return info | ||
|
||
|
||
@pytest.fixture() | ||
def _populate_pyinfo_cache(monkeypatch): | ||
"""Add metadata to virtualenv.discovery.cached_py_info._CACHE for all (mocked) registry entries""" | ||
import virtualenv.discovery.cached_py_info | ||
|
||
# Data matches _mock_registry fixture | ||
interpreters = [ | ||
("ContinuumAnalytics", 3, 10, 32, "C:\\Users\\user\\Miniconda3\\python.exe", None), | ||
("ContinuumAnalytics", 3, 10, 64, "C:\\Users\\user\\Miniconda3-64\\python.exe", None), | ||
("PythonCore", 3, 9, 64, "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python36\\python.exe", None), | ||
("PythonCore", 3, 9, 64, "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python36\\python.exe", None), | ||
("PythonCore", 3, 5, 64, "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python35\\python.exe", None), | ||
("PythonCore", 3, 9, 64, "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python36\\python.exe", None), | ||
("PythonCore", 3, 7, 32, "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python37-32\\python.exe", None), | ||
("PythonCore", 3, 12, 64, "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python312\\python.exe", None), | ||
("PythonCore", 2, 7, 64, "C:\\Python27\\python.exe", None), | ||
("PythonCore", 3, 4, 64, "C:\\Python34\\python.exe", None), | ||
("CompanyA", 3, 6, 64, "Z:\\CompanyA\\Python\\3.6\\python.exe", None), | ||
] | ||
for _, major, minor, arch, exe, _ in interpreters: | ||
info = _mock_pyinfo(major, minor, arch, exe) | ||
monkeypatch.setitem(virtualenv.discovery.cached_py_info._CACHE, Path(info.executable), info) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import sys | ||
|
||
import pytest | ||
|
||
from virtualenv.discovery.py_spec import PythonSpec | ||
|
||
|
||
@pytest.mark.skipif(sys.platform != "win32", reason="no Windows registry") | ||
@pytest.mark.usefixtures("_mock_registry") | ||
@pytest.mark.usefixtures("_populate_pyinfo_cache") | ||
@pytest.mark.parametrize( | ||
("string_spec", "expected_exe"), | ||
[ | ||
# 64-bit over 32-bit | ||
("python3.10", "C:\\Users\\user\\Miniconda3-64\\python.exe"), | ||
("cpython3.10", "C:\\Users\\user\\Miniconda3-64\\python.exe"), | ||
# 1 installation of 3.9 available | ||
("python3.12", "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"), | ||
("cpython3.12", "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"), | ||
# resolves to highest available version | ||
("python", "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"), | ||
("cpython", "C:\\Users\\user\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"), | ||
# Non-standard org name | ||
("python3.6", "Z:\\CompanyA\\Python\\3.6\\python.exe"), | ||
("cpython3.6", "Z:\\CompanyA\\Python\\3.6\\python.exe"), | ||
], | ||
) | ||
def test_propose_interpreters(string_spec, expected_exe): | ||
from virtualenv.discovery.windows import propose_interpreters | ||
|
||
spec = PythonSpec.from_string_spec(string_spec) | ||
interpreter = next(propose_interpreters(spec=spec, cache_dir=None, env=None)) | ||
assert interpreter.executable == expected_exe |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.