Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions tests/test_cached_py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
import os
import sys
from pathlib import Path
from subprocess import TimeoutExpired
from typing import TYPE_CHECKING
from subprocess import TimeoutExpired, list2cmdline
from typing import TYPE_CHECKING, Final
from unittest.mock import MagicMock, patch

import pytest

from python_discovery import DiskCache, PythonInfo
from python_discovery._cached_py_info import (
LogCmd,
_get_from_cache,
_get_via_file_cache,
_load_cached_py_info,
_resolve_py_info_script,
Expand Down Expand Up @@ -323,3 +324,67 @@ def test_from_exe_cache_entry_with_bad_content(tmp_path: Path) -> None:
required = _get_via_file_cache(PythonInfo, cache, path, sys.executable, env)
assert isinstance(required, PythonInfo)
assert isinstance(store.read(), dict)


_LAUNCHER: Final[str] = r"C:\Program Files\Python Launcher\py.exe"
_ARGS: Final[str] = '-3.11 --config "settings folder\\python.json"'
_SELECTED: Final[str] = r"C:\Python311\python.exe"


@pytest.fixture
def popen(mocker: MockerFixture) -> MagicMock:
start, end = "a" * 32, "b" * 32
mocker.patch("python_discovery._cached_py_info.gen_cookie", side_effect=[start, end])
info = PythonInfo()
info.executable = _SELECTED
out = f"{start[::-1]}{info.to_json()}{end[::-1]}"
process = MagicMock(**{"communicate.return_value": (out, ""), "returncode": 0})
return mocker.patch("python_discovery._cached_py_info.Popen", return_value=process)


def test_executable_args_precede_query_script(popen: MagicMock) -> None:
_run_subprocess(PythonInfo, _LAUNCHER, dict(os.environ), _ARGS)

cmd = popen.call_args.args[0]
assert cmd.startswith(f"{list2cmdline([_LAUNCHER])} {_ARGS} ")
assert cmd.endswith(f" {'a' * 32} {'b' * 32}")


@pytest.mark.usefixtures("popen")
def test_executable_args_report_the_selected_interpreter() -> None:
failure, result = _run_subprocess(PythonInfo, _LAUNCHER, dict(os.environ), _ARGS)

assert failure is None
assert result is not None
assert result.executable == _SELECTED


def test_executable_args_disk_entry_not_reused_without_args(tmp_path: Path, mocker: MockerFixture) -> None:
with_args, without_args = PythonInfo(), PythonInfo()
with_args.executable = "selected"
query = mocker.patch(
"python_discovery._cached_py_info._run_subprocess",
side_effect=[(None, with_args), (None, without_args)],
)
cache, path, env = DiskCache(tmp_path), Path(sys.executable), dict(os.environ)
_get_via_file_cache(PythonInfo, cache, path, sys.executable, env, executable_args=_ARGS)

result = _get_via_file_cache(PythonInfo, cache, path, sys.executable, env)

assert result is without_args
assert query.call_count == 2


def test_executable_args_memory_cache_keyed_by_args(tmp_path: Path, mocker: MockerFixture) -> None:
mocker.patch.dict("python_discovery._cached_py_info._CACHE", clear=True)
first, second = PythonInfo(), PythonInfo()
query = mocker.patch("python_discovery._cached_py_info._get_via_file_cache", side_effect=[first, second])
cache, env = DiskCache(tmp_path), dict(os.environ)

results = [
_get_from_cache(PythonInfo, cache, _LAUNCHER, env, ignore_cache=False, executable_args=args)
for args in ("-3.11", "-3.12", "-3.11")
]

assert results == [first, second, first]
assert query.call_count == 2
79 changes: 0 additions & 79 deletions tests/test_cached_py_info_executable_args.py

This file was deleted.