From f7282b84bd2e3baaa18fd21f361aca792269f5a5 Mon Sep 17 00:00:00 2001 From: uweschmitt Date: Thu, 7 Aug 2014 16:13:12 +0200 Subject: [PATCH 1/7] fixed strange infinite recursion bug --- _pytest/capture.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index 0b7f615bc..f48dc19fa 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -238,7 +238,8 @@ class EncodedFile(object): self.write(data) def __getattr__(self, name): - return getattr(self.buffer, name) + if name != "buffer": + return getattr(self.buffer, name) class MultiCapture(object): From 9597d3dafe123d500bcff27f5d2866bf6797ec4d Mon Sep 17 00:00:00 2001 From: uweschmitt Date: Thu, 7 Aug 2014 16:56:45 +0200 Subject: [PATCH 2/7] better fix as replacement for last commit --- _pytest/capture.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index f48dc19fa..8bcb49377 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -238,8 +238,12 @@ class EncodedFile(object): self.write(data) def __getattr__(self, name): - if name != "buffer": - return getattr(self.buffer, name) + return getattr(self.buffer, name) + + def __setattr__(self, dd): + """default implementation for __setattr__ because unpickling causes infinite + recursion if only __getattr__ is overloaded and __setattr__ is missing""" + self.__dict__ = dd class MultiCapture(object): From c0d1f3f7ef3135df950994e9f6f86ca7dcfb7f0f Mon Sep 17 00:00:00 2001 From: uweschmitt Date: Thu, 7 Aug 2014 17:17:05 +0200 Subject: [PATCH 3/7] even better fix as replacement for last commit which was wrong --- _pytest/capture.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index 8bcb49377..f27ab1a74 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -240,9 +240,9 @@ class EncodedFile(object): def __getattr__(self, name): return getattr(self.buffer, name) - def __setattr__(self, dd): - """default implementation for __setattr__ because unpickling causes infinite - recursion if only __getattr__ is overloaded and __setattr__ is missing""" + def __setstate__(self, dd): + """default implementation for __setstate__ because unpickling causes infinite + recursion if only __getattr__ is overloaded and __setstate__ is missing""" self.__dict__ = dd From 5d024c7433939dee6cf56fe4ed829f553bde36b4 Mon Sep 17 00:00:00 2001 From: uweschmitt Date: Mon, 11 Aug 2014 12:42:36 +0200 Subject: [PATCH 4/7] hopefully final fix for strange infinite recursion bug --- _pytest/capture.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index f27ab1a74..164d5538b 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -238,12 +238,8 @@ class EncodedFile(object): self.write(data) def __getattr__(self, name): - return getattr(self.buffer, name) - - def __setstate__(self, dd): - """default implementation for __setstate__ because unpickling causes infinite - recursion if only __getattr__ is overloaded and __setstate__ is missing""" - self.__dict__ = dd + if hasattr(self, "buffer"): + return getattr(self.buffer, name) class MultiCapture(object): From 224b3a2eda73617b681005409382454f0ac12e93 Mon Sep 17 00:00:00 2001 From: uweschmitt Date: Mon, 11 Aug 2014 12:57:47 +0200 Subject: [PATCH 5/7] hopefully final fix for strange infinite recursion bug --- _pytest/capture.py | 2 ++ _pytest/main.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/_pytest/capture.py b/_pytest/capture.py index 164d5538b..1f4762b06 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -240,6 +240,8 @@ class EncodedFile(object): def __getattr__(self, name): if hasattr(self, "buffer"): return getattr(self.buffer, name) + else: + raise AttributeError("attribute buffer of %r not set" % self) class MultiCapture(object): diff --git a/_pytest/main.py b/_pytest/main.py index cbe2fabda..1be074243 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -159,6 +159,8 @@ class HookProxy: self.config = config def __getattr__(self, name): + if not hasattr(self, "config"): + raise AttributeError("attribute config of %r not set" % self) hookmethod = getattr(self.config.hook, name) def call_matching_hooks(**kwargs): From d1bde69c1e7505b1074f4fbd5591ac31e24dbeaa Mon Sep 17 00:00:00 2001 From: uweschmitt Date: Tue, 19 Aug 2014 12:57:37 +0200 Subject: [PATCH 6/7] added smoke test for bug fixed in 3716:dc080608b6d1 --- testing/test_capture.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/testing/test_capture.py b/testing/test_capture.py index 28199fb91..6ddbf7e6e 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,6 +1,7 @@ # note: py.io capture tests where copied from # pylib 1.4.20.dev2 (rev 13d9af95547e) from __future__ import with_statement +import cPickle import os import sys import py @@ -1022,3 +1023,12 @@ def test_error_attribute_issue555(testdir): """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + + +def test_pickling_and_unpickling_enocded_file(): + # see + # https://bitbucket.org/hpk42/pytest/pull-request/194/fixed-strange-infinite-recursion-bug/diff + ef = capture.EncodedFile(None, None) + ef_as_str = cPickle.dumps(ef) + # this raises infinite recursion if EncodedFile.__getattr__ is not implemented properly: + cPickle.loads(ef_as_str) From 7d9d502a0137a5f1de3405ce1c631a82ae614a2c Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 5 Sep 2014 23:55:14 +0100 Subject: [PATCH 7/7] Use py3k compatible .__getattr__() code From the python-dev thread it seemed like using object.__getattribute__(self, 'name') is the cleanest way of implementing a class wich uses .__getattr__() and should be pickelable. That only works on new-style classes so this also turns HookProxy into a new-style class on py2. This also re-writes the test to not use cPickle so it runs on py3k. --- _pytest/assertion/oldinterpret.py | 2 +- _pytest/capture.py | 5 +---- _pytest/main.py | 7 +++---- testing/test_capture.py | 12 ++++++------ 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/_pytest/assertion/oldinterpret.py b/_pytest/assertion/oldinterpret.py index 40c6d3936..8ca1d8f5c 100644 --- a/_pytest/assertion/oldinterpret.py +++ b/_pytest/assertion/oldinterpret.py @@ -57,7 +57,7 @@ class View(object): def __getattr__(self, attr): # attributes not found in the normal hierarchy rooted on View # are looked up in the object's real class - return getattr(self.__obj__, attr) + return getattr(object.__getattribute__(self, '__obj__'), attr) def __viewkey__(self): return self.__obj__.__class__ diff --git a/_pytest/capture.py b/_pytest/capture.py index 1f4762b06..c22c34bad 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -238,10 +238,7 @@ class EncodedFile(object): self.write(data) def __getattr__(self, name): - if hasattr(self, "buffer"): - return getattr(self.buffer, name) - else: - raise AttributeError("attribute buffer of %r not set" % self) + return getattr(object.__getattribute__(self, "buffer"), name) class MultiCapture(object): diff --git a/_pytest/main.py b/_pytest/main.py index 1be074243..2d46c56e8 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -153,15 +153,14 @@ def pytest_ignore_collect(path, config): ignore_paths.extend([py.path.local(x) for x in excludeopt]) return path in ignore_paths -class HookProxy: +class HookProxy(object): def __init__(self, fspath, config): self.fspath = fspath self.config = config def __getattr__(self, name): - if not hasattr(self, "config"): - raise AttributeError("attribute config of %r not set" % self) - hookmethod = getattr(self.config.hook, name) + config = object.__getattribute__(self, "config") + hookmethod = getattr(config.hook, name) def call_matching_hooks(**kwargs): plugins = self.config._getmatchingplugins(self.fspath) diff --git a/testing/test_capture.py b/testing/test_capture.py index 6ddbf7e6e..0175ec285 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,7 +1,7 @@ # note: py.io capture tests where copied from # pylib 1.4.20.dev2 (rev 13d9af95547e) from __future__ import with_statement -import cPickle +import pickle import os import sys import py @@ -1026,9 +1026,9 @@ def test_error_attribute_issue555(testdir): def test_pickling_and_unpickling_enocded_file(): - # see - # https://bitbucket.org/hpk42/pytest/pull-request/194/fixed-strange-infinite-recursion-bug/diff + # See https://bitbucket.org/hpk42/pytest/pull-request/194 + # pickle.loads() raises infinite recursion if + # EncodedFile.__getattr__ is not implemented properly ef = capture.EncodedFile(None, None) - ef_as_str = cPickle.dumps(ef) - # this raises infinite recursion if EncodedFile.__getattr__ is not implemented properly: - cPickle.loads(ef_as_str) + ef_as_str = pickle.dumps(ef) + pickle.loads(ef_as_str)