1# Copyright 2014 Altera Corporation. All Rights Reserved. 2# Copyright 2015 John McGehee 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""A base class for unit tests using the :py:class:`pyfakefs` module. 17 18This class searches `sys.modules` for modules that import the `os`, `glob`, 19`shutil`, and `tempfile` modules. 20 21The `setUp()` method binds these modules to the corresponding fake 22modules from `pyfakefs`. Further, the built in functions `file()` and 23`open()` are bound to fake functions. 24 25The `tearDownPyfakefs()` method returns the module bindings to their original 26state. 27 28It is expected that `setUp()` be invoked at the beginning of the derived 29class' `setUp()` method, and `tearDownPyfakefs()` be invoked at the end of the 30derived class' `tearDown()` method. 31 32During the test, everything uses the fake file system and modules. This means 33that even in your test, you can use familiar functions like `open()` and 34`os.makedirs()` to manipulate the fake file system. 35 36This also means existing unit tests that use the real file system can be 37retrofitted to use `pyfakefs` by simply changing their base class from 38`:py:class`unittest.TestCase` to 39`:py:class`pyfakefs.fake_filesystem_unittest.TestCase`. 40""" 41 42import sys 43if sys.version_info < (2, 7): 44 import unittest2 as unittest 45else: 46 import unittest 47import doctest 48import inspect 49from pyfakefs import fake_filesystem 50from pyfakefs import fake_filesystem_glob 51from pyfakefs import fake_filesystem_shutil 52from pyfakefs import fake_tempfile 53if sys.version_info < (3,): 54 import __builtin__ as builtins 55else: 56 import builtins 57 58import mox3.stubout 59 60def load_doctests(loader, tests, ignore, module): 61 '''Load the doctest tests for the specified module into unittest.''' 62 _patcher = Patcher() 63 globs = _patcher.replaceGlobs(vars(module)) 64 tests.addTests(doctest.DocTestSuite(module, 65 globs=globs, 66 setUp=_patcher.setUp, 67 tearDown=_patcher.tearDown)) 68 return tests 69 70 71class TestCase(unittest.TestCase): 72 def __init__(self, methodName='runTest'): 73 super(TestCase, self).__init__(methodName) 74 self._stubber = Patcher() 75 76 @property 77 def fs(self): 78 return self._stubber.fs 79 80 @property 81 def patches(self): 82 return self._stubber.patches 83 84 def setUpPyfakefs(self): 85 '''Bind the file-related modules to the :py:class:`pyfakefs` fake file 86 system instead of the real file system. Also bind the fake `file()` and 87 `open()` functions. 88 89 Invoke this at the beginning of the `setUp()` method in your unit test 90 class. 91 ''' 92 self._stubber.setUp() 93 self.addCleanup(self._stubber.tearDown) 94 95 96 def tearDownPyfakefs(self): 97 ''':meth:`pyfakefs.fake_filesystem_unittest.setUpPyfakefs` registers the 98 tear down procedure using :py:meth:`unittest.TestCase.addCleanup`. Thus this 99 method is deprecated, and remains just for backward compatibility. 100 ''' 101 pass 102 103class Patcher(object): 104 ''' 105 Instantiate a stub creator to bind and un-bind the file-related modules to 106 the :py:mod:`pyfakefs` fake modules. 107 ''' 108 SKIPMODULES = set([None, fake_filesystem, fake_filesystem_glob, 109 fake_filesystem_shutil, fake_tempfile, sys]) 110 '''Stub nothing that is imported within these modules. 111 `sys` is included to prevent `sys.path` from being stubbed with the fake 112 `os.path`. 113 ''' 114 assert None in SKIPMODULES, "sys.modules contains 'None' values; must skip them." 115 116 # To add py.test support per issue https://github.com/jmcgeheeiv/pyfakefs/issues/43, 117 # it appears that adding 'py', 'pytest', '_pytest' to SKIPNAMES will help 118 SKIPNAMES = set(['os', 'glob', 'path', 'shutil', 'tempfile']) 119 120 def __init__(self): 121 # Attributes set by _findModules() 122 self._osModules = None 123 self._globModules = None 124 self._pathModules = None 125 self._shutilModules = None 126 self._tempfileModules = None 127 self._findModules() 128 assert None not in vars(self).values(), \ 129 "_findModules() missed the initialization of an instance variable" 130 131 # Attributes set by _refresh() 132 self._stubs = None 133 self.fs = None 134 self.fake_os = None 135 self.fake_glob = None 136 self.fake_path = None 137 self.fake_shutil = None 138 self.fake_tempfile_ = None 139 self.fake_open = None 140 # _isStale is set by tearDown(), reset by _refresh() 141 self._isStale = True 142 self._refresh() 143 assert None not in vars(self).values(), \ 144 "_refresh() missed the initialization of an instance variable" 145 assert self._isStale == False, "_refresh() did not reset _isStale" 146 147 def _findModules(self): 148 '''Find and cache all modules that import file system modules. 149 Later, `setUp()` will stub these with the fake file system 150 modules. 151 ''' 152 self._osModules = set() 153 self._globModules = set() 154 self._pathModules = set() 155 self._shutilModules = set() 156 self._tempfileModules = set() 157 for name, module in set(sys.modules.items()): 158 if (module in self.SKIPMODULES or 159 (not inspect.ismodule(module)) or 160 name.split('.')[0] in self.SKIPNAMES): 161 continue 162 if 'os' in module.__dict__: 163 self._osModules.add(module) 164 if 'glob' in module.__dict__: 165 self._globModules.add(module) 166 if 'path' in module.__dict__: 167 self._pathModules.add(module) 168 if 'shutil' in module.__dict__: 169 self._shutilModules.add(module) 170 if 'tempfile' in module.__dict__: 171 self._tempfileModules.add(module) 172 173 def _refresh(self): 174 '''Renew the fake file system and set the _isStale flag to `False`.''' 175 if self._stubs is not None: 176 self._stubs.SmartUnsetAll() 177 self._stubs = mox3.stubout.StubOutForTesting() 178 179 self.fs = fake_filesystem.FakeFilesystem() 180 self.fake_os = fake_filesystem.FakeOsModule(self.fs) 181 self.fake_glob = fake_filesystem_glob.FakeGlobModule(self.fs) 182 self.fake_path = self.fake_os.path 183 self.fake_shutil = fake_filesystem_shutil.FakeShutilModule(self.fs) 184 self.fake_tempfile_ = fake_tempfile.FakeTempfileModule(self.fs) 185 self.fake_open = fake_filesystem.FakeFileOpen(self.fs) 186 187 self._isStale = False 188 189 def setUp(self, doctester=None): 190 '''Bind the file-related modules to the :py:mod:`pyfakefs` fake 191 modules real ones. Also bind the fake `file()` and `open()` functions. 192 ''' 193 if self._isStale: 194 self._refresh() 195 196 if doctester is not None: 197 doctester.globs = self.replaceGlobs(doctester.globs) 198 199 if sys.version_info < (3,): 200 # file() was eliminated in Python3 201 self._stubs.SmartSet(builtins, 'file', self.fake_open) 202 self._stubs.SmartSet(builtins, 'open', self.fake_open) 203 204 for module in self._osModules: 205 self._stubs.SmartSet(module, 'os', self.fake_os) 206 for module in self._globModules: 207 self._stubs.SmartSet(module, 'glob', self.fake_glob) 208 for module in self._pathModules: 209 self._stubs.SmartSet(module, 'path', self.fake_path) 210 for module in self._shutilModules: 211 self._stubs.SmartSet(module, 'shutil', self.fake_shutil) 212 for module in self._tempfileModules: 213 self._stubs.SmartSet(module, 'tempfile', 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 self._stubs.SmartUnsetAll() 237