diff --git a/AUTHORS b/AUTHORS index 27c0b3ac408..59042885e5e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -352,6 +352,7 @@ Nicolas Delaby Nicolas Simonds Nico Vidal Nikesh Chavhan +Nikita Zamuldinov Nikolay Kondratyev Nipunn Koorapati Oleg Pidsadnyi diff --git a/changelog/14560.bugfix.rst b/changelog/14560.bugfix.rst new file mode 100644 index 00000000000..7adcd6cf6b9 --- /dev/null +++ b/changelog/14560.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when parametrized tests used custom classes with a ``__getattr__`` that raises ``KeyError`` instead of ``AttributeError`` during test id generation. diff --git a/repro_test.py b/repro_test.py new file mode 100644 index 00000000000..00f73abdc92 --- /dev/null +++ b/repro_test.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import sys + + +sys.path.insert(0, "/root/.openclaw/workspace-fork/pytest-work/src") + +from _pytest.python import IdMaker + + +class CustomDict: + def __init__(self, data): + self._data = data + + def __getattr__(self, item): + return self._data[item] + + +val = CustomDict({"a": 1}) +result = IdMaker([], [], None, None, None, None)._idval(val, "a", 6) +assert result == "a6", f"Expected 'a6', got {result!r}" +print("Test passed!") diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ad5a2c6a59b..f09a76c2a9c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1048,9 +1048,12 @@ def _idval_from_value(self, val: object) -> str | None: pass elif isinstance(val, enum.Enum): return str(val) - elif isinstance(getattr(val, "__name__", None), str): + try: + name = getattr(val, "__name__", None) + except (AttributeError, KeyError): + name = None + if isinstance(name, str): # Name of a class, function, module, etc. - name: str = getattr(val, "__name__") return name return None diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 026589d65f5..4cf31fceff1 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -425,6 +425,24 @@ def test_function(): IdMaker([], [], None, None, None, None)._idval(val, "a", 6) == expected ) + def test_idval_custom_class_getattr_keyerror(self) -> None: + """Regression test for #14560. + + Custom classes with a ``__getattr__`` that raises ``KeyError`` + (instead of ``AttributeError``) should not crash id generation. + """ + + class CustomDict: + def __init__(self, data: dict) -> None: + self._data = data + + def __getattr__(self, item: str) -> object: + return self._data[item] + + val = CustomDict({"a": 1}) + result = IdMaker([], [], None, None, None, None)._idval(val, "a", 6) + assert result == "a6" + def test_notset_idval(self) -> None: """Test that a NOTSET value (used by an empty parameterset) generates a proper ID.