You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
4.8 KiB
140 lines
4.8 KiB
#!/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/ |
|
|
|
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 = []
|
|
|