Skip to content

Commit db1c2b7

Browse files
delta87patchback[bot]
authored andcommitted
Fix crash when directory is removed during collection (#13086)
Fixes #13083 --------- Co-authored-by: Bruno Oliveira <[email protected]> (cherry picked from commit 3214263)
1 parent 2c93423 commit db1c2b7

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ Ran Benita
353353
Raphael Castaneda
354354
Raphael Pierzina
355355
Rafal Semik
356+
Reza Mousavi
356357
Raquel Alegre
357358
Ravi Chandra
358359
Reagan Lee

changelog/13083.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed issue where pytest could crash if one of the collected directories got removed during collection.

src/_pytest/pathlib.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -955,17 +955,24 @@ 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
"""
959960
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.
961+
# Attempt to create a scandir iterator for the given path.
962+
try:
963+
scandir_iter = os.scandir(path)
964+
except FileNotFoundError:
965+
# If the directory does not exist, return an empty list.
966+
return []
967+
# Use the scandir iterator in a context manager to ensure it is properly closed.
968+
with scandir_iter as s:
963969
for entry in s:
964970
try:
965971
entry.is_file()
966972
except OSError as err:
967973
if _ignore_error(err):
968974
continue
975+
# Reraise non-ignorable errors to avoid hiding issues.
969976
raise
970977
entries.append(entry)
971978
entries.sort(key=sort_key) # type: ignore[arg-type]

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() -> None:
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("some permission error")
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="some permission error"):
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)