aboutsummaryrefslogtreecommitdiff
path: root/pyfakefs/fake_filesystem_unittest.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyfakefs/fake_filesystem_unittest.py')
-rw-r--r--pyfakefs/fake_filesystem_unittest.py698
1 files changed, 421 insertions, 277 deletions
diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py
index 6633cb5..4604a65 100644
--- a/pyfakefs/fake_filesystem_unittest.py
+++ b/pyfakefs/fake_filesystem_unittest.py
@@ -35,10 +35,15 @@ Existing unit tests that use the real file system can be retrofitted to use
pyfakefs by simply changing their base class from `:py:class`unittest.TestCase`
to `:py:class`pyfakefs.fake_filesystem_unittest.TestCase`.
"""
+import _io # type:ignore[import]
+import builtins
import doctest
import functools
+import genericpath
import inspect
+import io
import linecache
+import os
import shutil
import sys
import tempfile
@@ -46,28 +51,38 @@ import tokenize
from importlib.abc import Loader, MetaPathFinder
from types import ModuleType, TracebackType, FunctionType
from typing import (
- Any, Callable, Dict, List, Set, Tuple, Optional, Union,
- AnyStr, Type, Iterator, cast, ItemsView, Sequence
+ Any,
+ Callable,
+ Dict,
+ List,
+ Set,
+ Tuple,
+ Optional,
+ Union,
+ Type,
+ Iterator,
+ cast,
+ ItemsView,
+ Sequence,
)
import unittest
import warnings
from unittest import TestSuite
-from pyfakefs.deprecator import Deprecator
from pyfakefs.fake_filesystem import (
- set_uid, set_gid, reset_ids, PatchMode, FakeFile, FakeFilesystem
+ set_uid,
+ set_gid,
+ reset_ids,
+ PatchMode,
+ FakeFilesystem,
)
from pyfakefs.helpers import IS_PYPY
from pyfakefs.mox3_stubout import StubOutForTesting
-try:
- from importlib.machinery import ModuleSpec
-except ImportError:
- ModuleSpec = object # type: ignore[assignment, misc]
-
+from importlib.machinery import ModuleSpec
from importlib import reload
-from pyfakefs import fake_filesystem
+from pyfakefs import fake_filesystem, fake_io, fake_os, fake_open, fake_path, fake_file
from pyfakefs import fake_filesystem_shutil
from pyfakefs import fake_pathlib
from pyfakefs import mox3_stubout
@@ -76,20 +91,22 @@ from pyfakefs.extra_packages import pathlib2, use_scandir
if use_scandir:
from pyfakefs import fake_scandir
-OS_MODULE = 'nt' if sys.platform == 'win32' else 'posix'
-PATH_MODULE = 'ntpath' if sys.platform == 'win32' else 'posixpath'
-
-
-def patchfs(_func: Callable = None, *,
- additional_skip_names: Optional[
- List[Union[str, ModuleType]]] = None,
- modules_to_reload: Optional[List[ModuleType]] = None,
- modules_to_patch: Optional[Dict[str, ModuleType]] = None,
- allow_root_user: bool = True,
- use_known_patches: bool = True,
- patch_open_code: PatchMode = PatchMode.OFF,
- patch_default_args: bool = False,
- use_cache: bool = True) -> Callable:
+OS_MODULE = "nt" if sys.platform == "win32" else "posix"
+PATH_MODULE = "ntpath" if sys.platform == "win32" else "posixpath"
+
+
+def patchfs(
+ _func: Optional[Callable] = None,
+ *,
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True
+) -> Callable:
"""Convenience decorator to use patcher with additional parameters in a
test function.
@@ -108,14 +125,15 @@ def patchfs(_func: Callable = None, *,
@functools.wraps(f)
def wrapped(*args, **kwargs):
with Patcher(
- additional_skip_names=additional_skip_names,
- modules_to_reload=modules_to_reload,
- modules_to_patch=modules_to_patch,
- allow_root_user=allow_root_user,
- use_known_patches=use_known_patches,
- patch_open_code=patch_open_code,
- patch_default_args=patch_default_args,
- use_cache=use_cache) as p:
+ additional_skip_names=additional_skip_names,
+ modules_to_reload=modules_to_reload,
+ modules_to_patch=modules_to_patch,
+ allow_root_user=allow_root_user,
+ use_known_patches=use_known_patches,
+ patch_open_code=patch_open_code,
+ patch_default_args=patch_default_args,
+ use_cache=use_cache,
+ ) as p:
args = list(args)
args.append(p.fs)
return f(*args, **kwargs)
@@ -128,23 +146,28 @@ def patchfs(_func: Callable = None, *,
"Decorator argument is not a function.\n"
"Did you mean `@patchfs(additional_skip_names=...)`?"
)
- if hasattr(_func, 'patchings'):
+ if hasattr(_func, "patchings"):
_func.nr_patches = len(_func.patchings) # type: ignore
return wrap_patchfs(_func)
return wrap_patchfs
+DOCTEST_PATCHER = None
+
+
def load_doctests(
- loader: Any, tests: TestSuite, ignore: Any, module: ModuleType,
- additional_skip_names: Optional[
- List[Union[str, ModuleType]]] = None,
- modules_to_reload: Optional[List[ModuleType]] = None,
- modules_to_patch: Optional[Dict[str, ModuleType]] = None,
- allow_root_user: bool = True,
- use_known_patches: bool = True,
- patch_open_code: PatchMode = PatchMode.OFF,
- patch_default_args: bool = False
+ loader: Any,
+ tests: TestSuite,
+ ignore: Any,
+ module: ModuleType,
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
) -> TestSuite: # pylint:disable=unused-argument
"""Load the doctest tests for the specified module into unittest.
Args:
@@ -154,18 +177,28 @@ def load_doctests(
File `example_test.py` in the pyfakefs release provides a usage example.
"""
- _patcher = Patcher(additional_skip_names=additional_skip_names,
- modules_to_reload=modules_to_reload,
- modules_to_patch=modules_to_patch,
- allow_root_user=allow_root_user,
- use_known_patches=use_known_patches,
- patch_open_code=patch_open_code,
- patch_default_args=patch_default_args)
- globs = _patcher.replace_globs(vars(module))
- tests.addTests(doctest.DocTestSuite(module,
- globs=globs,
- setUp=_patcher.setUp,
- tearDown=_patcher.tearDown))
+ has_patcher = Patcher.DOC_PATCHER is not None
+ if not has_patcher:
+ Patcher.DOC_PATCHER = Patcher(
+ additional_skip_names=additional_skip_names,
+ modules_to_reload=modules_to_reload,
+ modules_to_patch=modules_to_patch,
+ allow_root_user=allow_root_user,
+ use_known_patches=use_known_patches,
+ patch_open_code=patch_open_code,
+ patch_default_args=patch_default_args,
+ is_doc_test=True,
+ )
+ assert Patcher.DOC_PATCHER is not None
+ globs = Patcher.DOC_PATCHER.replace_globs(vars(module))
+ tests.addTests(
+ doctest.DocTestSuite(
+ module,
+ globs=globs,
+ setUp=Patcher.DOC_PATCHER.setUp,
+ tearDown=Patcher.DOC_PATCHER.tearDown,
+ )
+ )
return tests
@@ -214,19 +247,26 @@ class TestCaseMixin:
modules_to_patch: Optional[Dict[str, ModuleType]] = None
@property
+ def patcher(self):
+ if hasattr(self, "_patcher"):
+ return self._patcher or Patcher.PATCHER
+ return Patcher.PATCHER
+
+ @property
def fs(self) -> FakeFilesystem:
- return cast(FakeFilesystem, self._stubber.fs)
-
- def setUpPyfakefs(self,
- additional_skip_names: Optional[
- List[Union[str, ModuleType]]] = None,
- modules_to_reload: Optional[List[ModuleType]] = None,
- modules_to_patch: Optional[Dict[str, ModuleType]] = None,
- allow_root_user: bool = True,
- use_known_patches: bool = True,
- patch_open_code: PatchMode = PatchMode.OFF,
- patch_default_args: bool = False,
- use_cache: bool = True) -> None:
+ return cast(FakeFilesystem, self.patcher.fs)
+
+ def setUpPyfakefs(
+ self,
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True,
+ ) -> None:
"""Bind the file-related modules to the :py:class:`pyfakefs` fake file
system instead of the real file system. Also bind the fake `open()`
function.
@@ -238,13 +278,68 @@ class TestCaseMixin:
the current test case. Settings the arguments here may be a more
convenient way to adapt the setting than overwriting `__init__()`.
"""
+ # if the class has already a patcher setup, we use this one
+ if Patcher.PATCHER is not None:
+ return
+
if additional_skip_names is None:
additional_skip_names = self.additional_skip_names
if modules_to_reload is None:
modules_to_reload = self.modules_to_reload
if modules_to_patch is None:
modules_to_patch = self.modules_to_patch
- self._stubber = Patcher(
+ self._patcher = Patcher(
+ additional_skip_names=additional_skip_names,
+ modules_to_reload=modules_to_reload,
+ modules_to_patch=modules_to_patch,
+ allow_root_user=allow_root_user,
+ use_known_patches=use_known_patches,
+ patch_open_code=patch_open_code,
+ patch_default_args=patch_default_args,
+ use_cache=use_cache,
+ )
+
+ self._patcher.setUp()
+ cast(TestCase, self).addCleanup(self._patcher.tearDown)
+
+ @classmethod
+ def setUpClassPyfakefs(
+ cls,
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True,
+ ) -> None:
+ """Similar to :py:func:`setUpPyfakefs`, but as a class method that
+ can be used in `setUpClass` instead of in `setUp`.
+ The fake filesystem will live in all test methods in the test class
+ and can be used in the usual way.
+ Note that using both :py:func:`setUpClassPyfakefs` and
+ :py:func:`setUpPyfakefs` in the same class will not work correctly.
+
+ .. note:: This method is only available from Python 3.8 onwards.
+ """
+ if sys.version_info < (3, 8):
+ raise NotImplementedError(
+ "setUpClassPyfakefs is only available in "
+ "Python versions starting from 3.8"
+ )
+
+ # if the class has already a patcher setup, we use this one
+ if Patcher.PATCHER is not None:
+ return
+
+ if additional_skip_names is None:
+ additional_skip_names = cls.additional_skip_names
+ if modules_to_reload is None:
+ modules_to_reload = cls.modules_to_reload
+ if modules_to_patch is None:
+ modules_to_patch = cls.modules_to_patch
+ Patcher.PATCHER = Patcher(
additional_skip_names=additional_skip_names,
modules_to_reload=modules_to_reload,
modules_to_patch=modules_to_patch,
@@ -252,11 +347,21 @@ class TestCaseMixin:
use_known_patches=use_known_patches,
patch_open_code=patch_open_code,
patch_default_args=patch_default_args,
- use_cache=use_cache
+ use_cache=use_cache,
)
- self._stubber.setUp()
- cast(TestCase, self).addCleanup(self._stubber.tearDown)
+ Patcher.PATCHER.setUp()
+ cast(TestCase, cls).addClassCleanup(Patcher.PATCHER.tearDown)
+
+ @classmethod
+ def fake_fs(cls):
+ """Convenience class method for accessing the fake filesystem.
+ For use inside `setUpClass`, after :py:func:`setUpClassPyfakefs`
+ has been called.
+ """
+ if Patcher.PATCHER:
+ return Patcher.PATCHER.fs
+ return None
def pause(self) -> None:
"""Pause the patching of the file system modules until `resume` is
@@ -265,7 +370,7 @@ class TestCaseMixin:
Calling pause() twice is silently ignored.
"""
- self._stubber.pause()
+ self.patcher.pause()
def resume(self) -> None:
"""Resume the patching of the file system modules if `pause` has
@@ -273,7 +378,7 @@ class TestCaseMixin:
executed in the fake file system.
Does nothing if patching is not paused.
"""
- self._stubber.resume()
+ self.patcher.resume()
class TestCase(unittest.TestCase, TestCaseMixin):
@@ -283,11 +388,13 @@ class TestCase(unittest.TestCase, TestCaseMixin):
The arguments are explained in :py:class:`TestCaseMixin`.
"""
- def __init__(self, methodName: str = 'runTest',
- additional_skip_names: Optional[
- List[Union[str, ModuleType]]] = None,
- modules_to_reload: Optional[List[ModuleType]] = None,
- modules_to_patch: Optional[Dict[str, ModuleType]] = None):
+ def __init__(
+ self,
+ methodName: str = "runTest",
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ ):
"""Creates the test class instance and the patcher used to stub out
file system related modules.
@@ -301,53 +408,10 @@ class TestCase(unittest.TestCase, TestCaseMixin):
self.modules_to_reload = modules_to_reload
self.modules_to_patch = modules_to_patch
- @Deprecator('add_real_file')
- def copyRealFile(self, real_file_path: AnyStr,
- fake_file_path: Optional[AnyStr] = None,
- create_missing_dirs: bool = True) -> FakeFile:
- """Add the file `real_file_path` in the real file system to the same
- path in the fake file system.
-
- **This method is deprecated** in favor of
- :py:meth:`FakeFilesystem..add_real_file`.
- `copyRealFile()` is retained with limited functionality for backward
- compatibility only.
-
- Args:
- real_file_path: Path to the file in both the real and fake
- file systems
- fake_file_path: Deprecated. Use the default, which is
- `real_file_path`.
- If a value other than `real_file_path` is specified, a `ValueError`
- exception will be raised.
- create_missing_dirs: Deprecated. Use the default, which creates
- missing directories in the fake file system. If `False` is
- specified, a `ValueError` exception is raised.
-
- Returns:
- The newly created FakeFile object.
-
- Raises:
- OSError: If the file already exists in the fake file system.
- ValueError: If deprecated argument values are specified.
-
- See:
- :py:meth:`FakeFileSystem.add_real_file`
- """
- if fake_file_path is not None and real_file_path != fake_file_path:
- raise ValueError("CopyRealFile() is deprecated and no longer "
- "supports different real and fake file paths")
- if not create_missing_dirs:
- raise ValueError("CopyRealFile() is deprecated and no longer "
- "supports NOT creating missing directories")
- assert self._stubber.fs is not None
- return self._stubber.fs.add_real_file(real_file_path, read_only=False)
-
def tearDownPyfakefs(self) -> None:
"""This method is deprecated and exists only for backward
compatibility. It does nothing.
"""
- pass
class Patcher:
@@ -363,21 +427,49 @@ class Patcher:
with Patcher():
doStuff()
"""
- '''Stub nothing that is imported within these modules.
+
+ """Stub nothing that is imported within these modules.
`sys` is included to prevent `sys.path` from being stubbed with the fake
`os.path`.
- The `pytest` and `py` modules are used by pytest and have to access the
- real file system.
The `linecache` module is used to read the test file in case of test
failure to get traceback information before test tear down.
In order to make sure that reading the test file is not faked,
we skip faking the module.
We also have to set back the cached open function in tokenize.
- '''
+ """
SKIPMODULES = {
- None, fake_filesystem, fake_filesystem_shutil,
- sys, linecache, tokenize
+ None,
+ fake_filesystem,
+ fake_filesystem_shutil,
+ fake_os,
+ fake_io,
+ fake_open,
+ fake_path,
+ fake_file,
+ sys,
+ linecache,
+ tokenize,
+ os,
+ io,
+ _io,
+ genericpath,
+ os.path,
}
+ if sys.platform == "win32":
+ import nt # type:ignore[import]
+ import ntpath
+
+ SKIPMODULES.add(nt)
+ SKIPMODULES.add(ntpath)
+ else:
+ import posix
+ import posixpath
+ import fcntl
+
+ SKIPMODULES.add(posix)
+ SKIPMODULES.add(posixpath)
+ SKIPMODULES.add(fcntl)
+
# caches all modules that do not have file system modules or function
# to speed up _find_modules
CACHED_MODULES: Set[ModuleType] = set()
@@ -386,28 +478,42 @@ class Patcher:
FS_DEFARGS: List[Tuple[FunctionType, int, Callable[..., Any]]] = []
SKIPPED_FS_MODULES: Dict[str, Set[Tuple[ModuleType, str]]] = {}
- assert None in SKIPMODULES, ("sys.modules contains 'None' values;"
- " must skip them.")
+ assert None in SKIPMODULES, "sys.modules contains 'None' values;" " must skip them."
- IS_WINDOWS = sys.platform in ('win32', 'cygwin')
+ IS_WINDOWS = sys.platform in ("win32", "cygwin")
- SKIPNAMES = {'os', 'path', 'io', 'genericpath', 'fcntl',
- OS_MODULE, PATH_MODULE}
+ SKIPNAMES: Set[str] = set()
# hold values from last call - if changed, the cache has to be invalidated
PATCHED_MODULE_NAMES: Set[str] = set()
ADDITIONAL_SKIP_NAMES: Set[str] = set()
PATCH_DEFAULT_ARGS = False
-
- def __init__(self, additional_skip_names: Optional[
- List[Union[str, ModuleType]]] = None,
- modules_to_reload: Optional[List[ModuleType]] = None,
- modules_to_patch: Optional[Dict[str, ModuleType]] = None,
- allow_root_user: bool = True,
- use_known_patches: bool = True,
- patch_open_code: PatchMode = PatchMode.OFF,
- patch_default_args: bool = False,
- use_cache: bool = True) -> None:
+ PATCHER: Optional["Patcher"] = None
+ DOC_PATCHER: Optional["Patcher"] = None
+ REF_COUNT = 0
+ DOC_REF_COUNT = 0
+
+ def __new__(cls, *args, **kwargs):
+ if kwargs.get("is_doc_test", False):
+ if cls.DOC_PATCHER is None:
+ cls.DOC_PATCHER = super().__new__(cls)
+ return cls.DOC_PATCHER
+ if cls.PATCHER is None:
+ cls.PATCHER = super().__new__(cls)
+ return cls.PATCHER
+
+ def __init__(
+ self,
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True,
+ is_doc_test: bool = False,
+ ) -> None:
"""
Args:
additional_skip_names: names of modules inside of which no module
@@ -439,7 +545,12 @@ class Patcher:
feature, this argument allows to turn it off in case it
causes any problems.
"""
-
+ self.is_doc_test = is_doc_test
+ if is_doc_test:
+ if self.DOC_REF_COUNT > 0:
+ return
+ elif self.REF_COUNT > 0:
+ return
if not allow_root_user:
# set non-root IDs even if the real user is root
set_uid(1)
@@ -449,11 +560,12 @@ class Patcher:
# save the original open function for use in pytest plugin
self.original_open = open
self.patch_open_code = patch_open_code
+ self.fake_open: fake_open.FakeFileOpen
if additional_skip_names is not None:
skip_names = [
- cast(ModuleType, m).__name__ if inspect.ismodule(m)
- else cast(str, m) for m in additional_skip_names
+ cast(ModuleType, m).__name__ if inspect.ismodule(m) else cast(str, m)
+ for m in additional_skip_names
]
self._skip_names.update(skip_names)
@@ -464,7 +576,7 @@ class Patcher:
# reload tempfile under posix to patch default argument
self.modules_to_reload: List[ModuleType] = (
- [] if sys.platform == 'win32' else [tempfile]
+ [] if sys.platform == "win32" else [tempfile]
)
if modules_to_reload is not None:
self.modules_to_reload.extend(modules_to_reload)
@@ -473,8 +585,9 @@ class Patcher:
if use_known_patches:
from pyfakefs.patched_packages import (
- get_modules_to_patch, get_classes_to_patch,
- get_fake_module_classes
+ get_modules_to_patch,
+ get_classes_to_patch,
+ get_fake_module_classes,
)
modules_to_patch = modules_to_patch or {}
@@ -516,13 +629,18 @@ class Patcher:
self._dyn_patcher: Optional[DynamicPatcher] = None
self._patching = False
- def clear_cache(self) -> None:
+ @classmethod
+ def clear_fs_cache(cls) -> None:
"""Clear the module cache."""
- self.__class__.CACHED_MODULES = set()
- self.__class__.FS_MODULES = {}
- self.__class__.FS_FUNCTIONS = {}
- self.__class__.FS_DEFARGS = []
- self.__class__.SKIPPED_FS_MODULES = {}
+ cls.CACHED_MODULES = set()
+ cls.FS_MODULES = {}
+ cls.FS_FUNCTIONS = {}
+ cls.FS_DEFARGS = []
+ cls.SKIPPED_FS_MODULES = {}
+
+ def clear_cache(self) -> None:
+ """Clear the module cache (convenience instance method)."""
+ self.__class__.clear_fs_cache()
def _init_fake_module_classes(self) -> None:
# IMPORTANT TESTING NOTE: Whenever you add a new module below, test
@@ -530,63 +648,63 @@ class Patcher:
# and a test in fake_filesystem_unittest_test.py, class
# TestAttributesWithFakeModuleNames.
self._fake_module_classes = {
- 'os': fake_filesystem.FakeOsModule,
- 'shutil': fake_filesystem_shutil.FakeShutilModule,
- 'io': fake_filesystem.FakeIoModule,
- 'pathlib': fake_pathlib.FakePathlibModule
+ "os": fake_os.FakeOsModule,
+ "shutil": fake_filesystem_shutil.FakeShutilModule,
+ "io": fake_io.FakeIoModule,
+ "pathlib": fake_pathlib.FakePathlibModule,
}
if IS_PYPY:
# in PyPy io.open, the module is referenced as _io
- self._fake_module_classes['_io'] = fake_filesystem.FakeIoModule
- if sys.platform != 'win32':
- self._fake_module_classes[
- 'fcntl'] = fake_filesystem.FakeFcntlModule
+ self._fake_module_classes["_io"] = fake_io.FakeIoModule
+ if sys.platform == "win32":
+ self._fake_module_classes["nt"] = fake_path.FakeNtModule
+ else:
+ self._fake_module_classes["fcntl"] = fake_filesystem.FakeFcntlModule
# class modules maps class names against a list of modules they can
# be contained in - this allows for alternative modules like
# `pathlib` and `pathlib2`
- self._class_modules['Path'] = ['pathlib']
- self._unfaked_module_classes[
- 'pathlib'] = fake_pathlib.RealPathlibModule
+ self._class_modules["Path"] = ["pathlib"]
+ self._unfaked_module_classes["pathlib"] = fake_pathlib.RealPathlibModule
if pathlib2:
- self._fake_module_classes[
- 'pathlib2'] = fake_pathlib.FakePathlibModule
- self._class_modules['Path'].append('pathlib2')
- self._unfaked_module_classes[
- 'pathlib2'] = fake_pathlib.RealPathlibModule
- self._fake_module_classes[
- 'Path'] = fake_pathlib.FakePathlibPathModule
- self._unfaked_module_classes[
- 'Path'] = fake_pathlib.RealPathlibPathModule
+ self._fake_module_classes["pathlib2"] = fake_pathlib.FakePathlibModule
+ self._class_modules["Path"].append("pathlib2")
+ self._unfaked_module_classes["pathlib2"] = fake_pathlib.RealPathlibModule
+ self._fake_module_classes["Path"] = fake_pathlib.FakePathlibPathModule
+ self._unfaked_module_classes["Path"] = fake_pathlib.RealPathlibPathModule
if use_scandir:
- self._fake_module_classes[
- 'scandir'] = fake_scandir.FakeScanDirModule
+ self._fake_module_classes["scandir"] = fake_scandir.FakeScanDirModule
def _init_fake_module_functions(self) -> None:
# handle patching function imported separately like
# `from os import stat`
# each patched function name has to be looked up separately
for mod_name, fake_module in self._fake_module_classes.items():
- if (hasattr(fake_module, 'dir') and
- inspect.isfunction(fake_module.dir)):
- for fct_name in fake_module.dir():
- module_attr = (getattr(fake_module, fct_name), mod_name)
- self._fake_module_functions.setdefault(
- fct_name, {})[mod_name] = module_attr
- if mod_name == 'os':
- self._fake_module_functions.setdefault(
- fct_name, {})[OS_MODULE] = module_attr
+ if hasattr(fake_module, "dir"):
+ module_dir = fake_module.dir
+ if inspect.isfunction(module_dir):
+ for fct_name in fake_module.dir():
+ module_attr = (getattr(fake_module, fct_name), mod_name)
+ self._fake_module_functions.setdefault(fct_name, {})[
+ mod_name
+ ] = module_attr
+ if mod_name == "os":
+ self._fake_module_functions.setdefault(fct_name, {})[
+ OS_MODULE
+ ] = module_attr
# special handling for functions in os.path
fake_module = fake_filesystem.FakePathModule
for fct_name in fake_module.dir():
module_attr = (getattr(fake_module, fct_name), PATH_MODULE)
- self._fake_module_functions.setdefault(
- fct_name, {})['genericpath'] = module_attr
- self._fake_module_functions.setdefault(
- fct_name, {})[PATH_MODULE] = module_attr
-
- def __enter__(self) -> 'Patcher':
+ self._fake_module_functions.setdefault(fct_name, {})[
+ "genericpath"
+ ] = module_attr
+ self._fake_module_functions.setdefault(fct_name, {})[
+ PATH_MODULE
+ ] = module_attr
+
+ def __enter__(self) -> "Patcher":
"""Context manager for usage outside of
fake_filesystem_unittest.TestCase.
Ensure that all patched modules are removed in case of an
@@ -595,49 +713,45 @@ class Patcher:
self.setUp()
return self
- def __exit__(self,
- exc_type: Optional[Type[BaseException]],
- exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType]) -> None:
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
self.tearDown()
- def _is_fs_module(self, mod: ModuleType,
- name: str,
- module_names: List[str]) -> bool:
- try:
- # check for __name__ first and ignore the AttributeException
- # if it does not exist - avoids calling expansive ismodule
- if mod.__name__ in module_names and inspect.ismodule(mod):
- return True
- except Exception:
- pass
+ def _is_fs_module(
+ self, mod: ModuleType, name: str, module_names: List[str]
+ ) -> bool:
try:
- if (name in self._class_modules and
- mod.__module__ in self._class_modules[name]):
- return inspect.isclass(mod)
+ return (
+ inspect.ismodule(mod)
+ and mod.__name__ in module_names
+ or inspect.isclass(mod)
+ and mod.__module__ in self._class_modules.get(name, [])
+ )
except Exception:
- # handle AttributeError and any other exception possibly triggered
- # by side effects of inspect methods
- pass
- return False
+ # handle cases where the module has no __name__ or __module__
+ # attribute - see #460, and any other exception triggered
+ # by inspect functions
+ return False
def _is_fs_function(self, fct: FunctionType) -> bool:
try:
- # check for __name__ first and ignore the AttributeException
- # if it does not exist - avoids calling expansive inspect
- # methods in most cases
- return (fct.__name__ in self._fake_module_functions and
- fct.__module__ in self._fake_module_functions[
- fct.__name__] and
- (inspect.isfunction(fct) or inspect.isbuiltin(fct)))
+ return (
+ (inspect.isfunction(fct) or inspect.isbuiltin(fct))
+ and fct.__name__ in self._fake_module_functions
+ and fct.__module__ in self._fake_module_functions[fct.__name__]
+ )
except Exception:
- # handle AttributeError and any other exception possibly triggered
- # by side effects of inspect methods
+ # handle cases where the function has no __name__ or __module__
+ # attribute, or any other exception in inspect functions
return False
def _def_values(
- self,
- item: FunctionType) -> Iterator[Tuple[FunctionType, int, Any]]:
+ self, item: FunctionType
+ ) -> Iterator[Tuple[FunctionType, int, Any]]:
"""Find default arguments that are file-system functions to be
patched in top-level functions and members of top-level classes."""
# check for module-level functions
@@ -653,8 +767,7 @@ class Patcher:
# check for methods in class
# (nested classes are ignored for now)
# inspect.getmembers is very expansive!
- for m in inspect.getmembers(item,
- predicate=inspect.isfunction):
+ for m in inspect.getmembers(item, predicate=inspect.isfunction):
f = cast(FunctionType, m[1])
if f.__defaults__:
for i, d in enumerate(f.__defaults__):
@@ -666,8 +779,7 @@ class Patcher:
# _DontDoThat() (see #523)
pass
- def _find_def_values(
- self, module_items: ItemsView[str, FunctionType]) -> None:
+ def _find_def_values(self, module_items: ItemsView[str, FunctionType]) -> None:
for _, fct in module_items:
for f, i, d in self._def_values(fct):
self.__class__.FS_DEFARGS.append((f, i, d))
@@ -680,8 +792,11 @@ class Patcher:
module_names = list(self._fake_module_classes.keys()) + [PATH_MODULE]
for name, module in list(sys.modules.items()):
try:
- if (self.use_cache and module in self.CACHED_MODULES or
- not inspect.ismodule(module)):
+ if (
+ self.use_cache
+ and module in self.CACHED_MODULES
+ or not inspect.ismodule(module)
+ ):
continue
except Exception:
# workaround for some py (part of pytest) versions
@@ -691,30 +806,35 @@ class Patcher:
if self.use_cache:
self.__class__.CACHED_MODULES.add(module)
continue
- skipped = (module in self.SKIPMODULES or
- any([sn.startswith(module.__name__)
- for sn in self._skip_names]))
+ skipped = module in self.SKIPMODULES or any(
+ [sn.startswith(module.__name__) for sn in self._skip_names]
+ )
module_items = module.__dict__.copy().items()
- modules = {name: mod for name, mod in module_items
- if self._is_fs_module(mod, name, module_names)}
+ modules = {
+ name: mod
+ for name, mod in module_items
+ if self._is_fs_module(mod, name, module_names)
+ }
if skipped:
for name, mod in modules.items():
- self.__class__.SKIPPED_FS_MODULES.setdefault(
- name, set()).add((module, mod.__name__))
+ self.__class__.SKIPPED_FS_MODULES.setdefault(name, set()).add(
+ (module, mod.__name__)
+ )
else:
for name, mod in modules.items():
self.__class__.FS_MODULES.setdefault(name, set()).add(
- (module, mod.__name__))
- functions = {name: fct for name, fct in
- module_items
- if self._is_fs_function(fct)}
+ (module, mod.__name__)
+ )
+ functions = {
+ name: fct for name, fct in module_items if self._is_fs_function(fct)
+ }
for name, fct in functions.items():
self.__class__.FS_FUNCTIONS.setdefault(
- (name, fct.__name__, fct.__module__),
- set()).add(module)
+ (name, fct.__name__, fct.__module__), set()
+ ).add(module)
# find default arguments that are file system functions
if self.patch_default_args:
@@ -729,13 +849,14 @@ class Patcher:
self._stubs.smart_unset_all()
self._stubs = mox3_stubout.StubOutForTesting()
- self.fs = fake_filesystem.FakeFilesystem(patcher=self)
+ self.fs = fake_filesystem.FakeFilesystem(patcher=self, create_temp_dir=True)
self.fs.patch_open_code = self.patch_open_code
+ self.fake_open = fake_open.FakeFileOpen(self.fs)
for name in self._fake_module_classes:
self.fake_modules[name] = self._fake_module_classes[name](self.fs)
- if hasattr(self.fake_modules[name], 'skip_names'):
+ if hasattr(self.fake_modules[name], "skip_names"):
self.fake_modules[name].skip_names = self._skip_names
- self.fake_modules[PATH_MODULE] = self.fake_modules['os'].path
+ self.fake_modules[PATH_MODULE] = self.fake_modules["os"].path
for name in self._unfaked_module_classes:
self.unfaked_modules[name] = self._unfaked_module_classes[name]()
@@ -745,18 +866,25 @@ class Patcher:
"""Bind the file-related modules to the :py:mod:`pyfakefs` fake
modules real ones. Also bind the fake `file()` and `open()` functions.
"""
- self.has_fcopy_file = (sys.platform == 'darwin' and
- hasattr(shutil, '_HAS_FCOPYFILE') and
- shutil._HAS_FCOPYFILE)
+ if self.is_doc_test:
+ self.__class__.DOC_REF_COUNT += 1
+ if self.__class__.DOC_REF_COUNT > 1:
+ return
+ else:
+ self.__class__.REF_COUNT += 1
+ if self.__class__.REF_COUNT > 1:
+ return
+ self.has_fcopy_file = (
+ sys.platform == "darwin"
+ and hasattr(shutil, "_HAS_FCOPYFILE")
+ and shutil._HAS_FCOPYFILE
+ )
if self.has_fcopy_file:
shutil._HAS_FCOPYFILE = False # type: ignore[attr-defined]
- temp_dir = tempfile.gettempdir()
with warnings.catch_warnings():
# ignore warnings, see #542 and #614
- warnings.filterwarnings(
- 'ignore'
- )
+ warnings.filterwarnings("ignore")
self._find_modules()
self._refresh()
@@ -768,11 +896,6 @@ class Patcher:
linecache.open = self.original_open # type: ignore[attr-defined]
tokenize._builtin_open = self.original_open # type: ignore
- # the temp directory is assumed to exist at least in `tempfile1`,
- # so we create it here for convenience
- assert self.fs is not None
- self.fs.create_dir(temp_dir)
-
def start_patching(self) -> None:
if not self._patching:
self._patching = True
@@ -792,7 +915,9 @@ class Patcher:
for (name, ft_name, ft_mod), modules in self.FS_FUNCTIONS.items():
method, mod_name = self._fake_module_functions[ft_name][ft_mod]
fake_module = self.fake_modules[mod_name]
- attr = method.__get__(fake_module, fake_module.__class__)
+ attr = method.__get__(
+ fake_module, fake_module.__class__
+ ) # pytype: disable=attribute-error
for module in modules:
self._stubs.smart_set(module, name, attr)
@@ -800,20 +925,22 @@ class Patcher:
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])
+ self._stubs.smart_set(module, name, self.fake_modules[attr])
for name, modules in self.SKIPPED_FS_MODULES.items():
for module, attr in modules:
if attr in self.unfaked_modules:
- self._stubs.smart_set(
- module, name, self.unfaked_modules[attr])
+ self._stubs.smart_set(module, name, self.unfaked_modules[attr])
+ if sys.version_info >= (3, 12):
+ # workaround for patching open - does not work with skip modules
+ self._stubs.smart_set(builtins, "open", self.fake_open)
def patch_defaults(self) -> None:
- for (fct, idx, ft) in self.FS_DEFARGS:
- method, mod_name = self._fake_module_functions[
- ft.__name__][ft.__module__]
+ for fct, idx, ft in self.FS_DEFARGS:
+ method, mod_name = self._fake_module_functions[ft.__name__][ft.__module__]
fake_module = self.fake_modules[mod_name]
- attr = method.__get__(fake_module, fake_module.__class__)
+ attr = method.__get__(
+ fake_module, fake_module.__class__
+ ) # pytype: disable=attribute-error
new_defaults = []
assert fct.__defaults__ is not None
for i, d in enumerate(fct.__defaults__):
@@ -834,11 +961,23 @@ class Patcher:
def tearDown(self, doctester: Any = None):
"""Clear the fake filesystem bindings created by `setUp()`."""
+ if self.is_doc_test:
+ self.__class__.DOC_REF_COUNT -= 1
+ if self.__class__.DOC_REF_COUNT > 0:
+ return
+ else:
+ self.__class__.REF_COUNT -= 1
+ if self.__class__.REF_COUNT > 0:
+ return
self.stop_patching()
if self.has_fcopy_file:
shutil._HAS_FCOPYFILE = True # type: ignore[attr-defined]
reset_ids()
+ if self.is_doc_test:
+ self.__class__.DOC_PATCHER = None
+ else:
+ self.__class__.PATCHER = None
def stop_patching(self) -> None:
if self._patching:
@@ -852,7 +991,7 @@ class Patcher:
sys.meta_path.pop(0)
def unset_defaults(self) -> None:
- for (fct, idx, ft) in self.FS_DEFARGS:
+ for fct, idx, ft in self.FS_DEFARGS:
new_defaults = []
for i, d in enumerate(cast(Tuple, fct.__defaults__)):
if i == idx:
@@ -898,10 +1037,12 @@ class Pause:
elif isinstance(caller, FakeFilesystem):
self._fs = caller
else:
- raise ValueError('Invalid argument - should be of type '
- '"fake_filesystem_unittest.Patcher", '
- '"fake_filesystem_unittest.TestCase" '
- 'or "fake_filesystem.FakeFilesystem"')
+ raise ValueError(
+ "Invalid argument - should be of type "
+ '"fake_filesystem_unittest.Patcher", '
+ '"fake_filesystem_unittest.TestCase" '
+ 'or "fake_filesystem.FakeFilesystem"'
+ )
def __enter__(self) -> FakeFilesystem:
self._fs.pause()
@@ -939,8 +1080,9 @@ class DynamicPatcher(MetaPathFinder, Loader):
for module in self._patcher.modules_to_reload:
if module.__name__ in sys.modules:
reload(module)
- reloaded_module_names = [module.__name__
- for module in self._patcher.modules_to_reload]
+ reloaded_module_names = [
+ module.__name__ for module in self._patcher.modules_to_reload
+ ]
# Dereference all modules loaded during the test so they will reload on
# the next use, ensuring that no faked modules are referenced after the
# test.
@@ -953,14 +1095,16 @@ class DynamicPatcher(MetaPathFinder, Loader):
if name not in self.modules:
self._loaded_module_names.add(name)
return False
- if (name in sys.modules and
- type(sys.modules[name]) == self.modules[name]):
+ if name in sys.modules and type(sys.modules[name]) is self.modules[name]:
return False
return True
- def find_spec(self, fullname: str,
- path: Optional[Sequence[Union[bytes, str]]],
- target: Optional[ModuleType] = None) -> Optional[ModuleSpec]:
+ def find_spec(
+ self,
+ fullname: str,
+ path: Optional[Sequence[Union[bytes, str]]],
+ target: Optional[ModuleType] = None,
+ ) -> Optional[ModuleSpec]:
"""Module finder."""
if self.needs_patch(fullname):
return ModuleSpec(fullname, self)