@@ -114,6 +114,19 @@ def special_which(name: str, path: Any = None) -> str | None: # noqa: ARG001
114114
115115 monkeypatch .setattr (shutil , "which" , special_which )
116116
117+ # Also mock python-discovery's get_interpreter for backwards compatibility
118+ # with tests that rely on the mocked shutil.which behavior
119+ def mock_get_interpreter (* args : object , ** kwargs : object ) -> None :
120+ # For tests, we want to respect who the traditional discovery would have found
121+ # Since special_which returns None for unknown interpreters, we also return None
122+ return None
123+
124+ monkeypatch .setattr (
125+ nox .virtualenv ,
126+ "_python_discovery_get_interpreter" ,
127+ lambda : mock_get_interpreter ,
128+ )
129+
117130 def special_run (cmd : Any , * args : Any , ** kwargs : Any ) -> TextProcessResult : # noqa: ARG001
118131 return TextProcessResult (sysexec_result )
119132
@@ -1596,12 +1609,26 @@ def test_download_python_never_missing_interpreter(
15961609 pbs_install_mock : mock .Mock ,
15971610 venv_backend : str ,
15981611 make_one : Callable [..., tuple [VirtualEnv , Path ]],
1612+ monkeypatch : pytest .MonkeyPatch ,
15991613) -> None :
1614+ """Test that InterpreterNotFound is raised when interpreter is missing."""
1615+
1616+ def mock_get_interpreter (* args : object , ** kwargs : object ) -> None :
1617+ return None
1618+
1619+ # Mock at the nox module level to prevent python-discovery from finding
1620+ monkeypatch .setattr (
1621+ nox .virtualenv ,
1622+ "_python_discovery_get_interpreter" ,
1623+ lambda : mock_get_interpreter ,
1624+ )
1625+
16001626 venv , _ = make_one (
16011627 interpreter = "python3.11" ,
16021628 venv_backend = venv_backend ,
16031629 download_python = "never" ,
16041630 )
1631+
16051632 with pytest .raises (nox .virtualenv .InterpreterNotFound ):
16061633 _ = venv ._resolved_interpreter
16071634
@@ -1656,7 +1683,19 @@ def test_download_python_auto_missing_interpreter(
16561683 pbs_install_mock : mock .Mock ,
16571684 venv_backend : str ,
16581685 make_one : Callable [..., tuple [VirtualEnv , Path ]],
1686+ monkeypatch : pytest .MonkeyPatch ,
16591687) -> None :
1688+
1689+ # Mock python-discovery to return None (simulating missing interpreter)
1690+ def mock_get_interpreter (* args : object , ** kwargs : object ) -> None :
1691+ return None
1692+
1693+ monkeypatch .setattr (
1694+ nox .virtualenv ,
1695+ "_python_discovery_get_interpreter" ,
1696+ lambda : mock_get_interpreter ,
1697+ )
1698+
16601699 venv , _ = make_one (
16611700 interpreter = "python3.11" ,
16621701 venv_backend = venv_backend ,
@@ -1688,14 +1727,26 @@ def test_download_python_auto_missing_interpreter(
16881727 "nox.virtualenv.uv_install_python" ,
16891728 return_value = True ,
16901729)
1691- @mock .patch .object (shutil , "which" , return_value = "/usr/bin/python3.11" )
1730+ @mock .patch .object (shutil , "which" , return_value = None )
16921731def test_download_python_always_preexisting_interpreter (
16931732 which : mock .Mock ,
16941733 uv_install_mock : mock .Mock ,
16951734 pbs_install_mock : mock .Mock ,
16961735 venv_backend : str ,
16971736 make_one : Callable [..., tuple [VirtualEnv , Path ]],
1737+ monkeypatch : pytest .MonkeyPatch ,
16981738) -> None :
1739+
1740+ # Mock python-discovery to return None (simulating missing interpreter)
1741+ def mock_get_interpreter (* args : object , ** kwargs : object ) -> None :
1742+ return None
1743+
1744+ monkeypatch .setattr (
1745+ nox .virtualenv ,
1746+ "_python_discovery_get_interpreter" ,
1747+ lambda : mock_get_interpreter ,
1748+ )
1749+
16991750 venv , _ = make_one (
17001751 interpreter = "python3.11" ,
17011752 venv_backend = venv_backend ,
@@ -1728,17 +1779,27 @@ def test_download_python_failed_install(
17281779 download_python : str ,
17291780 venv_backend : str ,
17301781 make_one : Callable [..., tuple [VirtualEnv , Path ]],
1782+ monkeypatch : pytest .MonkeyPatch ,
17311783) -> None :
1784+
1785+ # Mock python-discovery to return None (simulating missing interpreter)
1786+ def mock_get_interpreter (* args : object , ** kwargs : object ) -> None :
1787+ return None
1788+
1789+ monkeypatch .setattr (
1790+ nox .virtualenv ,
1791+ "_python_discovery_get_interpreter" ,
1792+ lambda : mock_get_interpreter ,
1793+ )
1794+ monkeypatch .setattr (shutil , "which" , lambda _x : None )
1795+
17321796 venv , _ = make_one (
17331797 interpreter = "python3.11" ,
17341798 venv_backend = venv_backend ,
17351799 download_python = download_python ,
17361800 )
17371801
1738- with (
1739- mock .patch .object (shutil , "which" , return_value = None ) as _ ,
1740- pytest .raises (nox .virtualenv .InterpreterNotFound ),
1741- ):
1802+ with pytest .raises (nox .virtualenv .InterpreterNotFound ):
17421803 _ = venv ._resolved_interpreter
17431804
17441805 if venv_backend == "uv" :
@@ -1850,8 +1911,20 @@ def test_download_python_uv_unsupported_version(
18501911 uv_install_mock : mock .Mock ,
18511912 download_python : str ,
18521913 make_one : Callable [..., tuple [VirtualEnv , Path ]],
1914+ monkeypatch : pytest .MonkeyPatch ,
18531915) -> None :
18541916 """Test we dont install for unsupported uv versions"""
1917+
1918+ # Mock python-discovery to return None (simulating missing interpreter)
1919+ def mock_get_interpreter (* args : object , ** kwargs : object ) -> None :
1920+ return None
1921+
1922+ monkeypatch .setattr (
1923+ nox .virtualenv ,
1924+ "_python_discovery_get_interpreter" ,
1925+ lambda : mock_get_interpreter ,
1926+ )
1927+
18551928 venv , _ = make_one (
18561929 interpreter = "python3.11" ,
18571930 venv_backend = "uv" ,
@@ -1866,3 +1939,23 @@ def test_download_python_uv_unsupported_version(
18661939 which .assert_not_called ()
18671940 else : # auto
18681941 which .assert_any_call ("python3.11" )
1942+
1943+
1944+ @pytest .mark .parametrize (
1945+ ("spec" , "is_version_range" ),
1946+ [
1947+ (">=3.11,<3.13" , True ),
1948+ (">=3.9" , True ),
1949+ ("<3.14" , True ),
1950+ ("~=3.11" , True ),
1951+ ("==3.12.1" , True ),
1952+ ("!=3.11" , True ),
1953+ ("3.12" , False ),
1954+ ("python3.12" , False ),
1955+ ("pypy3.10" , False ),
1956+ ],
1957+ )
1958+ def test_is_version_range_spec (spec : str , is_version_range : bool ) -> None :
1959+ """Test the _is_version_range_spec function correctly identifies version specs."""
1960+ result = nox .virtualenv ._is_version_range_spec (spec )
1961+ assert result == is_version_range
0 commit comments