From f48c8c041eb7734175b58edb3bf47e3a1924730f Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Sat, 23 Nov 2024 16:17:46 +0100 Subject: [PATCH] Add checks to avoid patching debugger modules - added Patcher.RUNTIME_SKIPMODULES to allow to skip modules whose name starts with specific strings, if the related debuuger is loaded - only done for debugger used in PyCharm and VSCode --- CHANGES.md | 2 ++ pyfakefs/fake_filesystem_unittest.py | 25 +++++++++++++++- .../tests/fake_filesystem_unittest_test.py | 30 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 8e46c30a..7548375a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ The released versions correspond to PyPI releases. ### Enhancements * added some support for loading fake modules in `AUTO` patch mode using `importlib.import_module` (see [#1079](../../issues/1079)) +* added some support to avoid patching debugger related modules + (see [#1083](../../issues/1083)) ### Performance * avoid reloading `tempfile` in Posix systems diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py index f3797373..84baabdc 100644 --- a/pyfakefs/fake_filesystem_unittest.py +++ b/pyfakefs/fake_filesystem_unittest.py @@ -514,6 +514,13 @@ class Patcher: SKIPMODULES.add(posixpath) SKIPMODULES.add(fcntl) + # a list of test and debug modules detected at run-time + # each tool defines one or more module name prefixes for modules to be skipped + RUNTIME_SKIPMODULES = { + "pydevd": ["_pydevd_", "pydevd", "_pydev_"], # Python debugger (PyCharm/VSCode) + "_jb_runner_tools": ["_jb_"], # JetBrains tools + } + # caches all modules that do not have file system modules or function # to speed up _find_modules CACHED_MODULES: Set[ModuleType] = set() @@ -1045,10 +1052,26 @@ def patch_functions(self) -> None: self._stubs.smart_set(module, name, attr) def patch_modules(self) -> None: + skip_prefix_list = [] + for rt_skip_module, prefixes in self.RUNTIME_SKIPMODULES.items(): + if rt_skip_module in sys.modules: + skip_prefix_list.extend(prefixes) + skip_prefixes = tuple(skip_prefix_list) + assert self._stubs is not None for name, modules in self.FS_MODULES.items(): for module, attr in modules: - self._stubs.smart_set(module, name, self.fake_modules[attr]) + try: + if not skip_prefixes or not module.__name__.startswith( + skip_prefixes + ): + self._stubs.smart_set(module, name, self.fake_modules[attr]) + elif attr in self.unfaked_modules: + self._stubs.smart_set(module, name, self.unfaked_modules[attr]) + except Exception: + # handle the rare case that a module has no __name__ + pass + for name, modules in self.SKIPPED_FS_MODULES.items(): for module, attr in modules: if attr in self.unfaked_modules: diff --git a/pyfakefs/tests/fake_filesystem_unittest_test.py b/pyfakefs/tests/fake_filesystem_unittest_test.py index 0a613a1a..99ac9636 100644 --- a/pyfakefs/tests/fake_filesystem_unittest_test.py +++ b/pyfakefs/tests/fake_filesystem_unittest_test.py @@ -456,6 +456,36 @@ def test_path_succeeds(self): pyfakefs.tests.import_as_example.return_this_file_path() +class RuntimeSkipModuleTest(fake_filesystem_unittest.TestCase): + """Emulates skipping a module using RUNTIME_SKIPMODULES. + Not all functionality implemented for skip modules will work here.""" + + def setUp(self): + Patcher.RUNTIME_SKIPMODULES.update( + {"pyfakefs.tests.import_as_example": ["pyfakefs.tests.import_"]} + ) + self.setUpPyfakefs() + + def tearDown(self): + del self.patcher.RUNTIME_SKIPMODULES["pyfakefs.tests.import_as_example"] + + def test_fake_path_does_not_exist1(self): + self.fs.create_file("foo") + self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists1("foo")) + + def test_fake_path_does_not_exist2(self): + self.fs.create_file("foo") + self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists2("foo")) + + def test_fake_path_does_not_exist3(self): + self.fs.create_file("foo") + self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists3("foo")) + + def test_fake_path_does_not_exist4(self): + self.fs.create_file("foo") + self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists4("foo")) + + class FakeExampleModule: """Used to patch a function that uses system-specific functions that cannot be patched automatically."""