Index: tests/pymox/stubout.py |
=================================================================== |
--- tests/pymox/stubout.py (revision 0) |
+++ tests/pymox/stubout.py (revision 0) |
@@ -0,0 +1,142 @@ |
+#!/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. |
+ |
+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: |
+ if isinstance(old_attribute, staticmethod): |
+ old_child = staticmethod(old_child) |
+ elif isinstance(old_attribute, classmethod): |
+ old_child = classmethod(old_child.im_func) |
+ |
+ 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 = [] |
Property changes on: tests\pymox\stubout.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |