141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
|
#!/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 = []
|