From 054b335366b2e939f1d619473adbcd76ffeca639 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sun, 8 Jan 2023 00:00:00 -0800 Subject: [PATCH] Fix MonkeyPatch.setattr polluting vars(target) --- changelog/10644.bugfix.rst | 3 +++ src/_pytest/monkeypatch.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 changelog/10644.bugfix.rst diff --git a/changelog/10644.bugfix.rst b/changelog/10644.bugfix.rst new file mode 100644 index 000000000..15b4019d3 --- /dev/null +++ b/changelog/10644.bugfix.rst @@ -0,0 +1,3 @@ +`monkeypatch.setattr` no longer leaves a leftover item in the target's dictionary after cleanup when patching an inherited attribute of a non-class object. This fixes a bug where a `__get__` descriptor's dynamic lookup was blocked and replaced by a cached static lookup after `monkeypatch` teardown. + +Previously `monkeypatch.setattr` avoided leaving leftover items in the target's dictionary when patching class objects but not when patching non-class objects. diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index c6e29ac76..fcf8f6fdb 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -223,7 +223,6 @@ class MonkeyPatch: applies to ``monkeypatch.setattr`` as well. """ __tracebackhide__ = True - import inspect if isinstance(value, Notset): if not isinstance(target, str): @@ -246,9 +245,10 @@ class MonkeyPatch: if raising and oldval is notset: raise AttributeError(f"{target!r} has no attribute {name!r}") - # avoid class descriptors like staticmethod/classmethod - if inspect.isclass(target): - oldval = target.__dict__.get(name, notset) + # Prevent `undo` from polluting `vars(target)` with an object that was not in it + # before monkeypatching, such as inherited attributes or the results of + # descriptor binding. + oldval = vars(target).get(name, notset) self._setattr.append((target, name, oldval)) setattr(target, name, value)