1ffe3c632Sopenharmony_ci#!/usr/bin/python2.4
2ffe3c632Sopenharmony_ci#
3ffe3c632Sopenharmony_ci# Copyright 2008 Google Inc.
4ffe3c632Sopenharmony_ci#
5ffe3c632Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
6ffe3c632Sopenharmony_ci# you may not use this file except in compliance with the License.
7ffe3c632Sopenharmony_ci# You may obtain a copy of the License at
8ffe3c632Sopenharmony_ci#
9ffe3c632Sopenharmony_ci#      http://www.apache.org/licenses/LICENSE-2.0
10ffe3c632Sopenharmony_ci#
11ffe3c632Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
12ffe3c632Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
13ffe3c632Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ffe3c632Sopenharmony_ci# See the License for the specific language governing permissions and
15ffe3c632Sopenharmony_ci# limitations under the License.
16ffe3c632Sopenharmony_ci
17ffe3c632Sopenharmony_ci# This file is used for testing.  The original is at:
18ffe3c632Sopenharmony_ci#   http://code.google.com/p/pymox/
19ffe3c632Sopenharmony_ci
20ffe3c632Sopenharmony_ciimport inspect
21ffe3c632Sopenharmony_ci
22ffe3c632Sopenharmony_ci
23ffe3c632Sopenharmony_ciclass StubOutForTesting:
24ffe3c632Sopenharmony_ci  """Sample Usage:
25ffe3c632Sopenharmony_ci     You want os.path.exists() to always return true during testing.
26ffe3c632Sopenharmony_ci
27ffe3c632Sopenharmony_ci     stubs = StubOutForTesting()
28ffe3c632Sopenharmony_ci     stubs.Set(os.path, 'exists', lambda x: 1)
29ffe3c632Sopenharmony_ci       ...
30ffe3c632Sopenharmony_ci     stubs.UnsetAll()
31ffe3c632Sopenharmony_ci
32ffe3c632Sopenharmony_ci     The above changes os.path.exists into a lambda that returns 1.  Once
33ffe3c632Sopenharmony_ci     the ... part of the code finishes, the UnsetAll() looks up the old value
34ffe3c632Sopenharmony_ci     of os.path.exists and restores it.
35ffe3c632Sopenharmony_ci
36ffe3c632Sopenharmony_ci  """
37ffe3c632Sopenharmony_ci  def __init__(self):
38ffe3c632Sopenharmony_ci    self.cache = []
39ffe3c632Sopenharmony_ci    self.stubs = []
40ffe3c632Sopenharmony_ci
41ffe3c632Sopenharmony_ci  def __del__(self):
42ffe3c632Sopenharmony_ci    self.SmartUnsetAll()
43ffe3c632Sopenharmony_ci    self.UnsetAll()
44ffe3c632Sopenharmony_ci
45ffe3c632Sopenharmony_ci  def SmartSet(self, obj, attr_name, new_attr):
46ffe3c632Sopenharmony_ci    """Replace obj.attr_name with new_attr. This method is smart and works
47ffe3c632Sopenharmony_ci       at the module, class, and instance level while preserving proper
48ffe3c632Sopenharmony_ci       inheritance. It will not stub out C types however unless that has been
49ffe3c632Sopenharmony_ci       explicitly allowed by the type.
50ffe3c632Sopenharmony_ci
51ffe3c632Sopenharmony_ci       This method supports the case where attr_name is a staticmethod or a
52ffe3c632Sopenharmony_ci       classmethod of obj.
53ffe3c632Sopenharmony_ci
54ffe3c632Sopenharmony_ci       Notes:
55ffe3c632Sopenharmony_ci      - If obj is an instance, then it is its class that will actually be
56ffe3c632Sopenharmony_ci        stubbed. Note that the method Set() does not do that: if obj is
57ffe3c632Sopenharmony_ci        an instance, it (and not its class) will be stubbed.
58ffe3c632Sopenharmony_ci      - The stubbing is using the builtin getattr and setattr. So, the __get__
59ffe3c632Sopenharmony_ci        and __set__ will be called when stubbing (TODO: A better idea would
60ffe3c632Sopenharmony_ci        probably be to manipulate obj.__dict__ instead of getattr() and
61ffe3c632Sopenharmony_ci        setattr()).
62ffe3c632Sopenharmony_ci
63ffe3c632Sopenharmony_ci       Raises AttributeError if the attribute cannot be found.
64ffe3c632Sopenharmony_ci    """
65ffe3c632Sopenharmony_ci    if (inspect.ismodule(obj) or
66ffe3c632Sopenharmony_ci        (not inspect.isclass(obj) and obj.__dict__.has_key(attr_name))):
67ffe3c632Sopenharmony_ci      orig_obj = obj
68ffe3c632Sopenharmony_ci      orig_attr = getattr(obj, attr_name)
69ffe3c632Sopenharmony_ci
70ffe3c632Sopenharmony_ci    else:
71ffe3c632Sopenharmony_ci      if not inspect.isclass(obj):
72ffe3c632Sopenharmony_ci        mro = list(inspect.getmro(obj.__class__))
73ffe3c632Sopenharmony_ci      else:
74ffe3c632Sopenharmony_ci        mro = list(inspect.getmro(obj))
75ffe3c632Sopenharmony_ci
76ffe3c632Sopenharmony_ci      mro.reverse()
77ffe3c632Sopenharmony_ci
78ffe3c632Sopenharmony_ci      orig_attr = None
79ffe3c632Sopenharmony_ci
80ffe3c632Sopenharmony_ci      for cls in mro:
81ffe3c632Sopenharmony_ci        try:
82ffe3c632Sopenharmony_ci          orig_obj = cls
83ffe3c632Sopenharmony_ci          orig_attr = getattr(obj, attr_name)
84ffe3c632Sopenharmony_ci        except AttributeError:
85ffe3c632Sopenharmony_ci          continue
86ffe3c632Sopenharmony_ci
87ffe3c632Sopenharmony_ci    if orig_attr is None:
88ffe3c632Sopenharmony_ci      raise AttributeError("Attribute not found.")
89ffe3c632Sopenharmony_ci
90ffe3c632Sopenharmony_ci    # Calling getattr() on a staticmethod transforms it to a 'normal' function.
91ffe3c632Sopenharmony_ci    # We need to ensure that we put it back as a staticmethod.
92ffe3c632Sopenharmony_ci    old_attribute = obj.__dict__.get(attr_name)
93ffe3c632Sopenharmony_ci    if old_attribute is not None and isinstance(old_attribute, staticmethod):
94ffe3c632Sopenharmony_ci      orig_attr = staticmethod(orig_attr)
95ffe3c632Sopenharmony_ci
96ffe3c632Sopenharmony_ci    self.stubs.append((orig_obj, attr_name, orig_attr))
97ffe3c632Sopenharmony_ci    setattr(orig_obj, attr_name, new_attr)
98ffe3c632Sopenharmony_ci
99ffe3c632Sopenharmony_ci  def SmartUnsetAll(self):
100ffe3c632Sopenharmony_ci    """Reverses all the SmartSet() calls, restoring things to their original
101ffe3c632Sopenharmony_ci    definition.  Its okay to call SmartUnsetAll() repeatedly, as later calls
102ffe3c632Sopenharmony_ci    have no effect if no SmartSet() calls have been made.
103ffe3c632Sopenharmony_ci
104ffe3c632Sopenharmony_ci    """
105ffe3c632Sopenharmony_ci    self.stubs.reverse()
106ffe3c632Sopenharmony_ci
107ffe3c632Sopenharmony_ci    for args in self.stubs:
108ffe3c632Sopenharmony_ci      setattr(*args)
109ffe3c632Sopenharmony_ci
110ffe3c632Sopenharmony_ci    self.stubs = []
111ffe3c632Sopenharmony_ci
112ffe3c632Sopenharmony_ci  def Set(self, parent, child_name, new_child):
113ffe3c632Sopenharmony_ci    """Replace child_name's old definition with new_child, in the context
114ffe3c632Sopenharmony_ci    of the given parent.  The parent could be a module when the child is a
115ffe3c632Sopenharmony_ci    function at module scope.  Or the parent could be a class when a class'
116ffe3c632Sopenharmony_ci    method is being replaced.  The named child is set to new_child, while
117ffe3c632Sopenharmony_ci    the prior definition is saved away for later, when UnsetAll() is called.
118ffe3c632Sopenharmony_ci
119ffe3c632Sopenharmony_ci    This method supports the case where child_name is a staticmethod or a
120ffe3c632Sopenharmony_ci    classmethod of parent.
121ffe3c632Sopenharmony_ci    """
122ffe3c632Sopenharmony_ci    old_child = getattr(parent, child_name)
123ffe3c632Sopenharmony_ci
124ffe3c632Sopenharmony_ci    old_attribute = parent.__dict__.get(child_name)
125ffe3c632Sopenharmony_ci    if old_attribute is not None and isinstance(old_attribute, staticmethod):
126ffe3c632Sopenharmony_ci      old_child = staticmethod(old_child)
127ffe3c632Sopenharmony_ci
128ffe3c632Sopenharmony_ci    self.cache.append((parent, old_child, child_name))
129ffe3c632Sopenharmony_ci    setattr(parent, child_name, new_child)
130ffe3c632Sopenharmony_ci
131ffe3c632Sopenharmony_ci  def UnsetAll(self):
132ffe3c632Sopenharmony_ci    """Reverses all the Set() calls, restoring things to their original
133ffe3c632Sopenharmony_ci    definition.  Its okay to call UnsetAll() repeatedly, as later calls have
134ffe3c632Sopenharmony_ci    no effect if no Set() calls have been made.
135ffe3c632Sopenharmony_ci
136ffe3c632Sopenharmony_ci    """
137ffe3c632Sopenharmony_ci    # Undo calls to Set() in reverse order, in case Set() was called on the
138ffe3c632Sopenharmony_ci    # same arguments repeatedly (want the original call to be last one undone)
139ffe3c632Sopenharmony_ci    self.cache.reverse()
140ffe3c632Sopenharmony_ci
141ffe3c632Sopenharmony_ci    for (parent, old_child, child_name) in self.cache:
142ffe3c632Sopenharmony_ci      setattr(parent, child_name, old_child)
143ffe3c632Sopenharmony_ci    self.cache = []
144