Test for MonkeyPatch.setattr/delattr polluting vars(target)

This commit is contained in:
Ganden Schaffner 2023-01-08 00:00:00 -08:00
parent 3ad4344656
commit b1b4469ae6
No known key found for this signature in database
GPG Key ID: AAF2420F20D8B553
1 changed files with 57 additions and 7 deletions

View File

@ -5,12 +5,30 @@ import textwrap
from pathlib import Path
from typing import Dict
from typing import Generator
from typing import Optional
from typing import overload
from typing import Type
from typing import TypeVar
import pytest
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Pytester
T = TypeVar("T")
class SomeDescriptor:
@overload
def __get__(self, instance: None, owner: type) -> int:
...
@overload
def __get__(self, instance: T, owner: Optional[Type[T]] = ...) -> int:
...
def __get__(self, instance: Optional[T], owner: Optional[Type[T]] = None) -> int:
return 1
@pytest.fixture
def mp() -> Generator[MonkeyPatch, None, None]:
@ -22,15 +40,21 @@ def mp() -> Generator[MonkeyPatch, None, None]:
def test_setattr() -> None:
import inspect
class A:
x = 1
y = SomeDescriptor()
monkeypatch = MonkeyPatch()
pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2)
monkeypatch.setattr(A, "y", 2, raising=False)
assert A.y == 2 # type: ignore
monkeypatch.setattr(A, "w", 2, raising=False)
assert A.w == 2 # type: ignore
monkeypatch.undo()
assert not hasattr(A, "y")
assert not hasattr(A, "w")
with pytest.raises(TypeError):
monkeypatch.setattr(A, "w") # type: ignore[call-overload]
monkeypatch = MonkeyPatch()
monkeypatch.setattr(A, "x", 2)
@ -44,8 +68,23 @@ def test_setattr() -> None:
monkeypatch.undo() # double-undo makes no modification
assert A.x == 5
with pytest.raises(TypeError):
monkeypatch.setattr(A, "y") # type: ignore[call-overload]
# Test that inherited attributes don't get written into the target's instance
# dictionary.
a = A()
monkeypatch = MonkeyPatch()
assert "x" not in vars(a)
monkeypatch.setattr(a, "x", 2)
monkeypatch.undo()
assert "x" not in vars(a)
# Test that class/instance descriptors don't get bound and written into the target's
# dictionary.
for obj in (A, A()):
monkeypatch = MonkeyPatch()
assert isinstance(inspect.getattr_static(obj, "y"), SomeDescriptor)
monkeypatch.setattr(obj, "y", 2)
monkeypatch.undo()
assert isinstance(inspect.getattr_static(obj, "y"), SomeDescriptor)
class TestSetattrWithImportPath:
@ -96,8 +135,11 @@ class TestSetattrWithImportPath:
def test_delattr() -> None:
import inspect
class A:
x = 1
y = SomeDescriptor()
monkeypatch = MonkeyPatch()
monkeypatch.delattr(A, "x")
@ -106,14 +148,22 @@ def test_delattr() -> None:
assert A.x == 1
monkeypatch = MonkeyPatch()
pytest.raises(AttributeError, monkeypatch.delattr, A, "w")
monkeypatch.delattr(A, "w", raising=False)
monkeypatch.delattr(A, "x")
pytest.raises(AttributeError, monkeypatch.delattr, A, "y")
monkeypatch.delattr(A, "y", raising=False)
monkeypatch.setattr(A, "x", 5, raising=False)
assert A.x == 5
monkeypatch.undo()
assert A.x == 1
# Test that (non-inherited) class descriptors don't get bound and written into the
# target's dictionary.
monkeypatch = MonkeyPatch()
assert isinstance(inspect.getattr_static(A, "y"), SomeDescriptor)
monkeypatch.delattr(A, "y")
monkeypatch.undo()
assert isinstance(inspect.getattr_static(A, "y"), SomeDescriptor)
def test_setitem() -> None:
d = {"x": 1}