aboutsummaryrefslogtreecommitdiff
path: root/docs/usage.rst
blob: f90377ff9dcab94294e866ec6c287cda64d4b4a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
Usage
=====

Test Scenarios
--------------
There are several approaches for implementing tests using ``pyfakefs``.

Patch using the pytest plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``pyfakefs`` functions as a `pytest`_ plugin that provides the `fs` fixture,
which is registered at installation time.
Using this fixture automatically patches all file system functions with
the fake file system functions. It also allows to access several
convenience methods (see :ref:`convenience_methods`).

Here is an example for a simple test:

.. code:: python

   def my_fakefs_test(fs):
       # "fs" is the reference to the fake file system
       fs.create_file("/var/data/xx1.txt")
       assert os.path.exists("/var/data/xx1.txt")

If you are bothered by the ``pylint`` warning,
``C0103: Argument name "fs" doesn't conform to snake_case naming style
(invalid-name)``,
you can define a longer name in your ``conftest.py`` and use that in your
tests:

.. code:: python

    @pytest.fixture
    def fake_filesystem(fs):  # pylint:disable=invalid-name
        """Variable name 'fs' causes a pylint warning. Provide a longer name
        acceptable to pylint for use in tests.
        """
        yield fs

Class-, module- and session-scoped fixtures
...........................................
For convenience, class-, module- and session-scoped fixtures with the same
functionality are provided, named ``fs_class``, ``fs_module`` and ``fs_session``,
respectively.

.. caution:: If any of these fixtures is active, any other ``fs`` fixture will
  not setup / tear down the fake filesystem in the current scope; instead, it
  will just serve as a reference to the active fake filesystem. That means that changes
  done in the fake filesystem inside a test will remain there until the respective scope
  ends.

Patch using fake_filesystem_unittest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are using the Python ``unittest`` package, the easiest approach is to
use test classes derived from ``fake_filesystem_unittest.TestCase``.

If you call ``setUpPyfakefs()`` in your ``setUp()``, ``pyfakefs`` will
automatically find all real file functions and modules, and stub these out
with the fake file system functions and modules:

.. code:: python

    from pyfakefs.fake_filesystem_unittest import TestCase


    class ExampleTestCase(TestCase):
        def setUp(self):
            self.setUpPyfakefs()

        def test_create_file(self):
            file_path = "/test/file.txt"
            self.assertFalse(os.path.exists(file_path))
            self.fs.create_file(file_path)
            self.assertTrue(os.path.exists(file_path))

The usage is explained in more detail in :ref:`auto_patch` and
demonstrated in the files `example.py`_ and `example_test.py`_.

If your setup is the same for all tests in a class, you can use the class setup
method ``setUpClassPyfakefs`` instead:

.. code:: python

    from pyfakefs.fake_filesystem_unittest import TestCase


    class ExampleTestCase(TestCase):
        @classmethod
        def setUpClass(cls):
            cls.setUpClassPyfakefs()
            # setup the fake filesystem using standard functions
            pathlib.Path("/test/file1.txt").touch()
            # you can also access the fake fs via fake_fs() if needed
            cls.fake_fs().create_file("/test/file2.txt", contents="test")

        def test1(self):
            self.assertTrue(os.path.exists("/test/file1.txt"))
            self.assertTrue(os.path.exists("/test/file2.txt"))

        def test2(self):
            self.assertTrue(os.path.exists("/test/file1.txt"))
            file_path = "/test/file3.txt"
            # self.fs is the same instance as cls.fake_fs() above
            self.fs.create_file(file_path)
            self.assertTrue(os.path.exists(file_path))

.. note:: This feature cannot be used with a Python version before Python 3.8 due to
  a missing feature in ``unittest``.

.. caution:: If this is used, any changes made in the fake filesystem inside a test
  will remain there for all following tests in the test class, if they are not reverted
  in the test itself.


Patch using fake_filesystem_unittest.Patcher
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are using other means of testing like `nose`_,
you can do the patching using ``fake_filesystem_unittest.Patcher``--the class
doing the actual work of replacing the filesystem modules with the fake modules
in the first two approaches.

The easiest way is to just use ``Patcher`` as a context manager:

.. code:: python

   from pyfakefs.fake_filesystem_unittest import Patcher

   with Patcher() as patcher:
       # access the fake_filesystem object via patcher.fs
       patcher.fs.create_file("/foo/bar", contents="test")

       # the following code works on the fake filesystem
       with open("/foo/bar") as f:
           contents = f.read()

You can also initialize ``Patcher`` manually:

.. code:: python

   from pyfakefs.fake_filesystem_unittest import Patcher

   patcher = Patcher()
   patcher.setUp()  # called in the initialization code
   ...
   patcher.tearDown()  # somewhere in the cleanup code

Patch using fake_filesystem_unittest.patchfs decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is basically a convenience wrapper for the previous method.
If you are not using ``pytest`` and  want to use the fake filesystem for a
single function, you can write:

.. code:: python

   from pyfakefs.fake_filesystem_unittest import patchfs


   @patchfs
   def test_something(fake_fs):
       # access the fake_filesystem object via fake_fs
       fake_fs.create_file("/foo/bar", contents="test")

Note that ``fake_fs`` is a positional argument and the argument name does
not matter. If there are additional ``mock.patch`` decorators that also
create positional arguments, the argument order is the same as the decorator
order, as shown here:

.. code:: python

   @patchfs
   @mock.patch("foo.bar")
   def test_something(fake_fs, mocked_bar):
       ...


   @mock.patch("foo.bar")
   @patchfs
   def test_something(mocked_bar, fake_fs):
       ...

.. note::
  Avoid writing the ``patchfs`` decorator *between* ``mock.patch`` operators,
  as the order will not be what you expect. Due to implementation details,
  all arguments created by ``mock.patch`` decorators are always expected to
  be contiguous, regardless of other decorators positioned between them.

.. caution::
  In previous versions, the keyword argument `fs` has been used instead,
  which had to be positioned *after* all positional arguments regardless of
  the decorator order. If you upgrade from a version before pyfakefs 4.2,
  you may have to adapt the argument order.

You can also use this to make a single unit test use the fake fs:

.. code:: python

    class TestSomething(unittest.TestCase):
        @patchfs
        def test_something(self, fs):
            fs.create_file("/foo/bar", contents="test")


.. _customizing_patcher:

Customizing patching
--------------------

``fake_filesystem_unittest.Patcher`` provides a few arguments to adapt
patching for cases where it does not work out of the box. These arguments
can also be used with ``unittest`` and ``pytest``.

Using custom arguments
~~~~~~~~~~~~~~~~~~~~~~
The following sections describe how to apply these arguments in different
scenarios, using the argument :ref:`allow_root_user` as an example.

Patcher
.......
If you use the ``Patcher`` directly, you can just pass the arguments in the
constructor:

.. code:: python

  from pyfakefs.fake_filesystem_unittest import Patcher

  with Patcher(allow_root_user=False) as patcher:
      ...

Pytest
......

In case of ``pytest``, you have two possibilities:

- The standard way to customize the ``fs`` fixture is to write your own
  fixture which uses the ``Patcher`` with arguments as has been shown above:

.. code:: python

  import pytest
  from pyfakefs.fake_filesystem_unittest import Patcher


  @pytest.fixture
  def fs_no_root():
      with Patcher(allow_root_user=False) as patcher:
          yield patcher.fs


  def test_something(fs_no_root):
      ...

- You can also pass the arguments using ``@pytest.mark.parametrize``. Note that
  you have to provide `all Patcher arguments`_ before the needed ones, as
  keyword arguments cannot be used, and you have to add ``indirect=True``.
  This makes it less readable, but gives you a quick possibility to adapt a
  single test:

.. code:: python

  import pytest


  @pytest.mark.parametrize("fs", [[None, None, None, False]], indirect=True)
  def test_something(fs):
      ...

Unittest
........
If you are using ``fake_filesystem_unittest.TestCase``, the arguments can be
passed to ``setUpPyfakefs()``, which will pass them to the ``Patcher``
instance:

.. code:: python

  from pyfakefs.fake_filesystem_unittest import TestCase


  class SomeTest(TestCase):
      def setUp(self):
          self.setUpPyfakefs(allow_root_user=False)

      def testSomething(self):
          ...

patchfs
.......
If you use the ``patchfs`` decorator, you can pass the arguments directly to
the decorator:

.. code:: python

  from pyfakefs.fake_filesystem_unittest import patchfs


  @patchfs(allow_root_user=False)
  def test_something(fake_fs):
      ...


List of custom arguments
~~~~~~~~~~~~~~~~~~~~~~~~

Following is a description of the optional arguments that can be used to
customize ``pyfakefs``.

.. _modules_to_reload:

modules_to_reload
.................
``Pyfakefs`` patches modules that are imported before starting the test by
finding and replacing file system modules in all loaded modules at test
initialization time.
This allows to automatically patch file system related modules that are:

- imported directly, for example:

.. code:: python

  import os
  import pathlib.Path

- imported as another name:

.. code:: python

  import os as my_os

- imported using one of these two specially handled statements:

.. code:: python

  from os import path
  from pathlib import Path

Additionally, functions from file system related modules are patched
automatically if imported like:

.. code:: python

  from os.path import exists
  from os import stat

This also works if importing the functions as another name:

.. code:: python

  from os.path import exists as my_exists
  from io import open as io_open
  from builtins import open as bltn_open

There are a few cases where automatic patching does not work. We know of at
least two specific cases where this is the case:

Initializing a default argument with a file system function is not patched
automatically due to performance reasons (though it can be switched on using
:ref:`patch_default_args`):

.. code:: python

  import os


  def check_if_exists(filepath, file_exists=os.path.exists):
      return file_exists(filepath)


If initializing a global variable using a file system function, the
initialization will be done using the real file system:

.. code:: python

  from pathlib import Path

  path = Path("/example_home")

In this case, ``path`` will hold the real file system path inside the test.
The same is true, if a file system function is used in a decorator (this is
an example from a related issue):

.. code:: python

  import pathlib


  @click.command()
  @click.argument("foo", type=click.Path(path_type=pathlib.Path))
  def hello(foo):
      pass

To get these cases to work as expected under test, the respective modules
containing the code shall be added to the ``modules_to_reload`` argument (a
module list).
The passed modules will be reloaded, thus allowing ``pyfakefs`` to patch them
dynamically. All modules loaded after the initial patching described above
will be patched using this second mechanism.

Given that the example function ``check_if_exists`` shown above is located in
the file ``example/sut.py``, the following code will work:

.. code:: python

  import example

  # example using unittest
  class ReloadModuleTest(fake_filesystem_unittest.TestCase):
      def setUp(self):
          self.setUpPyfakefs(modules_to_reload=[example.sut])

      def test_path_exists(self):
          file_path = "/foo/bar"
          self.fs.create_dir(file_path)
          self.assertTrue(example.sut.check_if_exists(file_path))


  # example using pytest
  @pytest.mark.parametrize("fs", [[None, [example.sut]]], indirect=True)
  def test_path_exists(fs):
      file_path = "/foo/bar"
      fs.create_dir(file_path)
      assert example.sut.check_if_exists(file_path)


  # example using Patcher
  def test_path_exists():
      with Patcher(modules_to_reload=[example.sut]) as patcher:
          file_path = "/foo/bar"
          patcher.fs.create_dir(file_path)
          assert example.sut.check_if_exists(file_path)


  # example using patchfs decorator
  @patchfs(modules_to_reload=[example.sut])
  def test_path_exists(fs):
      file_path = "/foo/bar"
      fs.create_dir(file_path)
      assert example.sut.check_if_exists(file_path)


.. note:: If the reloaded modules depend on each other (e.g. one imports the other),
  the order in which they are reloaded matters. The dependent module should be reloaded
  first, so that on reloading the depending module it is already correctly patched.


modules_to_patch
................
Sometimes there are file system modules in other packages that are not
patched in standard ``pyfakefs``. To allow patching such modules,
``modules_to_patch`` can be used by adding a fake module implementation for
a module name. The argument is a dictionary of fake modules mapped to the
names to be faked.

This mechanism is used in ``pyfakefs`` itself to patch the external modules
`pathlib2` and `scandir` if present, and the following example shows how to
fake a module in Django that uses OS file system functions (note that this
has now been been integrated into ``pyfakefs``):

.. code:: python

  class FakeLocks:
      """django.core.files.locks uses low level OS functions, fake it."""

      _locks_module = django.core.files.locks

      def __init__(self, fs):
          """Each fake module expects the fake file system as an __init__
          parameter."""
          # fs represents the fake filesystem; for a real example, it can be
          # saved here and used in the implementation
          pass

      @staticmethod
      def lock(f, flags):
          return True

      @staticmethod
      def unlock(f):
          return True

      def __getattr__(self, name):
          return getattr(self._locks_module, name)


  ...
  # test code using Patcher
  with Patcher(modules_to_patch={"django.core.files.locks": FakeLocks}):
      test_django_stuff()

  # test code using unittest
  class TestUsingDjango(fake_filesystem_unittest.TestCase):
      def setUp(self):
          self.setUpPyfakefs(modules_to_patch={"django.core.files.locks": FakeLocks})

      def test_django_stuff(self):
          ...


  # test code using pytest
  @pytest.mark.parametrize(
      "fs", [[None, None, {"django.core.files.locks": FakeLocks}]], indirect=True
  )
  def test_django_stuff(fs):
      ...


  # test code using patchfs decorator
  @patchfs(modules_to_patch={"django.core.files.locks": FakeLocks})
  def test_django_stuff(fake_fs):
      ...

additional_skip_names
.....................
This may be used to add modules that shall not be patched. This is mostly
used to avoid patching the Python file system modules themselves, but may be
helpful in some special situations, for example if a testrunner needs to access
the file system after test setup. To make this possible, the affected module
can be added to ``additional_skip_names``:

.. code:: python

  with Patcher(additional_skip_names=["pydevd"]) as patcher:
      patcher.fs.create_file("foo")

Alternatively to the module names, the modules themselves may be used:

.. code:: python

  import pydevd

  with Patcher(additional_skip_names=[pydevd]) as patcher:
      patcher.fs.create_file("foo")

.. _allow_root_user:

allow_root_user
...............
This is ``True`` by default, meaning that the user is considered a root user
if the real user is a root user (e.g. has the user ID 0). If you want to run
your tests as a non-root user regardless of the actual user rights, you may
want to set this to ``False``.

use_known_patches
.................
Some libraries are known to require patching in order to work with
``pyfakefs``.
If ``use_known_patches`` is set to ``True`` (the default), ``pyfakefs`` patches
these libraries so that they will work with the fake filesystem. Currently, this
includes patches for ``pandas`` read methods like ``read_csv`` and
``read_excel``, and for ``Django`` file locks--more may follow. Ordinarily,
the default value of ``use_known_patches`` should be used, but it is present
to allow users to disable this patching in case it causes any problems. It
may be removed or replaced by more fine-grained arguments in future releases.

patch_open_code
...............
Since Python 3.8, the ``io`` module has the function ``open_code``, which
opens a file read-only and is used to open Python code files. By default, this
function is not patched, because the files it opens usually belong to the
executed library code and are not present in the fake file system.
Under some circumstances, this may not be the case, and the opened file
lives in the fake filesystem. For these cases, you can set ``patch_open_code``
to ``PatchMode.ON``. If you just want to patch ``open_case`` for files that
live in the fake filesystem, and use the real function for the rest, you can
set ``patch_open_code`` to ``PatchMode.AUTO``:

.. code:: python

  from pyfakefs.fake_filesystem_unittest import PatchMode


  @patchfs(patch_open_code=PatchMode.AUTO)
  def test_something(fs):
      ...

.. note:: This argument is subject to change or removal in future
  versions of ``pyfakefs``, depending on the upcoming use cases.

.. _patch_default_args:

patch_default_args
..................
As already mentioned, a default argument that is initialized with a file
system function is not patched automatically:

.. code:: python

  import os


  def check_if_exists(filepath, file_exists=os.path.exists):
      return file_exists(filepath)

As this is rarely needed, and the check to patch this automatically is quite
expansive, it is not done by default. Using ``patch_default_args`` will
search for this kind of default arguments and patch them automatically.
You could also use the :ref:`modules_to_reload` option with the module that
contains the default argument instead, if you want to avoid the overhead.

.. note:: There are some cases where this option dees not work:

  - if default arguments are *computed* using file system functions:

    .. code:: python

      import os


      def some_function(use_bar=os.path.exists("/foo/bar")):
          return do_something() if use_bar else do_something_else()

  - if the default argument is an instance of ``pathlib.Path``:

    .. code:: python

      import pathlib


      def foobar(dir_arg=pathlib.Path.cwd() / "logs"):
          do_something(dir_arg)

  In both cases the default arguments behave like global variables that use a file system function
  (which they basically are), and can only be handled using :ref:`modules_to_reload`.


use_cache
.........
If True (the default), patched and non-patched modules are cached between tests
to avoid the performance hit of the file system function lookup (the
patching itself is reverted after each test). This argument allows to turn it off in case it causes any problems:

.. code:: python

  @patchfs(use_cache=False)
  def test_something(fake_fs):
      fake_fs.create_file("foo", contents="test")
      ...

If using ``pytest``, the cache is always cleared before the final test shutdown, as there has been a problem
happening on shutdown related to removing the cached modules.
This does not happen for other test methods so far.

If you think you have encountered a similar problem with ``unittest``, you may try to clear the cache
during module shutdown using the class method for clearing the cache:

.. code:: python

  from pyfakefs.fake_filesystem_unittest import Patcher


  def tearDownModule():
      Patcher.clear_fs_cache()

Please write an issue if you encounter any problem that can be fixed by using this parameter.

If you want to clear the cache just for a specific test instead, you can call
``clear_cache`` on the ``Patcher`` or the ``fake_filesystem`` instance:

.. code:: python

  def test_something(fs):  # using pytest fixture
      fs.clear_cache()
      ...


.. _convenience_methods:

Using convenience methods
-------------------------
While ``pyfakefs`` can be used just with the standard Python file system
functions, there are few convenience methods in ``fake_filesystem`` that can
help you setting up your tests. The methods can be accessed via the
``fake_filesystem`` instance in your tests: ``Patcher.fs``, the ``fs``
fixture in pytest, ``TestCase.fs`` for ``unittest``, and the ``fs`` argument
for the ``patchfs`` decorator.

File creation helpers
~~~~~~~~~~~~~~~~~~~~~
To create files, directories or symlinks together with all the directories
in the path, you may use ``create_file()``, ``create_dir()``,
``create_symlink()`` and ``create_link()``, respectively.

``create_file()`` also allows you to set the file mode and the file contents
together with the encoding if needed. Alternatively, you can define a file
size without contents--in this case, you will not be able to perform
standard I\O operations on the file (may be used to fill up the file system
with large files, see also :ref:`set-fs-size`).

.. code:: python

    from pyfakefs.fake_filesystem_unittest import TestCase


    class ExampleTestCase(TestCase):
        def setUp(self):
            self.setUpPyfakefs()

        def test_create_file(self):
            file_path = "/foo/bar/test.txt"
            self.fs.create_file(file_path, contents="test")
            with open(file_path) as f:
                self.assertEqual("test", f.read())

``create_dir()`` behaves like ``os.makedirs()``.
``create_symlink`` and ``create_link`` behave like ``os.symlink`` and
``os.link``, with any missing parent directories of the link created
automatically.

.. caution::
  The first two arguments in ``create_symlink`` are reverted in relation to
  ``os.symlink`` for historical reasons.

.. _real_fs_access:

Access to files in the real file system
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to have read access to real files or directories, you can map
them into the fake file system using ``add_real_file()``,
``add_real_directory()``, ``add_real_symlink()`` and ``add_real_paths()``.
They take a file path, a directory path, a symlink path, or a list of paths,
respectively, and make them accessible from the fake file system. By
default, the contents of the mapped files and directories are read only on
demand, so that mapping them is relatively cheap. The access to the files is
by default read-only, but even if you add them using ``read_only=False``,
the files are written only in the fake system (e.g. in memory). The real
files are never changed.

``add_real_file()``, ``add_real_directory()`` and ``add_real_symlink()`` also
allow you to map a file or a directory tree into another location in the
fake filesystem via the argument ``target_path``.

.. code:: python

    from pyfakefs.fake_filesystem_unittest import TestCase


    class ExampleTestCase(TestCase):

        fixture_path = os.path.join(os.path.dirname(__file__), "fixtures")

        def setUp(self):
            self.setUpPyfakefs()
            # make the file accessible in the fake file system
            self.fs.add_real_directory(self.fixture_path)

        def test_using_fixture(self):
            with open(os.path.join(self.fixture_path, "fixture1.txt")) as f:
                # file contents are copied to the fake file system
                # only at this point
                contents = f.read()

You can do the same using ``pytest`` by using a fixture for test setup:

.. code:: python

    import pytest
    import os

    fixture_path = os.path.join(os.path.dirname(__file__), "fixtures")


    @pytest.fixture
    def my_fs(fs):
        fs.add_real_directory(fixture_path)
        yield fs


    @pytest.mark.usefixtures("my_fs")
    def test_using_fixture():
        with open(os.path.join(fixture_path, "fixture1.txt")) as f:
            contents = f.read()

.. note::
  If you are not using the fixture directly in the test, you can use
  ``@pytest.mark.usefixtures`` instead of passing the fixture as an argument.
  This avoids warnings about unused arguments from linters.

When using ``pytest`` another option is to load the contents of the real file
in a fixture and pass this fixture to the test function **before** passing
the ``fs`` fixture.

.. code:: python

    import pytest
    import os


    @pytest.fixture
    def content():
        fixture_path = os.path.join(os.path.dirname(__file__), "fixtures")
        with open(os.path.join(fixture_path, "fixture1.txt")) as f:
            contents = f.read()
        return contents


    def test_using_file_contents(content, fs):
        fs.create_file("fake/path.txt")
        assert content != ""


Handling mount points
~~~~~~~~~~~~~~~~~~~~~
Under Linux and macOS, the root path (``/``) is the only mount point created
in the fake file system. If you need support for more mount points, you can add
them using ``add_mount_point()``.

Under Windows, drives and UNC paths are internally handled as mount points.
Adding a file or directory on another drive or UNC path automatically
adds a mount point for that drive or UNC path root if needed. Explicitly
adding mount points shall not be needed under Windows.

A mount point has a separate device ID (``st_dev``) under all systems, and
some operations (like ``rename``) are not possible for files located on
different mount points. The fake file system size (if used) is also set per
mount point.

.. _set-fs-size:

Setting the file system size
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to know the file system size in your tests (for example for
testing cleanup scripts), you can set the fake file system size using
``set_disk_usage()``. By default, this sets the total size in bytes of the
root partition; if you add a path as parameter, the size will be related to
the mount point (see above) the path is related to.

By default, the size of the fake file system is set to 1 TB (which
for most tests can be considered as infinite). As soon as you set a
size, all files will occupy the space according to their size,
and you may fail to create new files if the fake file system is full.

.. code:: python

    from pyfakefs.fake_filesystem_unittest import TestCase


    class ExampleTestCase(TestCase):
        def setUp(self):
            self.setUpPyfakefs()
            self.fs.set_disk_usage(100)

        def test_disk_full(self):
            with open("/foo/bar.txt", "w") as f:
                with self.assertRaises(OSError):
                    f.write("a" * 200)
                    f.flush()

To get the file system size, you may use ``get_disk_usage()``, which is
modeled after ``shutil.disk_usage()``.

Suspending patching
~~~~~~~~~~~~~~~~~~~
Sometimes, you may want to access the real filesystem inside the test with
no patching applied. This can be achieved by using the ``pause/resume``
functions, which exist in ``fake_filesystem_unittest.Patcher``,
``fake_filesystem_unittest.TestCase`` and ``fake_filesystem.FakeFilesystem``.
There is also a context manager class ``fake_filesystem_unittest.Pause``
which encapsulates the calls to ``pause()`` and ``resume()``.

Here is an example that tests the usage with the ``pyfakefs`` pytest fixture:

.. code:: python

    from pyfakefs.fake_filesystem_unittest import Pause


    def test_pause_resume_contextmanager(fs):
        fake_temp_file = tempfile.NamedTemporaryFile()
        assert os.path.exists(fake_temp_file.name)
        fs.pause()
        assert not os.path.exists(fake_temp_file.name)
        real_temp_file = tempfile.NamedTemporaryFile()
        assert os.path.exists(real_temp_file.name)
        fs.resume()
        assert not os.path.exists(real_temp_file.name)
        assert os.path.exists(fake_temp_file.name)

Here is the same code using a context manager:

.. code:: python

    from pyfakefs.fake_filesystem_unittest import Pause


    def test_pause_resume_contextmanager(fs):
        fake_temp_file = tempfile.NamedTemporaryFile()
        assert os.path.exists(fake_temp_file.name)
        with Pause(fs):
            assert not os.path.exists(fake_temp_file.name)
            real_temp_file = tempfile.NamedTemporaryFile()
            assert os.path.exists(real_temp_file.name)
        assert not os.path.exists(real_temp_file.name)
        assert os.path.exists(fake_temp_file.name)

Simulating other file systems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Pyfakefs`` supports Linux, macOS and Windows operating systems. By default,
the file system of the OS where the tests run is assumed, but it is possible
to simulate other file systems to some extent. To set a specific file
system, you can change ``pyfakefs.FakeFilesystem.os`` to one of
``OSType.LINUX``, ``OSType.MACOS`` and ``OSType.WINDOWS``. On doing so, the
behavior of ``pyfakefs`` is adapted to the respective file system. Note that
setting this causes the fake file system to be reset, so you should call it
before adding any files.

Setting the ``os`` attributes changes a number of ``pyfakefs.FakeFilesystem``
attributes, which can also be set separately if needed:

  - ``is_windows_fs`` -  if ``True`` a Windows file system (NTFS) is assumed
  - ``is_macos`` - if ``True`` and ``is_windows_fs`` is ``False``, the
    standard macOS file system (HFS+) is assumed
  - if ``is_windows_fs`` and ``is_macos`` are ``False``, a Linux file system
    (something like ext3) is assumed
  - ``is_case_sensitive`` is set to ``True`` under Linux and to ``False``
    under Windows and macOS by default - you can change it to change the
    respective behavior
  - ``path_separator`` is set to ``\`` under Windows and to ``/`` under Posix,
    ``alternative_path_separator`` is set to ``/`` under Windows and to
    ``None`` under Posix--these can also be adapted if needed

The following test works both under Windows and Linux:

.. code:: python

  from pyfakefs.fake_filesystem import OSType


  def test_windows_paths(fs):
      fs.os = OSType.WINDOWS
      assert r"C:\foo\bar" == os.path.join("C:\\", "foo", "bar")
      assert os.path.splitdrive(r"C:\foo\bar") == ("C:", r"\foo\bar")
      assert os.path.ismount("C:")

Set file as inaccessible under Windows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normally, if you try to set a file or directory as inaccessible using ``chmod`` under
Windows, the value you provide is masked by a value that always ensures that no read
permissions for any user are removed. In reality, there is the possibility to make
a file or directory unreadable using the Windows ACL API, which is not directly
supported in the Python filesystem API. To make this possible to test, there is the
possibility to use the ``force_unix_mode`` argument to ``FakeFilesystem.chmod``:

.. code:: python

    def test_is_file_for_unreadable_dir_windows(fs):
        fs.os = OSType.WINDOWS
        path = pathlib.Path("/foo/bar")
        fs.create_file(path)
        # normal chmod does not really set the mode to 0
        self.fs.chmod("/foo", 0o000)
        assert path.is_file()
        # but it does in forced UNIX mode
        fs.chmod("/foo", 0o000, force_unix_mode=True)
        with pytest.raises(PermissionError):
            path.is_file()


.. _`example.py`: https://github.com/pytest-dev/pyfakefs/blob/main/pyfakefs/tests/example.py
.. _`example_test.py`: https://github.com/pytest-dev/pyfakefs/blob/main/pyfakefs/tests/example_test.py
.. _`pytest`: https://doc.pytest.org
.. _`nose`: https://docs.nose2.io/en/latest/
.. _`all Patcher arguments`: https://pytest-pyfakefs.readthedocs.io/en/latest/modules.html#pyfakefs.fake_filesystem_unittest.Patcher