diff --git a/fake_filesystem_unittest_test.py b/fake_filesystem_unittest_test.py index 156462d0..f64f81fc 100755 --- a/fake_filesystem_unittest_test.py +++ b/fake_filesystem_unittest_test.py @@ -138,9 +138,29 @@ def test_fakepathlib(self): else: self.assertTrue(self.fs.Exists('/fake_file.txt')) -import math as path +sys.path.append(os.path.join(os.path.dirname(__file__), 'fixtures')) +import module_with_attributes + + +class TestAttributesWithFakeModuleNames(TestPyfakefsUnittestBase): + """Test that module attributes with names like `path` or `io` are not + stubbed out. + """ + + def testAttributes(self): + """Attributes of module under test are not patched""" + global path + + self.assertEqual(module_with_attributes.os, 'os attribute value') + self.assertEqual(module_with_attributes.path, 'path attribute value') + self.assertEqual(module_with_attributes.pathlib, 'pathlib attribute value') + self.assertEqual(module_with_attributes.shutil, 'shutil attribute value') + self.assertEqual(module_with_attributes.tempfile, 'tempfile attribute value') + self.assertEqual(module_with_attributes.io, 'io attribute value') + +import math as path class TestPatchPathUnittestFailing(TestPyfakefsUnittestBase): """Tests the default behavior regarding the argument patch_path: An own path module (in this case an alias to math) cannot be imported, @@ -148,7 +168,8 @@ class TestPatchPathUnittestFailing(TestPyfakefsUnittestBase): """ def __init__(self, methodName='runTest'): - super(TestPatchPathUnittestFailing, self).__init__(methodName, patch_path=True) + super(TestPatchPathUnittestFailing, self).__init__(methodName, + patch_path=True) @unittest.expectedFailure def test_own_path_module(self): @@ -161,7 +182,8 @@ class TestPatchPathUnittestPassing(TestPyfakefsUnittestBase): """ def __init__(self, methodName='runTest'): - super(TestPatchPathUnittestPassing, self).__init__(methodName, patch_path=False) + super(TestPatchPathUnittestPassing, self).__init__(methodName, + patch_path=False) def test_own_path_module(self): self.assertEqual(2, path.floor(2.5)) diff --git a/fixtures/__init__.py b/fixtures/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fixtures/module_with_attributes.py b/fixtures/module_with_attributes.py new file mode 100644 index 00000000..218d5caa --- /dev/null +++ b/fixtures/module_with_attributes.py @@ -0,0 +1,31 @@ +# Copyright 2017 John McGehee +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module is for testing pyfakefs +:py:class:`fake_filesystem_unittest.Patcher`. It defines attributes that have +the same names as file modules, sudh as 'io` and `path`. Since these are not +modules, :py:class:`fake_filesystem_unittest.Patcher` should not patch them. + +Whenever a new module is added to +:py:meth:`fake_filesystem_unittest.Patcher._findModules`, the corresponding +attribute should be added here and in the test +:py:class:`fake_filesystem_unittest_test.TestAttributesWithFakeModuleNames`. +""" + +os = 'os attribute value' +path = 'path attribute value' +pathlib = 'pathlib attribute value' +shutil = 'shutil attribute value' +tempfile = 'tempfile attribute value' +io = 'io attribute value' diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py index 5e1f7298..bc59e834 100644 --- a/pyfakefs/fake_filesystem_unittest.py +++ b/pyfakefs/fake_filesystem_unittest.py @@ -17,7 +17,8 @@ for unit tests using the :py:class:`pyfakefs` module. `fake_filesystem_unittest.TestCase` searches `sys.modules` for modules -that import the `os`, `io`, `path`, and `tempfile` modules. +that import the `os`, `io`, `path` `shutil`, `pathlib`, and `tempfile` +modules. The `setUpPyfakefs()` method binds these modules to the corresponding fake modules from `pyfakefs`. Further, the `open()` built-in is bound to a fake @@ -67,9 +68,19 @@ """The real (not faked) `os` module.""" -def load_doctests(loader, tests, ignore, module): # pylint: disable=unused-argument - """Load the doctest tests for the specified module into unittest.""" - _patcher = Patcher() +def load_doctests(loader, tests, ignore, module, + additional_skip_names=None, patch_path=True): # pylint: disable=unused-argument + """Load the doctest tests for the specified module into unittest. + Args: + loader, tests, ignore : arguments passed in from `load_tests()` + module: module under test + additional_skip_names: see :py:class:`TestCase` for an explanation + patch_path: see :py:class:`TestCase` for an explanation + + File `example_test.py` in the pyfakefs release provides a usage example. + """ + _patcher = Patcher(additional_skip_names=additional_skip_names, + patch_path=patch_path) globs = _patcher.replaceGlobs(vars(module)) tests.addTests(doctest.DocTestSuite(module, globs=globs, @@ -88,18 +99,22 @@ def __init__(self, methodName='runTest', additional_skip_names=None, patch_path= file system related modules. Args: - methodName: the name of the test method (as in unittest.TestCase) - additional_skip_names: names of modules inside of which no module replacement - shall be done - (additionally to the hard-coded list: 'os', 'glob', 'path', 'tempfile', 'io') + methodName: the name of the test method (same as unittest.TestCase) + additional_skip_names: names of modules inside of which no module + replacement shall be performed, in addition to the names in + attribute :py:attr:`fake_filesystem_unittest.Patcher.SKIPNAMES`. patch_path: if False, modules named 'path' will not be patched with the fake 'os.path' module. Set this to False when you need to import - some other module named 'path', for example, - `from my_module import path` - Irrespective of patch_path, module 'os.path' is still correctly faked - if imported the usual way using `import os` or `import os.path`. + some other module named 'path', for example:: + from my_module import path + Irrespective of patch_path, module 'os.path' is still correctly faked + if imported the usual way using `import os` or `import os.path`. - Example usage in a derived test class: + If you specify arguments `additional_skip_names` or `patch_path` here + and you have DocTests, consider also specifying the same arguments to + :py:func:`load_doctests`. + + Example usage in a derived test class:: class MyTestCase(fake_filesystem_unittest.TestCase): def __init__(self, methodName='runTest'): @@ -190,9 +205,14 @@ class Patcher(object): """ Instantiate a stub creator to bind and un-bind the file-related modules to the :py:mod:`pyfakefs` fake modules. - For usage outside of TestCase (for example with pytest) use: - >>> with Patcher(): - >>> doStuff() + + The arguments are explained in :py:class:`TestCase`. + + :py:class:`Patcher` is used in :py:class:`TestCase`. :py:class:`Patcher` + also works as a context manager for PyTest:: + + with Patcher(): + doStuff() """ SKIPMODULES = set([None, fake_filesystem, fake_filesystem_shutil, fake_tempfile, sys]) @@ -276,17 +296,21 @@ def _findModules(self): (not inspect.ismodule(module)) or name.split('.')[0] in self._skipNames): continue - if 'os' in module.__dict__: + # IMPORTANT TESTING NOTE: Whenever you add a new module below, test + # it by adding an attribute in fixtures/module_with_attributes.py + # and a test in fake_filesystem_unittest_test.py, class + # TestAttributesWithFakeModuleNames. + if inspect.ismodule(module.__dict__.get('os')): self._os_modules.add(module) - if self._patchPath and 'path' in module.__dict__: + if self._patchPath and inspect.ismodule(module.__dict__.get('path')): self._path_modules.add(module) - if self.HAS_PATHLIB and 'pathlib' in module.__dict__: + if self.HAS_PATHLIB and inspect.ismodule(module.__dict__.get('pathlib')): self._pathlib_modules.add(module) - if 'shutil' in module.__dict__: + if inspect.ismodule(module.__dict__.get('shutil')): self._shutil_modules.add(module) - if 'tempfile' in module.__dict__: + if inspect.ismodule(module.__dict__.get('tempfile')): self._tempfile_modules.add(module) - if 'io' in module.__dict__: + if inspect.ismodule(module.__dict__.get('io')): self._io_modules.add(module) def _refresh(self):