From f8d31d24001e0afb519af0eafeb8848d663c21e8 Mon Sep 17 00:00:00 2001 From: Christopher Dignam Date: Wed, 12 Dec 2018 15:20:24 -0500 Subject: [PATCH] Bugfix: monkeypatch.delattr handles class descriptors Correct monkeypatch.delattr to match the correct behavior of monkeypatch.setattr when changing class descriptors --- AUTHORS | 1 + changelog/4536.bugfix.rst | 1 + src/_pytest/monkeypatch.py | 8 +++++++- testing/test_monkeypatch.py | 27 +++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 changelog/4536.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 1316f7b8f..e227a7ed8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,6 +48,7 @@ Christian Boelsen Christian Theunert Christian Tismer Christopher Gilling +Christopher Dignam CrazyMerlyn Cyrus Maden Dhiren Serai diff --git a/changelog/4536.bugfix.rst b/changelog/4536.bugfix.rst new file mode 100644 index 000000000..0a88ee755 --- /dev/null +++ b/changelog/4536.bugfix.rst @@ -0,0 +1 @@ +monkeypatch.delattr handles class descriptors like staticmethod/classmethod diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 2c81de177..3ef05769b 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -181,6 +181,8 @@ class MonkeyPatch(object): attribute is missing. """ __tracebackhide__ = True + import inspect + if name is notset: if not isinstance(target, six.string_types): raise TypeError( @@ -194,7 +196,11 @@ class MonkeyPatch(object): if raising: raise AttributeError(name) else: - self._setattr.append((target, name, getattr(target, name, notset))) + oldval = getattr(target, name, notset) + # avoid class descriptors like staticmethod/classmethod + if inspect.isclass(target): + oldval = target.__dict__.get(name, notset) + self._setattr.append((target, name, oldval)) delattr(target, name) def setitem(self, dic, name, value): diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index ebc233fbf..572c31357 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -391,6 +391,33 @@ def test_issue156_undo_staticmethod(Sample): assert Sample.hello() +def test_undo_class_descriptors_delattr(): + class SampleParent(object): + @classmethod + def hello(_cls): + pass + + @staticmethod + def world(): + pass + + class SampleChild(SampleParent): + pass + + monkeypatch = MonkeyPatch() + + original_hello = SampleChild.hello + original_world = SampleChild.world + monkeypatch.delattr(SampleParent, "hello") + monkeypatch.delattr(SampleParent, "world") + assert getattr(SampleParent, "hello", None) is None + assert getattr(SampleParent, "world", None) is None + + monkeypatch.undo() + assert original_hello == SampleChild.hello + assert original_world == SampleChild.world + + def test_issue1338_name_resolving(): pytest.importorskip("requests") monkeypatch = MonkeyPatch()