From 7f411a6aef714bab906bc3e22e240425ec0eeda7 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 4 Dec 2024 22:35:02 +0000 Subject: [PATCH 1/4] correct virtualenv bin dir under mingw python --- nox/virtualenv.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nox/virtualenv.py b/nox/virtualenv.py index dcf2b0f7..a8a6d283 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -24,6 +24,7 @@ import shutil import subprocess import sys +import sysconfig from pathlib import Path from socket import gethostbyname from typing import TYPE_CHECKING, Any, ClassVar @@ -75,6 +76,7 @@ def __dir__() -> list[str]: ] ) _SYSTEM = platform.system() +_IS_MINGW = sysconfig.get_platform().startswith("mingw") def find_uv() -> tuple[bool, str]: @@ -630,7 +632,7 @@ def _resolved_interpreter(self) -> str: @property def bin_paths(self) -> list[str]: """Returns the location of the virtualenv's bin folder.""" - if _SYSTEM == "Windows": + if _SYSTEM == "Windows" and not _IS_MINGW: return [os.path.join(self.location, "Scripts")] return [os.path.join(self.location, "bin")] From 53d37870fe50c3a593ee692a0a14d61ffb34edc2 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 12 Dec 2024 21:08:12 -0500 Subject: [PATCH 2/4] fix: use sys.platform instead of platform.system Signed-off-by: Henry Schreiner --- nox/virtualenv.py | 14 +++++++------- noxfile.py | 3 +-- tests/test_command.py | 10 +++++----- tests/test_virtualenv.py | 36 ++++++++++++++++++------------------ 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/nox/virtualenv.py b/nox/virtualenv.py index a8a6d283..8a83a106 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -19,12 +19,10 @@ import functools import json import os -import platform import re import shutil import subprocess import sys -import sysconfig from pathlib import Path from socket import gethostbyname from typing import TYPE_CHECKING, Any, ClassVar @@ -65,6 +63,10 @@ def __dir__() -> list[str]: return __all__ +# Use for test mocking and to make mypy happy +_PLATFORM = sys.platform + + # Problematic environment variables that are stripped from all commands inside # of a virtualenv. See https://github.com/theacodes/nox/issues/44 _BLACKLISTED_ENV_VARS = frozenset( @@ -75,8 +77,6 @@ def __dir__() -> list[str]: "UV_SYSTEM_PYTHON", ] ) -_SYSTEM = platform.system() -_IS_MINGW = sysconfig.get_platform().startswith("mingw") def find_uv() -> tuple[bool, str]: @@ -361,7 +361,7 @@ def _clean_location(self) -> bool: def bin_paths(self) -> list[str]: """Returns the location of the conda env's bin folder.""" # see https://github.com/conda/conda/blob/f60f0f1643af04ed9a51da3dd4fa242de81e32f4/conda/activate.py#L563-L572 - if _SYSTEM == "Windows": + if _PLATFORM.startswith("win"): return [ self.location, os.path.join(self.location, "Library", "mingw-w64", "bin"), @@ -603,7 +603,7 @@ def _resolved_interpreter(self) -> str: # The rest of this is only applicable to Windows, so if we don't have # an interpreter by now, raise. - if _SYSTEM != "Windows": + if not _PLATFORM.startswith("win"): self._resolved = InterpreterNotFound(self.interpreter) raise self._resolved @@ -632,7 +632,7 @@ def _resolved_interpreter(self) -> str: @property def bin_paths(self) -> list[str]: """Returns the location of the virtualenv's bin folder.""" - if _SYSTEM == "Windows" and not _IS_MINGW: + if _PLATFORM.startswith("win"): return [os.path.join(self.location, "Scripts")] return [os.path.join(self.location, "bin")] diff --git a/noxfile.py b/noxfile.py index 75dd7e3f..7c23c90f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -18,14 +18,13 @@ import contextlib import functools import os -import platform import shutil import sqlite3 import sys import nox -ON_WINDOWS_CI = "CI" in os.environ and platform.system() == "Windows" +ON_WINDOWS_CI = "CI" in os.environ and sys.platform.startswith("win32") nox.needs_version = ">=2024.4.15" nox.options.default_venv_backend = "uv|virtualenv" diff --git a/tests/test_command.py b/tests/test_command.py index dd1af5f1..3811587e 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -36,12 +36,12 @@ PYTHON = sys.executable skip_on_windows_primary_console_session = pytest.mark.skipif( - platform.system() == "Windows" and "SECONDARY_CONSOLE_SESSION" not in os.environ, + sys.platform.startswith("win") and "SECONDARY_CONSOLE_SESSION" not in os.environ, reason="On Windows, this test must run in a separate console session.", ) only_on_windows = pytest.mark.skipif( - platform.system() != "Windows", reason="Only run this test on Windows." + not sys.platform.startswith("win"), reason="Only run this test on Windows." ) @@ -127,7 +127,7 @@ def test_run_verbosity_failed_command( @pytest.mark.skipif( - platform.system() == "Windows", + sys.platform.startswith("win"), reason="See https://github.com/python/cpython/issues/85815", ) def test_run_non_str() -> None: @@ -285,7 +285,7 @@ def enable_ctrl_c(*, enabled: bool) -> None: def interrupt_process(proc: subprocess.Popen[Any]) -> None: """Send SIGINT or CTRL_C_EVENT to the process.""" - if platform.system() == "Windows": + if sys.platform.startswith("win"): # Disable Ctrl-C so we don't terminate ourselves. enable_ctrl_c(enabled=False) @@ -301,7 +301,7 @@ def command_with_keyboard_interrupt( monkeypatch: pytest.MonkeyPatch, marker: Any ) -> None: """Monkeypatch Popen.communicate to raise KeyboardInterrupt.""" - if platform.system() == "Windows": + if sys.platform.startswith("win"): # Enable Ctrl-C because the child inherits the setting from us. enable_ctrl_c(enabled=True) diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py index a05885cd..b5c1aa3b 100644 --- a/tests/test_virtualenv.py +++ b/tests/test_virtualenv.py @@ -38,7 +38,7 @@ from nox.virtualenv import CondaEnv, ProcessEnv, VirtualEnv -IS_WINDOWS = nox.virtualenv._SYSTEM == "Windows" +IS_WINDOWS = sys.platform.startswith("win") HAS_CONDA = shutil.which("conda") is not None HAS_UV = shutil.which("uv") is not None RAISE_ERROR = "RAISE_ERROR" @@ -249,7 +249,7 @@ def test_conda_env_create_verbose( assert kwargs["log"] -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") def test_condaenv_bin_windows(make_conda: Callable[..., tuple[CondaEnv, Path]]) -> None: venv, dir_ = make_conda() assert [ @@ -406,7 +406,7 @@ def test_bin_paths( assert str(dir_.joinpath("Scripts" if IS_WINDOWS else "bin")) == venv.bin -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") def test_bin_windows( make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]], ) -> None: @@ -899,7 +899,7 @@ def test__resolved_interpreter_none( ("2.7.15", "python2.7"), ], ) -@mock.patch("nox.virtualenv._SYSTEM", new="Linux") +@mock.patch("nox.virtualenv._PLATFORM", new="linux") @mock.patch.object(shutil, "which", return_value=True) def test__resolved_interpreter_numerical_non_windows( which: mock.Mock, @@ -914,7 +914,7 @@ def test__resolved_interpreter_numerical_non_windows( @pytest.mark.parametrize("input_", ["2.", "2.7."]) -@mock.patch("nox.virtualenv._SYSTEM", new="Linux") +@mock.patch("nox.virtualenv._PLATFORM", new="linux") @mock.patch.object(shutil, "which", return_value=False) def test__resolved_interpreter_invalid_numerical_id( which: mock.Mock, @@ -929,7 +929,7 @@ def test__resolved_interpreter_invalid_numerical_id( which.assert_called_once_with(input_) -@mock.patch("nox.virtualenv._SYSTEM", new="Linux") +@mock.patch("nox.virtualenv._PLATFORM", new="linux") @mock.patch.object(shutil, "which", return_value=False) def test__resolved_interpreter_32_bit_non_windows( which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]] @@ -941,7 +941,7 @@ def test__resolved_interpreter_32_bit_non_windows( which.assert_called_once_with("3.6-32") -@mock.patch("nox.virtualenv._SYSTEM", new="Linux") +@mock.patch("nox.virtualenv._PLATFORM", new="linux") @mock.patch.object(shutil, "which", return_value=True) def test__resolved_interpreter_non_windows( which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]] @@ -954,7 +954,7 @@ def test__resolved_interpreter_non_windows( which.assert_called_once_with("python3.6") -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch.object(shutil, "which") def test__resolved_interpreter_windows_full_path( which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]] @@ -976,7 +976,7 @@ def test__resolved_interpreter_windows_full_path( ("2.7-32", r"c:\python27\python.exe"), ], ) -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch.object(subprocess, "run") @mock.patch.object(shutil, "which") def test__resolved_interpreter_windows_pyexe( @@ -1011,7 +1011,7 @@ def special_run(cmd: str, *args: str, **kwargs: object) -> TextProcessResult: which.assert_has_calls([mock.call(input_), mock.call("py")]) -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch.object(subprocess, "run") @mock.patch.object(shutil, "which") def test__resolved_interpreter_windows_pyexe_fails( @@ -1020,8 +1020,8 @@ def test__resolved_interpreter_windows_pyexe_fails( # Establish that if the py launcher fails, we give the right error. venv, _ = make_one(interpreter="python3.6") - # Trick the nox.virtualenv._SYSTEM into thinking that it cannot find python3.6 - # (it likely will on Unix). Also, when the nox.virtualenv._SYSTEM looks for the + # Trick the nox.virtualenv into thinking that it cannot find python3.6 + # (it likely will on Unix). Also, when the nox.virtualenv looks for the # py launcher, give it a dummy that fails. def special_run(cmd: str, *args: str, **kwargs: object) -> TextProcessResult: # noqa: ARG001 return TextProcessResult("", 1) @@ -1036,7 +1036,7 @@ def special_run(cmd: str, *args: str, **kwargs: object) -> TextProcessResult: # which.assert_has_calls([mock.call("python3.6"), mock.call("py")]) -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch("nox.virtualenv.UV_PYTHON_SUPPORT", new=False) def test__resolved_interpreter_windows_path_and_version( make_one: Callable[..., tuple[VirtualEnv, Path]], @@ -1065,7 +1065,7 @@ def test__resolved_interpreter_windows_path_and_version( @pytest.mark.parametrize("input_", ["2.7", "python3.7", "goofy"]) @pytest.mark.parametrize("sysfind_result", [r"c:\python37-x64\python.exe", None]) @pytest.mark.parametrize("sysexec_result", ["3.7.3\\n", RAISE_ERROR]) -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch("nox.virtualenv.UV_PYTHON_SUPPORT", new=False) def test__resolved_interpreter_windows_path_and_version_fails( input_: str, @@ -1089,7 +1089,7 @@ def test__resolved_interpreter_windows_path_and_version_fails( print(venv._resolved_interpreter) -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch.object(shutil, "which") def test__resolved_interpreter_not_found( which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]] @@ -1106,7 +1106,7 @@ def test__resolved_interpreter_not_found( print(venv._resolved_interpreter) -@mock.patch("nox.virtualenv._SYSTEM", new="Windows") +@mock.patch("nox.virtualenv._PLATFORM", new="win32") @mock.patch("nox.virtualenv.locate_via_py", new=lambda _: None) # type: ignore[misc] # noqa: PT008 def test__resolved_interpreter_nonstandard( make_one: Callable[..., tuple[VirtualEnv, Path]], @@ -1119,7 +1119,7 @@ def test__resolved_interpreter_nonstandard( print(venv._resolved_interpreter) -@mock.patch("nox.virtualenv._SYSTEM", new="Linux") +@mock.patch("nox.virtualenv._PLATFORM", new="linux") @mock.patch.object(shutil, "which", return_value=True) def test__resolved_interpreter_cache_result( which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]] @@ -1135,7 +1135,7 @@ def test__resolved_interpreter_cache_result( assert which.call_count == 1 -@mock.patch("nox.virtualenv._SYSTEM", new="Linux") +@mock.patch("nox.virtualenv._PLATFORM", new="linux") @mock.patch.object(shutil, "which", return_value=None) def test__resolved_interpreter_cache_failure( which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]] From 509916fb0c4d5ebf0395792e268f698055963a06 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sat, 28 Dec 2024 15:34:19 +0000 Subject: [PATCH 3/4] continue with _IS_MINGW flag --- nox/virtualenv.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nox/virtualenv.py b/nox/virtualenv.py index 8a83a106..87df09ec 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -23,6 +23,7 @@ import shutil import subprocess import sys +import sysconfig from pathlib import Path from socket import gethostbyname from typing import TYPE_CHECKING, Any, ClassVar @@ -65,6 +66,7 @@ def __dir__() -> list[str]: # Use for test mocking and to make mypy happy _PLATFORM = sys.platform +_IS_MINGW = sysconfig.get_platform().startswith("mingw") # Problematic environment variables that are stripped from all commands inside @@ -632,7 +634,7 @@ def _resolved_interpreter(self) -> str: @property def bin_paths(self) -> list[str]: """Returns the location of the virtualenv's bin folder.""" - if _PLATFORM.startswith("win"): + if _PLATFORM.startswith("win") and not _IS_MINGW: return [os.path.join(self.location, "Scripts")] return [os.path.join(self.location, "bin")] From 2a42c2267ab831a0008e50c21877e78568d677ed Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 31 Dec 2024 20:50:30 +0000 Subject: [PATCH 4/4] add mocked test for mingw --- tests/test_virtualenv.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py index b5c1aa3b..e364ea8d 100644 --- a/tests/test_virtualenv.py +++ b/tests/test_virtualenv.py @@ -417,6 +417,18 @@ def test_bin_windows( assert str(dir_.joinpath("Scripts")) == venv.bin +@mock.patch("nox.virtualenv._PLATFORM", new="win32") +@mock.patch("nox.virtualenv._IS_MINGW", new=True) +def test_bin_windows_mingw( + make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]], +) -> None: + venv, dir_ = make_one() + assert venv.bin_paths + assert len(venv.bin_paths) == 1 + assert venv.bin_paths[0] == venv.bin + assert str(dir_.joinpath("bin")) == venv.bin + + def test_create( monkeypatch: pytest.MonkeyPatch, make_one: Callable[..., tuple[VirtualEnv, Path]],