diff --git a/CHANGES.md b/CHANGES.md index f1e7746c..6325397d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,10 @@ The released versions correspond to PyPI releases. ## Unreleased +### Fixes +* fixes the problem that filesystem patching was still active in the pytest + logreport phase (see [#904](../../issues/904)) + ## [Version 5.3.0](https://pypi.python.org/pypi/pyfakefs/5.3.0) (2023-10-11) Adds official support for Python 3.12. diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py index 78371c0e..1ff8d610 100644 --- a/pyfakefs/fake_filesystem_unittest.py +++ b/pyfakefs/fake_filesystem_unittest.py @@ -628,6 +628,7 @@ def __init__( self._isStale = True self._dyn_patcher: Optional[DynamicPatcher] = None self._patching = False + self._paused = False @classmethod def clear_fs_cache(cls) -> None: @@ -898,6 +899,7 @@ def setUp(self, doctester: Any = None) -> None: def start_patching(self) -> None: if not self._patching: self._patching = True + self._paused = False self.patch_modules() self.patch_functions() @@ -975,10 +977,11 @@ def tearDown(self, doctester: Any = None): else: self.__class__.PATCHER = None - def stop_patching(self) -> None: + def stop_patching(self, temporary=False) -> None: if self._patching: self._isStale = True self._patching = False + self._paused = temporary if self._stubs: self._stubs.smart_unset_all() self.unset_defaults() @@ -986,6 +989,10 @@ def stop_patching(self) -> None: self._dyn_patcher.cleanup() sys.meta_path.pop(0) + @property + def is_patching(self): + return self._patching + def unset_defaults(self) -> None: for fct, idx, ft in self.FS_DEFARGS: new_defaults = [] @@ -1003,7 +1010,7 @@ def pause(self) -> None: Calling pause() twice is silently ignored. """ - self.stop_patching() + self.stop_patching(temporary=True) def resume(self) -> None: """Resume the patching of the file system modules if `pause` has @@ -1011,7 +1018,8 @@ def resume(self) -> None: executed in the fake file system. Does nothing if patching is not paused. """ - self.start_patching() + if self._paused: + self.start_patching() class Pause: diff --git a/pyfakefs/pytest_plugin.py b/pyfakefs/pytest_plugin.py index 37b055fd..26bbd583 100644 --- a/pyfakefs/pytest_plugin.py +++ b/pyfakefs/pytest_plugin.py @@ -80,3 +80,16 @@ def fs_session(request): def pytest_sessionfinish(session, exitstatus): """Make sure that the cache is cleared before the final test shutdown.""" Patcher.clear_fs_cache() + + +@pytest.hookimpl(tryfirst=True) +def pytest_runtest_logreport(report): + """Make sure that patching is not active during reporting.""" + if report.when == "call" and Patcher.PATCHER is not None: + Patcher.PATCHER.pause() + + +def pytest_runtest_call(item): + """Resume paused patching before test start.""" + if Patcher.PATCHER is not None: + Patcher.PATCHER.resume() diff --git a/pyfakefs/pytest_tests/pytest_module_fixture_test.py b/pyfakefs/pytest_tests/pytest_module_fixture_test.py index 3140ec94..d4eeda66 100644 --- a/pyfakefs/pytest_tests/pytest_module_fixture_test.py +++ b/pyfakefs/pytest_tests/pytest_module_fixture_test.py @@ -20,7 +20,19 @@ def use_fs(fs_module): yield fs_module +@pytest.fixture(autouse=True) +def check_patching_stopped(fs): + # patching shall be paused at test end, even in module scope (see #904) + yield + assert not fs.patcher.is_patching + + @pytest.mark.usefixtures("fs") -def test_fs_uses_fs_module(): +def test_fs_uses_fs_module1(): # check that `fs` uses the same filesystem as `fs_module` assert os.path.exists(os.path.join("foo", "bar")) + + +def test_fs_uses_fs_module2(fs): + # check that testing was not stopped by the first test + assert os.path.exists(os.path.join("foo", "bar")) diff --git a/pyfakefs/pytest_tests/segfault_test.py b/pyfakefs/pytest_tests/segfault_test.py deleted file mode 100644 index 05f46dc1..00000000 --- a/pyfakefs/pytest_tests/segfault_test.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -This is a regression test for #866 that shall ensure that -shutting down the test session after this specific call does not result -in a segmentation fault. -""" -import opentimelineio as otio - - -def test_empty_fs(fs): - pass - - -def test_create_clip(fs): - """If the fs cache is not cleared during session shutdown, a segmentation fault - will happen during garbage collection of the cached modules.""" - otio.core.SerializableObjectWithMetadata(metadata={})