Skip to content

Commit 15a1c95

Browse files
delta87nicoddemus
authored andcommitted
Fix scandir() crash by returning [] when directory is not found (pytest-dev#13083)
1 parent bdfc3a9 commit 15a1c95

File tree

4 files changed

+51
-16
lines changed

4 files changed

+51
-16
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ Ran Benita
360360
Raphael Castaneda
361361
Raphael Pierzina
362362
Rafal Semik
363+
Reza Mousavi
363364
Raquel Alegre
364365
Ravi Chandra
365366
Reagan Lee

changelog/13083.bugfix.rst

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
13083.bugfix.rst:
2+
3+
Fix issue where scandir failed for empty or non-existent directories.
4+
5+
- Issue: https://github.com/pytest-dev/pytest/issues/13083
6+
- Authors: Reza Mousavi

src/_pytest/pathlib.py

+20-16
Original file line numberDiff line numberDiff line change
@@ -955,23 +955,27 @@ def scandir(
955955
956956
The returned entries are sorted according to the given key.
957957
The default is to sort by name.
958+
If the directory does not exist, return an empty list.
958959
"""
959-
entries = []
960-
with os.scandir(path) as s:
961-
# Skip entries with symlink loops and other brokenness, so the caller
962-
# doesn't have to deal with it.
963-
for entry in s:
964-
try:
965-
entry.is_file()
966-
except OSError as err:
967-
if _ignore_error(err):
968-
continue
969-
raise
970-
entries.append(entry)
971-
entries.sort(key=sort_key) # type: ignore[arg-type]
972-
return entries
973-
974-
960+
try:
961+
entries = []
962+
with os.scandir(path) as s:
963+
# Skip entries with symlink loops and other brokenness, so the caller
964+
# doesn't have to deal with it.
965+
for entry in s:
966+
try:
967+
entry.is_file()
968+
except OSError as err:
969+
if _ignore_error(err):
970+
continue
971+
raise
972+
entries.append(entry)
973+
entries.sort(key=sort_key) # type: ignore[arg-type]
974+
return entries
975+
except FileNotFoundError:
976+
return []
977+
978+
975979
def visit(
976980
path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool]
977981
) -> Iterator[os.DirEntry[str]]:

testing/test_pathlib.py

+24
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from _pytest.pathlib import resolve_package_path
3939
from _pytest.pathlib import resolve_pkg_root_and_module_name
4040
from _pytest.pathlib import safe_exists
41+
from _pytest.pathlib import scandir
4142
from _pytest.pathlib import spec_matches_module_path
4243
from _pytest.pathlib import symlink_or_skip
4344
from _pytest.pathlib import visit
@@ -569,6 +570,29 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
569570
assert getattr(module, "foo")() == 42
570571

571572

573+
def test_scandir_with_non_existent_directory() -> None:
574+
# Test with a directory that does not exist
575+
non_existent_dir = "path_to_non_existent_dir"
576+
result = scandir(non_existent_dir)
577+
# Assert that the result is an empty list
578+
assert result == []
579+
580+
581+
def test_scandir_handles_os_error():
582+
# Create a mock entry that will raise an OSError when is_file is called
583+
mock_entry = unittest.mock.MagicMock()
584+
mock_entry.is_file.side_effect = OSError("Permission denied")
585+
# Mock os.scandir to return an iterator with our mock entry
586+
with unittest.mock.patch("os.scandir") as mock_scandir:
587+
mock_scandir.return_value.__enter__.return_value = [mock_entry]
588+
# Call the scandir function with a path
589+
# We expect an OSError to be raised here
590+
with pytest.raises(OSError, match="Permission denied"):
591+
scandir("/fake/path")
592+
# Verify that the is_file method was called on the mock entry
593+
mock_entry.is_file.assert_called_once()
594+
595+
572596
class TestImportLibMode:
573597
def test_importmode_importlib_with_dataclass(
574598
self, tmp_path: Path, ns_param: bool

0 commit comments

Comments
 (0)