diff options
Diffstat (limited to 'mocker.py')
-rw-r--r-- | mocker.py | 92 |
1 files changed, 78 insertions, 14 deletions
@@ -1,7 +1,35 @@ """ -Copyright (c) 2007 Gustavo Niemeyer <gustavo@niemeyer.net> - -Graceful platform for test doubles in Python (mocks, stubs, fakes, and dummies). +Mocker + +Graceful platform for test doubles in Python: mocks, stubs, fakes, and dummies. + +Copyright (c) 2007-2010, Gustavo Niemeyer <gustavo@niemeyer.net> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import __builtin__ import tempfile @@ -18,12 +46,12 @@ if sys.version_info < (2, 4): from sets import Set as set # pragma: nocover -__all__ = ["Mocker", "expect", "IS", "CONTAINS", "IN", "MATCH", - "ANY", "ARGS", "KWARGS"] +__all__ = ["Mocker", "Expect", "expect", "IS", "CONTAINS", "IN", "MATCH", + "ANY", "ARGS", "KWARGS", "MockerTestCase"] __author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>" -__license__ = "PSF License" +__license__ = "BSD" __version__ = "0.10.1" @@ -56,6 +84,8 @@ class expect(object): """ + __mocker__ = None + def __init__(self, mock, attr=None): self._mock = mock self._attr = attr @@ -64,10 +94,26 @@ class expect(object): return self.__class__(self._mock, attr) def __call__(self, *args, **kwargs): - getattr(self._mock.__mocker__, self._attr)(*args, **kwargs) + mocker = self.__mocker__ + if not mocker: + mocker = self._mock.__mocker__ + getattr(mocker, self._attr)(*args, **kwargs) return self +def Expect(mocker): + """Create an expect() "function" using the given Mocker instance. + + This helper allows defining an expect() "function" which works even + in trickier cases such as: + + expect = Expect(mymocker) + expect(iter(mock)).generate([1, 2, 3]) + + """ + return type("Expect", (expect,), {"__mocker__": mocker}) + + # -------------------------------------------------------------------- # Extensions to Python's unittest. @@ -88,8 +134,6 @@ class MockerTestCase(unittest.TestCase): a few additional helper methods. """ - expect = expect - def __init__(self, methodName="runTest"): # So here is the trick: we take the real test method, wrap it on # a function that do the job we have to do, and insert it in the @@ -138,12 +182,22 @@ class MockerTestCase(unittest.TestCase): self.run = run_wrapper self.mocker = Mocker() + self.expect = Expect(self.mocker) self.__cleanup_funcs = [] self.__cleanup_paths = [] super(MockerTestCase, self).__init__(methodName) + def __call__(self, *args, **kwargs): + # This is necessary for Python 2.3 only, because it didn't use run(), + # which is supported above. + try: + super(MockerTestCase, self).__call__(*args, **kwargs) + finally: + if sys.version_info < (2, 4): + self.__cleanup() + def __cleanup(self): for path in self.__cleanup_paths: if os.path.isfile(path): @@ -307,10 +361,9 @@ class MockerTestCase(unittest.TestCase): except excClass, e: return e else: + excName = excClass if hasattr(excClass, "__name__"): excName = excClass.__name__ - else: - excName = str(excClass) raise self.failureException( "%s not raised (%r returned)" % (excName, result)) @@ -593,16 +646,19 @@ class MockerBase(object): while import_stack: module_path = ".".join(import_stack) try: - object = __import__(module_path, {}, {}, [""]) + __import__(module_path) except ImportError: attr_stack.insert(0, import_stack.pop()) if not import_stack: raise continue else: + object = sys.modules[module_path] for attr in attr_stack: object = getattr(object, attr) break + if isinstance(object, types.UnboundMethodType): + object = object.im_func if spec is True: spec = object if type is True: @@ -1075,6 +1131,10 @@ class Mock(object): if self.__mocker__.is_recording() or self.__mocker_type__ is None: return type(self) return self.__mocker_type__ + if name == "__length_hint__": + # This is used by Python 2.6+ to optimize the allocation + # of arrays in certain cases. Pretend it doesn't exist. + raise AttributeError("No __length_hint__ here!") return self.__mocker_act__("getattr", (name,)) def __setattr__(self, name, value): @@ -1114,9 +1174,12 @@ class Mock(object): def __nonzero__(self): try: - return self.__mocker_act__("nonzero") + result = self.__mocker_act__("nonzero") except MatchError, e: return True + if type(result) is Mock: + return True + return result def __iter__(self): # XXX On py3k, when next() becomes __next__(), we'll be able @@ -2042,6 +2105,7 @@ class Patcher(Task): try: return unpatched(*action.args, **action.kwargs) except AttributeError: + type, value, traceback = sys.exc_info() if action.kind == "getattr": # The normal behavior of Python is to try __getattribute__, # and if it raises AttributeError, try __getattr__. We've @@ -2053,7 +2117,7 @@ class Patcher(Task): pass else: return __getattr__(*action.args, **action.kwargs) - raise + raise type, value, traceback class PatchedMethod(object): |