1# Copyright 2008 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16This is a fork of the pymox library intended to work with Python 3. 17The file was modified by quermit@gmail.com and dawid.fatyga@gmail.com 18 19Previously, pyfakefs used just this file from the mox3 library. 20However, mox3 will soon be decommissioned, yet standard mock cannot 21be used because of the problem described in pyfakefs #182 and 22mock issue 250 (https://github.com/testing-cabal/mock/issues/250). 23Therefore just this file was forked from mox3 and incorporated 24into pyfakefs. 25""" 26 27import inspect 28 29 30class StubOutForTesting: 31 """Sample Usage: 32 33 You want os.path.exists() to always return true during testing. 34 35 stubs = StubOutForTesting() 36 stubs.Set(os.path, 'exists', lambda x: 1) 37 ... 38 stubs.UnsetAll() 39 40 The above changes os.path.exists into a lambda that returns 1. Once 41 the ... part of the code finishes, the UnsetAll() looks up the old value 42 of os.path.exists and restores it. 43 44 """ 45 46 def __init__(self): 47 self.cache = [] 48 self.stubs = [] 49 50 def __del__(self): 51 self.smart_unset_all() 52 self.unset_all() 53 54 def smart_set(self, obj, attr_name, new_attr): 55 """Replace obj.attr_name with new_attr. 56 57 This method is smart and works at the module, class, and instance level 58 while preserving proper inheritance. It will not stub out C types 59 however unless that has been explicitly allowed by the type. 60 61 This method supports the case where attr_name is a staticmethod or a 62 classmethod of obj. 63 64 Notes: 65 - If obj is an instance, then it is its class that will actually be 66 stubbed. Note that the method Set() does not do that: if obj is 67 an instance, it (and not its class) will be stubbed. 68 - The stubbing is using the builtin getattr and setattr. So, the 69 __get__ and __set__ will be called when stubbing (TODO: A better 70 idea would probably be to manipulate obj.__dict__ instead of 71 getattr() and setattr()). 72 73 Raises AttributeError if the attribute cannot be found. 74 """ 75 if (inspect.ismodule(obj) or 76 (not inspect.isclass(obj) and attr_name in obj.__dict__)): 77 orig_obj = obj 78 orig_attr = getattr(obj, attr_name) 79 80 else: 81 if not inspect.isclass(obj): 82 mro = list(inspect.getmro(obj.__class__)) 83 else: 84 mro = list(inspect.getmro(obj)) 85 86 mro.reverse() 87 88 orig_attr = None 89 90 for cls in mro: 91 try: 92 orig_obj = cls 93 orig_attr = getattr(obj, attr_name) 94 except AttributeError: 95 continue 96 97 if orig_attr is None: 98 raise AttributeError("Attribute not found.") 99 100 # Calling getattr() on a staticmethod transforms it to a 'normal' 101 # function. We need to ensure that we put it back as a staticmethod. 102 old_attribute = obj.__dict__.get(attr_name) 103 if (old_attribute is not None 104 and isinstance(old_attribute, staticmethod)): 105 orig_attr = staticmethod(orig_attr) 106 107 self.stubs.append((orig_obj, attr_name, orig_attr)) 108 setattr(orig_obj, attr_name, new_attr) 109 110 def smart_unset_all(self): 111 """Reverses all the SmartSet() calls. 112 113 Restores things to their original definition. Its okay to call 114 SmartUnsetAll() repeatedly, as later calls have no effect if no 115 SmartSet() calls have been made. 116 """ 117 self.stubs.reverse() 118 119 for args in self.stubs: 120 setattr(*args) 121 122 self.stubs = [] 123 124 def set(self, parent, child_name, new_child): 125 """Replace child_name's old definition with new_child. 126 127 Replace definition in the context of the given parent. The parent could 128 be a module when the child is a function at module scope. Or the parent 129 could be a class when a class' method is being replaced. The named 130 child is set to new_child, while the prior definition is saved away 131 for later, when unset_all() is called. 132 133 This method supports the case where child_name is a staticmethod or a 134 classmethod of parent. 135 """ 136 old_child = getattr(parent, child_name) 137 138 old_attribute = parent.__dict__.get(child_name) 139 if old_attribute is not None: 140 if isinstance(old_attribute, staticmethod): 141 old_child = staticmethod(old_child) 142 elif isinstance(old_attribute, classmethod): 143 old_child = classmethod(old_child.__func__) 144 145 self.cache.append((parent, old_child, child_name)) 146 setattr(parent, child_name, new_child) 147 148 def unset_all(self): 149 """Reverses all the Set() calls. 150 151 Restores things to their original definition. Its okay to call 152 unset_all() repeatedly, as later calls have no effect if no Set() 153 calls have been made. 154 """ 155 # Undo calls to set() in reverse order, in case set() was called on the 156 # same arguments repeatedly (want the original call to be last one 157 # undone) 158 self.cache.reverse() 159 160 for (parent, old_child, child_name) in self.cache: 161 setattr(parent, child_name, old_child) 162 self.cache = [] 163