Repository: pytest-dev/pyfakefs (python)
Issue: RuntimeError when call os.remove during os.scandir
- The Operating System
macos 10.15

- The Python version
3.7.5

- The Module version
pytest 7.4.0
pyfakefs 5.5.0

- A minimal example to reproduce the problem (preferably in the form of a failing test)
```python
import os


def test_file_removed_during_scandir(fs):
    fs.create_file('/wls/1.log')
    fs.create_file('/wls/2.log')
    with os.scandir('/wls') as it:
        for entry in it:
            if entry.is_file():
                os.remove(entry.path)
    assert not os.path.exists('/wls/1.log')
```


- The stack trace in case of an unexpected exception. 
```
    File ".../test.py", line 7, in test_file_removed_during_scandir
        for entry in it:
    File ".../pyfakefs/fake_scandir.py", line 149, in __next__
        entry = self.entry_iter.__next__()
RuntimeError: dictionary changed size during iteration
```

After modified code on `fake_scandir.ScanDirIter.__init__` as blow, the test works.
```python
    def __init__(self, filesystem, path):
        ...
        entries = self.filesystem.confirmdir(self.abspath, check_exe_perm=False).entries
        self.entry_iter = iter(list(entries))
```

Pull request description: Allow removing entries while iterating over scandir results
- fixes #1051

Trivial fix - create iterator from tuple of results.

#### Tasks
- [x] Unit tests added that reproduce the issue or prove feature is working
- [x] Fix or feature added
- [x] Entry to release notes added
- [x] Pre-commit CI shows no errors
- [x] Unit tests passing


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 23452ef..038457a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -19,6 +19,9 @@ The released versions correspond to PyPI releases.
 * added support for `os.fchmod`, allow file descriptor argument for `os.chmod` only for POSIX
   for Python < 3.13
 
+### Fixes
+* removing files while iterating over `scandir` results is now possible (see [#1051](../../issues/1051))
+
 ## [Version 5.6.0](https://pypi.python.org/pypi/pyfakefs/5.6.0) (2024-07-12)
 Adds preliminary Python 3.13 support.
 
diff --git a/pyfakefs/fake_scandir.py b/pyfakefs/fake_scandir.py
index 443c259..fff2538 100644
--- a/pyfakefs/fake_scandir.py
+++ b/pyfakefs/fake_scandir.py
@@ -138,7 +138,7 @@ class ScanDirIter:
             self.abspath = self.filesystem.absnormpath(path)
             self.path = to_string(path)
         entries = self.filesystem.confirmdir(self.abspath, check_exe_perm=False).entries
-        self.entry_iter = iter(entries)
+        self.entry_iter = iter(tuple(entries))
 
     def __iter__(self):
         return self


Test patch:
diff --git a/pyfakefs/tests/fake_os_test.py b/pyfakefs/tests/fake_os_test.py
index 10c608e..a110207 100644
--- a/pyfakefs/tests/fake_os_test.py
+++ b/pyfakefs/tests/fake_os_test.py
@@ -5452,6 +5452,20 @@ class FakeScandirTest(FakeOsModuleTestBase):
         children = [dir_entry.name for dir_entry in self.os.scandir(fd)]
         assert sorted(children) == ["file1", "file2", "subdir"]
 
+    def test_file_removed_during_scandir(self):
+        # regression test for #1051
+        dir_path = self.make_path("wls")
+        file1_path = self.os.path.join(dir_path, "1.log")
+        self.create_file(file1_path)
+        file2_path = self.os.path.join(dir_path, "2.log")
+        self.create_file(file2_path)
+        with self.os.scandir(dir_path) as it:
+            for entry in it:
+                if entry.is_file():
+                    self.os.remove(entry.path)
+        assert not self.os.path.exists(file1_path)
+        assert not self.os.path.exists(file2_path)
+
     def check_stat(
         self, absolute_symlink_expected_size, relative_symlink_expected_size
     ):