Skip to content

Commit 884b911

Browse files
committed
Fix crash when passing a very long cmdline argument (pytest-dev#11404)
Fixes pytest-dev#11394 (cherry picked from commit 28ccf47)
1 parent 6e49a74 commit 884b911

File tree

5 files changed

+77
-1
lines changed

5 files changed

+77
-1
lines changed

changelog/11394.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed crash when parsing long command line arguments that might be interpreted as files.

src/_pytest/main.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from _pytest.pathlib import absolutepath
3737
from _pytest.pathlib import bestrelpath
3838
from _pytest.pathlib import fnmatch_ex
39+
from _pytest.pathlib import safe_exists
3940
from _pytest.pathlib import visit
4041
from _pytest.reports import CollectReport
4142
from _pytest.reports import TestReport
@@ -895,7 +896,7 @@ def resolve_collection_argument(
895896
strpath = search_pypath(strpath)
896897
fspath = invocation_path / strpath
897898
fspath = absolutepath(fspath)
898-
if not fspath.exists():
899+
if not safe_exists(fspath):
899900
msg = (
900901
"module or package not found: {arg} (missing __init__.py?)"
901902
if as_pypath

src/_pytest/pathlib.py

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import atexit
22
import contextlib
3+
import errno
34
import fnmatch
45
import importlib.util
56
import itertools
@@ -791,3 +792,13 @@ def copytree(source: Path, target: Path) -> None:
791792
shutil.copyfile(x, newx)
792793
elif x.is_dir():
793794
newx.mkdir(exist_ok=True)
795+
796+
797+
def safe_exists(p: Path) -> bool:
798+
"""Like Path.exists(), but account for input arguments that might be too long (#11394)."""
799+
try:
800+
return p.exists()
801+
except OSError as e:
802+
if e.errno == errno.ENAMETOOLONG:
803+
return False
804+
raise

testing/test_main.py

+31
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,34 @@ def test(fix):
262262
"* 1 passed in *",
263263
]
264264
)
265+
266+
267+
def test_very_long_cmdline_arg(pytester: Pytester) -> None:
268+
"""
269+
Regression test for #11394.
270+
271+
Note: we could not manage to actually reproduce the error with this code, we suspect
272+
GitHub runners are configured to support very long paths, however decided to leave
273+
the test in place in case this ever regresses in the future.
274+
"""
275+
pytester.makeconftest(
276+
"""
277+
import pytest
278+
279+
def pytest_addoption(parser):
280+
parser.addoption("--long-list", dest="long_list", action="store", default="all", help="List of things")
281+
282+
@pytest.fixture(scope="module")
283+
def specified_feeds(request):
284+
list_string = request.config.getoption("--long-list")
285+
return list_string.split(',')
286+
"""
287+
)
288+
pytester.makepyfile(
289+
"""
290+
def test_foo(specified_feeds):
291+
assert len(specified_feeds) == 100_000
292+
"""
293+
)
294+
result = pytester.runpytest("--long-list", ",".join(["helloworld"] * 100_000))
295+
result.stdout.fnmatch_lines("* 1 passed *")

testing/test_pathlib.py

+32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import errno
12
import os.path
23
import pickle
34
import sys
@@ -24,6 +25,7 @@
2425
from _pytest.pathlib import maybe_delete_a_numbered_dir
2526
from _pytest.pathlib import module_name_from_path
2627
from _pytest.pathlib import resolve_package_path
28+
from _pytest.pathlib import safe_exists
2729
from _pytest.pathlib import symlink_or_skip
2830
from _pytest.pathlib import visit
2931
from _pytest.tmpdir import TempPathFactory
@@ -660,3 +662,33 @@ def __init__(self) -> None:
660662

661663
mod = import_path(init, root=tmp_path, mode=ImportMode.importlib)
662664
assert len(mod.instance.INSTANCES) == 1
665+
666+
667+
def test_safe_exists(tmp_path: Path) -> None:
668+
d = tmp_path.joinpath("some_dir")
669+
d.mkdir()
670+
assert safe_exists(d) is True
671+
672+
f = tmp_path.joinpath("some_file")
673+
f.touch()
674+
assert safe_exists(f) is True
675+
676+
# Use unittest.mock() as a context manager to have a very narrow
677+
# patch lifetime.
678+
p = tmp_path.joinpath("some long filename" * 100)
679+
with unittest.mock.patch.object(
680+
Path,
681+
"exists",
682+
autospec=True,
683+
side_effect=OSError(errno.ENAMETOOLONG, "name too long"),
684+
):
685+
assert safe_exists(p) is False
686+
687+
with unittest.mock.patch.object(
688+
Path,
689+
"exists",
690+
autospec=True,
691+
side_effect=OSError(errno.EIO, "another kind of error"),
692+
):
693+
with pytest.raises(OSError):
694+
_ = safe_exists(p)

0 commit comments

Comments
 (0)