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