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