| #!/usr/bin/python2.4 | |
| # | |
| # Copyright 2008 Google Inc. | |
| # | |
| # 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 file is used for testing. The original is at: | |
| # http://code.google.com/p/pymox/ | |
| import inspect | |
| class StubOutForTesting: | |
| """Sample Usage: | |
| You want os.path.exists() to always return true during testing. | |
| stubs = StubOutForTesting() | |
| stubs.Set(os.path, 'exists', lambda x: 1) | |
| ... | |
| stubs.UnsetAll() | |
| The above changes os.path.exists into a lambda that returns 1. Once | |
| the ... part of the code finishes, the UnsetAll() looks up the old value | |
| of os.path.exists and restores it. | |
| """ | |
| def __init__(self): | |
| self.cache = [] | |
| self.stubs = [] | |
| def __del__(self): | |
| self.SmartUnsetAll() | |
| self.UnsetAll() | |
| def SmartSet(self, obj, attr_name, new_attr): | |
| """Replace obj.attr_name with new_attr. This method is smart and works | |
| at the module, class, and instance level while preserving proper | |
| inheritance. It will not stub out C types however unless that has been | |
| explicitly allowed by the type. | |
| This method supports the case where attr_name is a staticmethod or a | |
| classmethod of obj. | |
| Notes: | |
| - If obj is an instance, then it is its class that will actually be | |
| stubbed. Note that the method Set() does not do that: if obj is | |
| an instance, it (and not its class) will be stubbed. | |
| - The stubbing is using the builtin getattr and setattr. So, the __get__ | |
| and __set__ will be called when stubbing (TODO: A better idea would | |
| probably be to manipulate obj.__dict__ instead of getattr() and | |
| setattr()). | |
| Raises AttributeError if the attribute cannot be found. | |
| """ | |
| if (inspect.ismodule(obj) or | |
| (not inspect.isclass(obj) and obj.__dict__.has_key(attr_name))): | |
| orig_obj = obj | |
| orig_attr = getattr(obj, attr_name) | |
| else: | |
| if not inspect.isclass(obj): | |
| mro = list(inspect.getmro(obj.__class__)) | |
| else: | |
| mro = list(inspect.getmro(obj)) | |
| mro.reverse() | |
| orig_attr = None | |
| for cls in mro: | |
| try: | |
| orig_obj = cls | |
| orig_attr = getattr(obj, attr_name) | |
| except AttributeError: | |
| continue | |
| if orig_attr is None: | |
| raise AttributeError("Attribute not found.") | |
| # Calling getattr() on a staticmethod transforms it to a 'normal' function. | |
| # We need to ensure that we put it back as a staticmethod. | |
| old_attribute = obj.__dict__.get(attr_name) | |
| if old_attribute is not None and isinstance(old_attribute, staticmethod): | |
| orig_attr = staticmethod(orig_attr) | |
| self.stubs.append((orig_obj, attr_name, orig_attr)) | |
| setattr(orig_obj, attr_name, new_attr) | |
| def SmartUnsetAll(self): | |
| """Reverses all the SmartSet() calls, restoring things to their original | |
| definition. Its okay to call SmartUnsetAll() repeatedly, as later calls | |
| have no effect if no SmartSet() calls have been made. | |
| """ | |
| self.stubs.reverse() | |
| for args in self.stubs: | |
| setattr(*args) | |
| self.stubs = [] | |
| def Set(self, parent, child_name, new_child): | |
| """Replace child_name's old definition with new_child, in the context | |
| of the given parent. The parent could be a module when the child is a | |
| function at module scope. Or the parent could be a class when a class' | |
| method is being replaced. The named child is set to new_child, while | |
| the prior definition is saved away for later, when UnsetAll() is called. | |
| This method supports the case where child_name is a staticmethod or a | |
| classmethod of parent. | |
| """ | |
| old_child = getattr(parent, child_name) | |
| old_attribute = parent.__dict__.get(child_name) | |
| if old_attribute is not None and isinstance(old_attribute, staticmethod): | |
| old_child = staticmethod(old_child) | |
| self.cache.append((parent, old_child, child_name)) | |
| setattr(parent, child_name, new_child) | |
| def UnsetAll(self): | |
| """Reverses all the Set() calls, restoring things to their original | |
| definition. Its okay to call UnsetAll() repeatedly, as later calls have | |
| no effect if no Set() calls have been made. | |
| """ | |
| # Undo calls to Set() in reverse order, in case Set() was called on the | |
| # same arguments repeatedly (want the original call to be last one undone) | |
| self.cache.reverse() | |
| for (parent, old_child, child_name) in self.cache: | |
| setattr(parent, child_name, old_child) | |
| self.cache = [] |