Skip to content

Commit 19796cf

Browse files
esafakgoogle-labs-jules[bot]pre-commit-ci[bot]
authored
fix: Prevent crash on file in PATH during discovery (#2917)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 01074bc commit 19796cf

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

src/virtualenv/discovery/builtin.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
186186
if path:
187187
for p in map(Path, path.split(os.pathsep)):
188188
with suppress(OSError):
189-
if next(p.iterdir(), None):
189+
if p.is_dir() and next(p.iterdir(), None):
190190
yield p
191191

192192

@@ -202,7 +202,13 @@ def __repr__(self) -> str:
202202
content += " with =>"
203203
for file_path in self.path.iterdir():
204204
try:
205-
if file_path.is_dir() or not (file_path.stat().st_mode & os.X_OK):
205+
if file_path.is_dir():
206+
continue
207+
if IS_WIN:
208+
pathext = self.env.get("PATHEXT", ".COM;.EXE;.BAT;.CMD").split(";")
209+
if not any(file_path.name.upper().endswith(ext) for ext in pathext):
210+
continue
211+
elif not (file_path.stat().st_mode & os.X_OK):
206212
continue
207213
except OSError:
208214
pass

tests/unit/discovery/test_discovery.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
import pytest
1313

14-
from virtualenv.discovery.builtin import Builtin, get_interpreter
14+
from virtualenv.discovery.builtin import Builtin, LazyPathDump, get_interpreter, get_paths
1515
from virtualenv.discovery.py_info import PythonInfo
16-
from virtualenv.info import fs_supports_symlink
16+
from virtualenv.info import IS_WIN, fs_supports_symlink
1717

1818

1919
@pytest.mark.skipif(not fs_supports_symlink(), reason="symlink not supported")
@@ -200,6 +200,14 @@ def test_returns_second_python_specified_when_more_than_one_is_specified_and_env
200200
assert result == mocker.sentinel.python_from_cli
201201

202202

203+
def test_discovery_via_path_with_file(tmp_path, monkeypatch):
204+
a_file = tmp_path / "a_file"
205+
a_file.touch()
206+
monkeypatch.setenv("PATH", str(a_file))
207+
interpreter = get_interpreter(uuid4().hex, [])
208+
assert interpreter is None
209+
210+
203211
def test_absolute_path_does_not_exist(tmp_path):
204212
"""
205213
Test that virtualenv does not fail when an absolute path that does not exist is provided.
@@ -255,6 +263,24 @@ def test_absolute_path_does_not_exist_fails(tmp_path):
255263
assert process.returncode != 0, process.stderr
256264

257265

266+
def test_get_paths_no_path_env(monkeypatch):
267+
monkeypatch.delenv("PATH", raising=False)
268+
paths = list(get_paths({}))
269+
assert paths
270+
271+
272+
def test_lazy_path_dump_debug(monkeypatch, tmp_path):
273+
monkeypatch.setenv("_VIRTUALENV_DEBUG", "1")
274+
a_dir = tmp_path
275+
executable_file = "a_file.exe" if IS_WIN else "a_file"
276+
(a_dir / executable_file).touch(mode=0o755)
277+
(a_dir / "b_file").touch(mode=0o644)
278+
dumper = LazyPathDump(0, a_dir, os.environ)
279+
output = repr(dumper)
280+
assert executable_file in output
281+
assert "b_file" not in output
282+
283+
258284
@pytest.mark.usefixtures("mock_get_interpreter")
259285
def test_returns_first_python_specified_when_no_env_var_is_specified(mocker, monkeypatch, session_app_data):
260286
monkeypatch.delenv("VIRTUALENV_PYTHON", raising=False)

0 commit comments

Comments
 (0)