summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGustavo Niemeyer <gustavo@niemeyer.net>2007-12-11 21:37:53 -0200
committerGustavo Niemeyer <gustavo@niemeyer.net>2007-12-11 21:37:53 -0200
commitd7c9c348405704cb69e9658df28d6008c5b2df7e (patch)
treeb7f7a6028a07f9347657ca800c87261be87787fb
parent44bfc93ffda5ece9eeb4e0c725aed23ccd9ed8e3 (diff)
downloadmocker-d7c9c348405704cb69e9658df28d6008c5b2df7e.tar.gz
Fixed patching of objects which define __getattr__.0.10.1
-rw-r--r--NEWS8
-rw-r--r--mocker.py28
-rwxr-xr-xtest.py50
3 files changed, 81 insertions, 5 deletions
diff --git a/NEWS b/NEWS
index c0e5d02..5693fb3 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,11 @@
+0.10.1 (2007-12-11)
+===================
+
+- Fixed patching of objects which define __getattr__.
+
+
0.10 (2007-12-09)
-==================
+=================
- Greatly improved error messages and logic for expression ordering!
diff --git a/mocker.py b/mocker.py
index 6de88e9..1e92f72 100644
--- a/mocker.py
+++ b/mocker.py
@@ -24,7 +24,7 @@ __all__ = ["Mocker", "expect", "IS", "CONTAINS", "IN", "MATCH",
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
-__version__ = "0.10"
+__version__ = "0.10.1"
ERROR_PREFIX = "[Mocker] "
@@ -1693,7 +1693,7 @@ class RunCounter(Task):
def verify(self):
if not self.min <= self._runs <= self.max:
if self._runs < self.min:
- raise AssertionError("Performed less times than expected.")
+ raise AssertionError("Performed fewer times than expected.")
raise AssertionError("Performed more times than expected.")
@@ -2016,7 +2016,21 @@ class Patcher(Task):
def execute(self, action, object):
attr = self._get_kind_attr(action.kind)
unpatched = self.get_unpatched_attr(object, attr)
- return unpatched(*action.args, **action.kwargs)
+ try:
+ return unpatched(*action.args, **action.kwargs)
+ except AttributeError:
+ if action.kind == "getattr":
+ # The normal behavior of Python is to try __getattribute__,
+ # and if it raises AttributeError, try __getattr__. We've
+ # tried the unpatched __getattribute__ above, and we'll now
+ # try __getattr__.
+ try:
+ __getattr__ = unpatched("__getattr__")
+ except AttributeError:
+ pass
+ else:
+ return __getattr__(*action.args, **action.kwargs)
+ raise
class PatchedMethod(object):
@@ -2037,6 +2051,14 @@ class PatchedMethod(object):
return mock.__mocker_act__(self._kind, args, kwargs, object)
return method
+ def __call__(self, obj, *args, **kwargs):
+ # At least with __getattribute__, Python seems to use *both* the
+ # descriptor API and also call the class attribute directly. It
+ # looks like an interpreter bug, or at least an undocumented
+ # inconsistency.
+ return self.__get__(obj)(*args, **kwargs)
+
+
def patcher_recorder(mocker, event):
mock = event.path.root_mock
if mock.__mocker_patcher__ and len(event.path.actions) == 1:
diff --git a/test.py b/test.py
index 77454e9..173e51e 100755
--- a/test.py
+++ b/test.py
@@ -3825,7 +3825,7 @@ class PatcherTest(TestCase):
self.assertEquals(obj8.attr, 8)
self.assertEquals(obj9.attr, "originalD")
- def test_execute_getattr(self):
+ def test_patched_getattr_execute_getattr(self):
class C(object):
def __getattribute__(self, attr):
if attr == "attr":
@@ -3843,6 +3843,54 @@ class PatcherTest(TestCase):
self.patcher.replay()
self.assertRaises(AttributeError, self.patcher.execute, action, obj)
+ def test_patched_real_getattr_on_different_instances(self):
+ def build_getattr(original):
+ def __getattr__(self, name):
+ if name == "attr":
+ return original
+ return object.__getattr__(self, name)
+ return __getattr__
+ self.C.__getattr__ = build_getattr("originalC")
+ self.D.__getattr__ = build_getattr("originalD")
+
+ class MockStub(object):
+ def __init__(self, id):
+ self.id = id
+ def __mocker_act__(self, kind, args=(), kwargs={}, object=None):
+ return self.id
+
+ obj1, obj2, obj3, obj4, obj5, obj6 = [self.C() for i in range(6)]
+ obj7, obj8, obj9 = [self.D() for i in range(3)]
+
+ obj2.__mocker_mock__ = MockStub(2)
+ self.patcher.monitor(obj2, "getattr")
+ obj5.__mocker_mock__ = MockStub(5)
+ self.patcher.monitor(obj5, "getattr")
+ obj8.__mocker_mock__ = MockStub(8)
+ self.patcher.monitor(obj8, "getattr")
+
+ self.patcher.replay()
+ self.assertEquals(obj1.attr, "originalC")
+ self.assertEquals(obj2.attr, 2)
+ self.assertEquals(obj3.attr, "originalC")
+ self.assertEquals(obj4.attr, "originalC")
+ self.assertEquals(obj5.attr, 5)
+ self.assertEquals(obj6.attr, "originalC")
+ self.assertEquals(obj7.attr, "originalD")
+ self.assertEquals(obj8.attr, 8)
+ self.assertEquals(obj9.attr, "originalD")
+
+ def test_patched_real_getattr_execute_getattr(self):
+ class C(object):
+ def __getattr__(self, attr):
+ if attr == "attr":
+ return "original"
+ action = Action("getattr", ("attr",), {})
+ obj = C()
+ self.patcher.monitor(obj, "getattr")
+ self.patcher.replay()
+ self.assertEquals(self.patcher.execute(action, obj), "original")
+
def test_execute_call(self):
class C(object):
def __call__(self, *args, **kwargs):