1Troubleshooting 2=============== 3This is a collection of problems with ``pyfakefs`` and possible solutions. 4It will be expanded continuously based on issues and problems found by users. 5 6Modules not working with pyfakefs 7--------------------------------- 8 9Modules may not work with ``pyfakefs`` for several reasons. ``pyfakefs`` 10works by patching some file system related modules and functions, specifically: 11 12- most file system related functions in the ``os`` and ``os.path`` modules 13- the ``pathlib`` module 14- the built-in ``open`` function and ``io.open`` 15- ``shutil.disk_usage`` 16 17Other file system related modules work with ``pyfakefs``, because they use 18exclusively these patched functions, specifically ``shutil`` (except for 19``disk_usage``), ``tempfile``, ``glob`` and ``zipfile``. 20 21A module may not work with ``pyfakefs`` because of one of the following 22reasons: 23 24- It uses a file system related function of the mentioned modules that is 25 not or not correctly patched. Mostly these are functions that are seldom 26 used, but may be used in Python libraries (this has happened for example 27 with a changed implementation of ``shutil`` in Python 3.7). Generally, 28 these shall be handled in issues and we are happy to fix them. 29- It uses file system related functions in a way that will not be patched 30 automatically. This is the case for functions that are executed while 31 reading a module. This case and a possibility to make them work is 32 documented above under ``modules_to_reload``. 33- It uses OS specific file system functions not contained in the Python 34 libraries. These will not work out of the box, and we generally will not 35 support them in ``pyfakefs``. If these functions are used in isolated 36 functions or classes, they may be patched by using the ``modules_to_patch`` 37 parameter (see the example for file locks in Django above), or by using 38 ``unittest.patch`` if you don't need to simulate the functions. We 39 added some of these patches to ``pyfakefs``, so that they are applied 40 automatically (currently done for some ``pandas`` and ``Django`` 41 functionality). 42- It uses C libraries to access the file system. There is no way no make 43 such a module work with ``pyfakefs``--if you want to use it, you 44 have to patch the whole module. In some cases, a library implemented in 45 Python with a similar interface already exists. An example is ``lxml``, 46 which can be substituted with ``ElementTree`` in most cases for testing. 47 48A list of Python modules that are known to not work correctly with 49``pyfakefs`` will be collected here: 50 51`multiprocessing`_ (built-in) 52~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 53This module has several issues (related to points 1 and 3 above). 54Currently there are no plans to fix this, but this may change in case of 55sufficient demand. 56 57`subprocess`_ (built-in) 58~~~~~~~~~~~~~~~~~~~~~~~~ 59This has very similar problems to ``multiprocessing`` and cannot be used with 60``pyfakefs`` to start a process. ``subprocess`` can either be mocked, if 61the process is not needed for the test, or patching can be paused to start 62a process if needed, and resumed afterwards 63(see `this issue <https://github.com/pytest-dev/pyfakefs/issues/447>`__). 64 65Modules that rely on ``subprocess`` or ``multiprocessing`` 66~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 67This includes a number of modules that need to start other executables to 68function correctly. Examples that have shown this problem include `GitPython`_ 69and `plumbum`_. Calling ``find_library`` also uses ``subprocess`` and does not work in 70the fake filesystem. 71 72`sqlite3`_ (built-in) 73~~~~~~~~~~~~~~~~~~~~~~~~ 74This is a database adapter written in C, which uses the database C API to access files. 75This (and similar database adapters) will not work with ``pyfakefs``, as it will always 76access the real filesystem. 77 78The `Pillow`_ Imaging Library 79~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80This library partly works with ``pyfakefs``, but it is known to not work at 81least if writing JPEG files 82(see `this issue <https://github.com/pytest-dev/pyfakefs/issues/529>`__) 83 84The `pandas`_ data analysis toolkit 85~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86This uses its own internal file system access written in C, thus much of 87``pandas`` will not work with ``pyfakefs`` out of the box. Having said that, 88``pyfakefs`` patches ``pandas`` to use standard file-system access instead, 89so that many of the ``read_xxx`` functions, including ``read_csv`` and 90``read_excel``, as well as some writer functions, do work with the fake file 91system. If you use only these functions, ``pyfakefs`` should work fine with 92``pandas``. 93 94`xlrd`_ 95~~~~~~~ 96This library is used by ``pandas`` to read Excel files in the `.xls` format, 97and can also be used stand-alone. Similar to ``pandas``, it is by default 98patched by ``pyfakefs`` to use normal file system functions that can be 99patched. 100 101`openpyxl`_ 102~~~~~~~~~~~ 103This is another library that reads and writes Excel files, and is also 104used by ``pandas`` if installed. ``openpyxl`` uses ``lxml`` for some file-system 105access if it is installed--in this case ``pyfakefs`` will not be able to patch 106it correctly (``lxml`` uses C functions for file system access). It will `not` 107use ``lxml`` however, if the environment variable ``OPENPYXL_LXML`` is set to 108"False" (or anything other than "True"), so if you set this variable `before` 109running the tests, it can work fine with ``pyfakefs``. 110 111If you encounter a module not working with ``pyfakefs``, and you are not sure 112if the module can be handled or how to do it, please write a new issue. We 113will check if it can be made to work, and at least add it to this list. 114 115Pyfakefs behaves differently than the real filesystem 116----------------------------------------------------- 117There are at least the following kinds of deviations from the actual behavior: 118 119- unwanted deviations that we didn't notice--if you find any of these, please 120 write an issue and we will try to fix it 121- behavior that depends on different OS versions and editions--as mentioned 122 in :ref:`limitations`, ``pyfakefs`` uses the systems used for CI tests in 123 GitHub Actions as reference system and will not replicate all system-specific behavior 124- behavior that depends on low-level OS functionality that ``pyfakefs`` is not 125 able to emulate; examples are the ``fcntl.ioctl`` and ``fcntl.fcntl`` 126 functions that are patched to do nothing 127 128The test code tries to access files in the real filesystem 129---------------------------------------------------------- 130The loading of the actual Python code from the real filesystem does not use 131the filesystem functions that ``pyfakefs`` patches, but in some cases it may 132access other files in the packages. An example is the ``pytz`` module, which is loading timezone information 133from configuration files. In these cases, you have to map the respective files 134or directories from the real into the fake filesystem as described in 135:ref:`real_fs_access`. For the timezone example, this could look like the following:: 136 137.. code:: python 138 139 from pathlib import Path 140 import pytz 141 from pyfakefs.fake_filesystem_unittest import TestCase 142 143 144 class ExampleTestCase(TestCase): 145 def setUp(self): 146 self.setUpPyfakefs() 147 info_dir = Path(pytz.__file__).parent / "zoneinfo" 148 self.fs.add_real_directory(info_dir) 149 150.. note:: In newer django versions, `tzdata` is used instead of `pytz`, but the usage will be the same. 151 152If you are using Django, various dependencies may expect both the project 153directory and the ``site-packages`` installation to exist in the fake filesystem. 154 155Here's an example of how to add these using pytest: 156 157.. code:: python 158 159 import os 160 import django 161 import pytest 162 163 164 @pytest.fixture 165 def fake_fs(fs): 166 PROJECT_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 167 fs.add_real_paths( 168 [ 169 PROJECT_BASE_DIR, 170 os.path.dirname(django.__file__), 171 ] 172 ) 173 return fs 174 175OS temporary directories 176------------------------ 177Tests relying on a completely empty file system on test start will fail. 178As ``pyfakefs`` does not fake the ``tempfile`` module (as described above), 179a temporary directory is required to ensure that ``tempfile`` works correctly, 180e.g., that ``tempfile.gettempdir()`` will return a valid value. This 181means that any newly created fake file system will always have either a 182directory named ``/tmp`` when running on POSIX systems, or 183``C:\Users\<user>\AppData\Local\Temp`` on Windows: 184 185.. code:: python 186 187 import os 188 189 190 def test_something(fs): 191 # the temp directory is always present at test start 192 assert len(os.listdir("/")) == 1 193 194Under macOS and linux, if the actual temp path is not `/tmp` (which will be the case if an environment variable 195`TEMPDIR`, `TEMP` or `TMP` points to another path), a symlink to the actual temp directory is additionally created 196as `/tmp` in the fake filesystem. Note that the file size of this link is ignored while 197calculating the fake filesystem size, so that the used size with an otherwise empty 198fake filesystem can always be assumed to be 0. 199Note also that the temp directory may not be what you expect, if you emulate another file system. For example, 200if you emulate Windows under Linux, the default temp directory will be at `C:\\tmp`. 201 202 203User rights 204----------- 205If you run ``pyfakefs`` tests as root (this happens by default if run in a 206docker container), ``pyfakefs`` also behaves as a root user, for example can 207write to write-protected files. This may not be the expected behavior, and 208can be changed. 209``Pyfakefs`` has a rudimentary concept of user rights, which differentiates 210between root user (with the user id 0) and any other user. By default, 211``pyfakefs`` assumes the user id of the current user, but you can change 212that using ``pyfakefs.helpers.set_uid()`` in your setup. This allows to run 213tests as non-root user in a root user environment and vice verse. 214Another possibility to run tests as non-root user in a root user environment 215is the convenience argument :ref:`allow_root_user`: 216 217.. code:: python 218 219 from pyfakefs.fake_filesystem_unittest import TestCase 220 221 222 class SomeTest(TestCase): 223 def setUp(self): 224 self.setUpPyfakefs(allow_root_user=False) 225 226``Pyfakefs`` also handles file permissions under UNIX systems while accessing files. 227If accessing files as another user and/or group, the respective group/other file 228permissions are considered. 229 230.. _usage_with_mock_open: 231 232Pyfakefs and mock_open 233---------------------- 234If you patch ``open`` using ``mock_open`` before the initialization of 235``pyfakefs``, it will not work properly, because the ``pyfakefs`` 236initialization relies on ``open`` working correctly. 237Generally, you should not need ``mock_open`` if using ``pyfakefs``, because you 238always can create the files with the needed content using ``create_file``. 239This is true for patching any filesystem functions--avoid patching them 240while working with ``pyfakefs``. 241If you still want to use ``mock_open``, make sure it is only used while 242patching is in progress. For example, if you are using ``pytest`` with the 243``mocker`` fixture used to patch ``open``, make sure that the ``fs`` fixture is 244passed before the ``mocker`` fixture to ensure this: 245 246.. code:: python 247 248 def test_mock_open_incorrect(mocker, fs): 249 # causes a recursion error 250 mocker.patch("builtins.open", mocker.mock_open(read_data="content")) 251 252 253 def test_mock_open_correct(fs, mocker): 254 # works correctly 255 mocker.patch("builtins.open", mocker.mock_open(read_data="content")) 256 257Pathlib.Path objects created outside of tests 258--------------------------------------------- 259An pattern which is more often seen with the increased usage of ``pathlib`` is the 260creation of global ``pathlib.Path`` objects (instead of string paths) that are imported 261into the tests. As these objects are created in the real filesystem, 262they do not have the same attributes as fake ``pathlib.Path`` objects, 263and both will always compare as not equal, 264regardless of the path they point to: 265 266.. code:: python 267 268 import pathlib 269 270 # This Path was made in the real filesystem, before the test 271 # stands up the fake filesystem 272 FILE_PATH = pathlib.Path(__file__).parent / "file.csv" 273 274 275 def test_path_equality(fs): 276 # This Path was made after the fake filesystem is set up, 277 # and thus patching within pathlib is in effect 278 fake_file_path = pathlib.Path(str(FILE_PATH)) 279 280 assert FILE_PATH == fake_file_path # fails, compares different objects 281 assert str(FILE_PATH) == str(fake_file_path) # succeeds, compares the actual paths 282 283Generally, mixing objects in the real filesystem and the fake filesystem 284is problematic and better avoided. 285 286.. note:: This problem only happens in Python versions up to 3.10. In Python 3.11, 287 `pathlib` has been restructured so that a pathlib path no longer contains a reference 288 to the original filesystem accessor, and it can safely be used in the fake filesystem. 289 290.. _nested_patcher_invocation: 291 292Nested file system fixtures and Patcher invocations 293--------------------------------------------------- 294``pyfakefs`` does not support nested faked file systems. Instead, it uses reference counting 295on the single fake filesystem instance. That means, if you are trying to create a fake filesystem 296inside a fake filesystem, only the reference count will increase, and any arguments you may pass 297to the patcher or fixture are ignored. Likewise, if you leave a nested fake filesystem, 298only the reference count is decreased and nothing is reverted. 299 300There are some situations where that may happen, probably without you noticing: 301 302* If you use the module- or session based variants of the ``fs`` fixture (e.g. ``fs_module`` or 303 ``fs_session``), you may still use the ``fs`` fixture in single tests. This will practically 304 reference the module- or session based fake filesystem, instead of creating a new one. 305 306.. code:: python 307 308 @pytest.fixture(scope="module", autouse=True) 309 def use_fs(fs_module): 310 # do some setup... 311 yield fs_module 312 313 314 def test_something(fs): 315 do_more_fs_setup() 316 test_something() 317 # the fs setup done in this test is not reverted! 318 319* If you invoke a ``Patcher`` instance inside a test with the ``fs`` fixture (or with an active 320 ``fs_module`` or ``fs_session`` fixture), this will be ignored. For example: 321 322.. code:: python 323 324 def test_something(fs): 325 with Patcher(allow_root_user=False): 326 # root user is still allowed 327 do_stuff() 328 329* The same is true, if you use ``setUpPyfakefs`` or ``setUpClassPyfakefs`` in a unittest context, or if you use 330 the ``patchfs`` decorator. ``Patcher`` instances created in the tests will be ignored likewise. 331 332.. _failing_dyn_patcher: 333 334Tests failing after a test using pyfakefs 335----------------------------------------- 336If previously passing tests fail after a test using ``pyfakefs``, something may be wrong with reverting the 337patches. The most likely cause is a problem with the dynamic patcher, which is invoked if modules are loaded 338dynamically during the tests. These modules are removed after the test, and reloaded the next time they are 339imported, to avoid any remaining patched functions or variables. Sometimes, there is a problem with that reload. 340 341If you want to know if your problem is indeed with the dynamic patcher, you can switch it off by setting 342:ref:`use_dynamic_patch` to `False` (here an example with pytest): 343 344.. code:: python 345 346 @pytest.fixture 347 def fs_no_dyn_patch(): 348 with Patcher(use_dynamic_patch=False): 349 yield 350 351 352 def test_something(fs_no_dyn_patch): 353 assert foo() # do the testing 354 355If in this case the following tests pass as expected, the dynamic patcher is indeed the problem. 356If your ``pyfakefs`` test also works with that setting, you may just use this. Otherwise, 357the dynamic patcher is needed, and the concrete problem has to be fixed. There is the possibility 358to add a hook for the cleanup of a specific module, which allows to change the process of unloading 359the module. This is currently used in ``pyfakefs`` for two cases: to reload ``django`` views instead of 360just unloading them (needed due to some django internals), and for the reload of a specific module 361in ``pandas``, which does not work out of the box. 362 363A cleanup handler takes the module name as an argument, and returns a Boolean that indicates if the 364cleanup was handled (by returning `True`), or if the module should still be unloaded. This handler has to 365be added to the patcher: 366 367.. code:: python 368 369 def handler_no_cleanup(_name): 370 # This is the simplest case: no cleanup is done at all. 371 # This makes only sense if you are sure that no file system functions are called. 372 return True 373 374 375 @pytest.fixture 376 def my_fs(): 377 with Patcher() as patcher: 378 patcher.cleanup_handlers["modulename"] = handler_no_cleanup 379 yield patcher.fs 380 381 382A specific problem are modules that use filesystem functions and are imported by other modules locally 383(e.g. inside a function). These kinds of modules are not correctly reset and need to be reloaded manually. 384For this case, the cleanup handler `reload_cleanup_handler` in `pyfakefs.helpers` can be used: 385 386.. code:: python 387 388 from pyfakefs.helpers import reload_cleanup_handler 389 390 391 @pytest.fixture 392 def my_fs(): 393 with Patcher() as patcher: 394 patcher.cleanup_handlers["modulename"] = reload_cleanup_handler 395 yield patcher.fs 396 397As this may not be trivial, we recommend to write an issue in ``pyfakefs`` with a reproducible example. 398We will analyze the problem, and if we find a solution we will either get this fixed in ``pyfakefs`` 399(if it is related to a commonly used module), or help you to resolve it. 400 401 402.. _`multiprocessing`: https://docs.python.org/3/library/multiprocessing.html 403.. _`subprocess`: https://docs.python.org/3/library/subprocess.html 404.. _`sqlite3`: https://docs.python.org/3/library/sqlite3.html 405.. _`GitPython`: https://pypi.org/project/GitPython/ 406.. _`plumbum`: https://pypi.org/project/plumbum/ 407.. _`Pillow`: https://pypi.org/project/Pillow/ 408.. _`pandas`: https://pypi.org/project/pandas/ 409.. _`xlrd`: https://pypi.org/project/xlrd/ 410.. _`openpyxl`: https://pypi.org/project/openpyxl/ 411