143 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
		
			4.9 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.
 | |
| 
 | |
| 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 = []
 | 
