OLD | NEW |
(Empty) | |
| 1 # Copyright 2014 Altera Corporation. All Rights Reserved. |
| 2 # Author: John McGehee |
| 3 # |
| 4 # Copyright 2014 John McGehee. All Rights Reserved. |
| 5 # |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 # you may not use this file except in compliance with the License. |
| 8 # You may obtain a copy of the License at |
| 9 # |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 |
| 11 # |
| 12 # Unless required by applicable law or agreed to in writing, software |
| 13 # distributed under the License is distributed on an "AS IS" BASIS, |
| 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 # See the License for the specific language governing permissions and |
| 16 # limitations under the License. |
| 17 |
| 18 """A base class for unit tests using the :py:class:`pyfakefs` module. |
| 19 |
| 20 This class searches `sys.modules` for modules that import the `os`, `glob`, |
| 21 `shutil`, and `tempfile` modules. |
| 22 |
| 23 The `setUp()` method binds these modules to the corresponding fake |
| 24 modules from `pyfakefs`. Further, the built in functions `file()` and |
| 25 `open()` are bound to fake functions. |
| 26 |
| 27 The `tearDownPyfakefs()` method returns the module bindings to their original |
| 28 state. |
| 29 |
| 30 It is expected that `setUp()` be invoked at the beginning of the derived |
| 31 class' `setUp()` method, and `tearDownPyfakefs()` be invoked at the end of the |
| 32 derived class' `tearDown()` method. |
| 33 |
| 34 During the test, everything uses the fake file system and modules. This means |
| 35 that even in your test, you can use familiar functions like `open()` and |
| 36 `os.makedirs()` to manipulate the fake file system. |
| 37 |
| 38 This also means existing unit tests that use the real file system can be |
| 39 retro-fitted to use `pyfakefs` by simply changing their base class from |
| 40 `:py:class`unittest.TestCase` to |
| 41 `:py:class`pyfakefs.fake_filesystem_unittest.TestCase`. |
| 42 """ |
| 43 |
| 44 import sys |
| 45 import unittest |
| 46 import doctest |
| 47 import inspect |
| 48 import fake_filesystem |
| 49 import fake_filesystem_glob |
| 50 import fake_filesystem_shutil |
| 51 import fake_tempfile |
| 52 |
| 53 import mock |
| 54 |
| 55 def load_doctests(loader, tests, ignore, module): |
| 56 '''Load the doctest tests for the specified module into unittest.''' |
| 57 _patcher = _Patcher() |
| 58 globs = _patcher.replaceGlobs(vars(module)) |
| 59 tests.addTests(doctest.DocTestSuite(module, |
| 60 globs=globs, |
| 61 setUp=_patcher.setUp, |
| 62 tearDown=_patcher.tearDown)) |
| 63 return tests |
| 64 |
| 65 |
| 66 class TestCase(unittest.TestCase): |
| 67 def __init__(self, methodName='runTest'): |
| 68 super(TestCase, self).__init__(methodName) |
| 69 self._stubber = _Patcher() |
| 70 |
| 71 @property |
| 72 def fs(self): |
| 73 return self._stubber.fs |
| 74 |
| 75 @property |
| 76 def patches(self): |
| 77 return self._stubber.patches |
| 78 |
| 79 def setUpPyfakefs(self): |
| 80 '''Bind the file-related modules to the :py:class:`pyfakefs` fake file |
| 81 system instead of the real file system. Also bind the fake `file()` and |
| 82 `open()` functions. |
| 83 |
| 84 Invoke this at the beginning of the `setUp()` method in your unit test |
| 85 class. |
| 86 ''' |
| 87 self._stubber.setUp() |
| 88 self.addCleanup(self._stubber.tearDown) |
| 89 |
| 90 |
| 91 def tearDownPyfakefs(self): |
| 92 ''':meth:`pyfakefs.fake_filesystem_unittest.setUpPyfakefs` registers the |
| 93 tear down procedure using :meth:unittest.TestCase.addCleanup`. Thus thi
s |
| 94 method is deprecated, and remains just for backward compatibility. |
| 95 ''' |
| 96 pass |
| 97 |
| 98 class _Patcher(object): |
| 99 ''' |
| 100 Instantiate a stub creator to bind and un-bind the file-related modules to |
| 101 the :py:module:`pyfakefs` fake modules. |
| 102 ''' |
| 103 SKIPMODULES = set([None, fake_filesystem, fake_filesystem_glob, |
| 104 fake_filesystem_shutil, fake_tempfile, unittest, |
| 105 sys]) |
| 106 '''Stub nothing that is imported within these modules. |
| 107 `sys` is included to prevent `sys.path` from being stubbed with the fake |
| 108 `os.path`. |
| 109 ''' |
| 110 assert None in SKIPMODULES, "sys.modules contains 'None' values; must skip t
hem." |
| 111 |
| 112 SKIPNAMES = set(['os', 'glob', 'path', 'shutil', 'tempfile']) |
| 113 |
| 114 def __init__(self): |
| 115 # Attributes set by _findModules() |
| 116 self._osModuleNames = None |
| 117 self._globModuleNames = None |
| 118 self._pathModuleNames = None |
| 119 self._shutilModuleNames = None |
| 120 self._tempfileModuleNames = None |
| 121 self._findModules() |
| 122 assert None not in vars(self).values(), \ |
| 123 "_findModules() missed the initialization of an instance variabl
e" |
| 124 |
| 125 # Attributes set by _refresh() |
| 126 self.fs = None |
| 127 self.fake_os = None |
| 128 self.fake_glob = None |
| 129 self.fake_path = None |
| 130 self.fake_shutil = None |
| 131 self.fake_tempfile_ = None |
| 132 self.fake_open = None |
| 133 # _isStale is set by tearDown(), reset by _refresh() |
| 134 self._isStale = True |
| 135 self._refresh() |
| 136 assert None not in vars(self).values(), \ |
| 137 "_refresh() missed the initialization of an instance variable" |
| 138 assert self._isStale == False, "_refresh() did not reset _isStale" |
| 139 |
| 140 def _findModules(self): |
| 141 '''Find and cache all modules that import file system modules. |
| 142 Later, `setUp()` will stub these with the fake file system |
| 143 modules. |
| 144 ''' |
| 145 self._osModuleNames = set() |
| 146 self._globModuleNames = set() |
| 147 self._pathModuleNames = set() |
| 148 self._shutilModuleNames = set() |
| 149 self._tempfileModuleNames = set() |
| 150 for name, module in set(sys.modules.items()): |
| 151 if module in self.SKIPMODULES or name in self.SKIPNAMES or (not insp
ect.ismodule(module)): |
| 152 continue |
| 153 if 'os' in module.__dict__ and inspect.ismodule(module.__dict__['os'
]): |
| 154 self._osModuleNames.add(name + '.os') |
| 155 if 'glob' in module.__dict__: |
| 156 self._globModuleNames.add(name + '.glob') |
| 157 if 'path' in module.__dict__: |
| 158 self._pathModuleNames.add(name + '.path') |
| 159 if 'shutil' in module.__dict__: |
| 160 self._shutilModuleNames.add(name + '.shutil') |
| 161 if 'tempfile' in module.__dict__: |
| 162 self._tempfileModuleNames.add(name + '.tempfile') |
| 163 |
| 164 def _refresh(self): |
| 165 '''Renew the fake file system and set the _isStale flag to `False`.''' |
| 166 mock.patch.stopall() |
| 167 |
| 168 self.fs = fake_filesystem.FakeFilesystem() |
| 169 self.fake_os = fake_filesystem.FakeOsModule(self.fs) |
| 170 self.fake_glob = fake_filesystem_glob.FakeGlobModule(self.fs) |
| 171 self.fake_path = self.fake_os.path |
| 172 self.fake_shutil = fake_filesystem_shutil.FakeShutilModule(self.fs) |
| 173 self.fake_tempfile_ = fake_tempfile.FakeTempfileModule(self.fs) |
| 174 self.fake_open = fake_filesystem.FakeFileOpen(self.fs) |
| 175 |
| 176 self._isStale = False |
| 177 |
| 178 def setUp(self, doctester=None): |
| 179 '''Bind the file-related modules to the :py:module:`pyfakefs` fake |
| 180 modules real ones. Also bind the fake `file()` and `open()` functions. |
| 181 ''' |
| 182 if self._isStale: |
| 183 self._refresh() |
| 184 |
| 185 if doctester is not None: |
| 186 doctester.globs = self.replaceGlobs(doctester.globs) |
| 187 |
| 188 def startPatch(self, realModuleName, fakeModule): |
| 189 if realModuleName == 'unittest.main.os': |
| 190 # Known issue with unittest.main resolving to unittest.main.Test
Program |
| 191 # See mock module bug 250, https://code.google.com/p/mock/issues
/detail?id=250. |
| 192 return |
| 193 patch = mock.patch(realModuleName, new=fakeModule) |
| 194 try: |
| 195 patch.start() |
| 196 except: |
| 197 target, attribute = realModuleName.rsplit('.', 1) |
| 198 print("Warning: Could not patch '{}' on module '{}' because '{}'
resolves to {}".format(attribute, target, target, patch.getter())) |
| 199 print(" See mock module bug 250, https://code.google.com
/p/mock/issues/detail?id=250") |
| 200 |
| 201 startPatch(self, '__builtin__.file', self.fake_open) |
| 202 startPatch(self, '__builtin__.open', self.fake_open) |
| 203 |
| 204 for module in self._osModuleNames: |
| 205 startPatch(self, module, self.fake_os) |
| 206 for module in self._globModuleNames: |
| 207 startPatch(self, module, self.fake_glob) |
| 208 for module in self._pathModuleNames: |
| 209 startPatch(self, module, self.fake_path) |
| 210 for module in self._shutilModuleNames: |
| 211 startPatch(self, module, self.fake_shutil) |
| 212 for module in self._tempfileModuleNames: |
| 213 startPatch(self, module, self.fake_tempfile_) |
| 214 |
| 215 def replaceGlobs(self, globs_): |
| 216 globs = globs_.copy() |
| 217 if self._isStale: |
| 218 self._refresh() |
| 219 if 'os' in globs: |
| 220 globs['os'] = fake_filesystem.FakeOsModule(self.fs) |
| 221 if 'glob' in globs: |
| 222 globs['glob'] = fake_filesystem_glob.FakeGlobModule(self.fs) |
| 223 if 'path' in globs: |
| 224 fake_os = globs['os'] if 'os' in globs \ |
| 225 else fake_filesystem.FakeOsModule(self.fs) |
| 226 globs['path'] = fake_os.path |
| 227 if 'shutil' in globs: |
| 228 globs['shutil'] = fake_filesystem_shutil.FakeShutilModule(self.fs) |
| 229 if 'tempfile' in globs: |
| 230 globs['tempfile'] = fake_tempfile.FakeTempfileModule(self.fs) |
| 231 return globs |
| 232 |
| 233 def tearDown(self, doctester=None): |
| 234 '''Clear the fake filesystem bindings created by `setUp()`.''' |
| 235 self._isStale = True |
| 236 mock.patch.stopall() |
OLD | NEW |