15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/python2.4
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright 2008 Google Inc.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License");
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# you may not use this file except in compliance with the License.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# You may obtain a copy of the License at
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#      http://www.apache.org/licenses/LICENSE-2.0
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Unless required by applicable law or agreed to in writing, software
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# distributed under the License is distributed on an "AS IS" BASIS,
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# See the License for the specific language governing permissions and
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# limitations under the License.
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This file is used for testing.  The original is at:
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   http://code.google.com/p/pymox/
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class StubOutForTesting:
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Sample Usage:
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     You want os.path.exists() to always return true during testing.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     stubs = StubOutForTesting()
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     stubs.Set(os.path, 'exists', lambda x: 1)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       ...
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     stubs.UnsetAll()
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     The above changes os.path.exists into a lambda that returns 1.  Once
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     the ... part of the code finishes, the UnsetAll() looks up the old value
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     of os.path.exists and restores it.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cache = []
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stubs = []
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __del__(self):
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.SmartUnsetAll()
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.UnsetAll()
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SmartSet(self, obj, attr_name, new_attr):
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Replace obj.attr_name with new_attr. This method is smart and works
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       at the module, class, and instance level while preserving proper
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       inheritance. It will not stub out C types however unless that has been
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       explicitly allowed by the type.
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       This method supports the case where attr_name is a staticmethod or a
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       classmethod of obj.
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       Notes:
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      - If obj is an instance, then it is its class that will actually be
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        stubbed. Note that the method Set() does not do that: if obj is
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        an instance, it (and not its class) will be stubbed.
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      - The stubbing is using the builtin getattr and setattr. So, the __get__
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        and __set__ will be called when stubbing (TODO: A better idea would
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        probably be to manipulate obj.__dict__ instead of getattr() and
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        setattr()).
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       Raises AttributeError if the attribute cannot be found.
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (inspect.ismodule(obj) or
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (not inspect.isclass(obj) and obj.__dict__.has_key(attr_name))):
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      orig_obj = obj
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      orig_attr = getattr(obj, attr_name)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not inspect.isclass(obj):
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mro = list(inspect.getmro(obj.__class__))
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mro = list(inspect.getmro(obj))
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      mro.reverse()
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      orig_attr = None
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for cls in mro:
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          orig_obj = cls
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          orig_attr = getattr(obj, attr_name)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except AttributeError:
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if orig_attr is None:
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise AttributeError("Attribute not found.")
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Calling getattr() on a staticmethod transforms it to a 'normal' function.
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # We need to ensure that we put it back as a staticmethod.
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    old_attribute = obj.__dict__.get(attr_name)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if old_attribute is not None and isinstance(old_attribute, staticmethod):
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      orig_attr = staticmethod(orig_attr)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stubs.append((orig_obj, attr_name, orig_attr))
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    setattr(orig_obj, attr_name, new_attr)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SmartUnsetAll(self):
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Reverses all the SmartSet() calls, restoring things to their original
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    definition.  Its okay to call SmartUnsetAll() repeatedly, as later calls
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    have no effect if no SmartSet() calls have been made.
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stubs.reverse()
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for args in self.stubs:
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      setattr(*args)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stubs = []
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Set(self, parent, child_name, new_child):
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Replace child_name's old definition with new_child, in the context
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    of the given parent.  The parent could be a module when the child is a
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function at module scope.  Or the parent could be a class when a class'
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    method is being replaced.  The named child is set to new_child, while
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    the prior definition is saved away for later, when UnsetAll() is called.
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This method supports the case where child_name is a staticmethod or a
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    classmethod of parent.
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    old_child = getattr(parent, child_name)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    old_attribute = parent.__dict__.get(child_name)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if old_attribute is not None and isinstance(old_attribute, staticmethod):
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      old_child = staticmethod(old_child)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cache.append((parent, old_child, child_name))
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    setattr(parent, child_name, new_child)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def UnsetAll(self):
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Reverses all the Set() calls, restoring things to their original
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    definition.  Its okay to call UnsetAll() repeatedly, as later calls have
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    no effect if no Set() calls have been made.
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Undo calls to Set() in reverse order, in case Set() was called on the
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # same arguments repeatedly (want the original call to be last one undone)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cache.reverse()
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (parent, old_child, child_name) in self.cache:
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      setattr(parent, child_name, old_child)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cache = []
141