• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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