diff options
author | Gustavo Niemeyer <gustavo@niemeyer.net> | 2007-12-11 21:37:53 -0200 |
---|---|---|
committer | Gustavo Niemeyer <gustavo@niemeyer.net> | 2007-12-11 21:37:53 -0200 |
commit | d7c9c348405704cb69e9658df28d6008c5b2df7e (patch) | |
tree | b7f7a6028a07f9347657ca800c87261be87787fb | |
parent | 44bfc93ffda5ece9eeb4e0c725aed23ccd9ed8e3 (diff) | |
download | mocker-d7c9c348405704cb69e9658df28d6008c5b2df7e.tar.gz |
Fixed patching of objects which define __getattr__.0.10.1
-rw-r--r-- | NEWS | 8 | ||||
-rw-r--r-- | mocker.py | 28 | ||||
-rwxr-xr-x | test.py | 50 |
3 files changed, 81 insertions, 5 deletions
@@ -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! @@ -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: @@ -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): |