diff --git a/changelog/14560.bugfix.rst b/changelog/14560.bugfix.rst new file mode 100644 index 00000000000..7072325e213 --- /dev/null +++ b/changelog/14560.bugfix.rst @@ -0,0 +1 @@ +Fixed automatic parametrization ID generation crashing when a parameter object raises an exception while accessing ``__name__``. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ad5a2c6a59b..cb82fcedc96 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1048,10 +1048,11 @@ 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): - # Name of a class, function, module, etc. - name: str = getattr(val, "__name__") - return name + else: + name: str | None = safe_getattr(val, "__name__", None) + if isinstance(name, str): + # Name of a class, function, module, etc. + return name return None def _idval_from_value_required(self, val: object, idx: int) -> str: diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 026589d65f5..ccb04bcaeaa 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -425,6 +425,18 @@ def test_function(): IdMaker([], [], None, None, None, None)._idval(val, "a", 6) == expected ) + def test_idval_ignores_broken_name_attribute(self) -> None: + """Use the fallback ID for values that reject __name__ lookup.""" + + class DictWrapper(dict[str, object]): + def __getattr__(self, name: str) -> object: + return self[name] + + assert ( + IdMaker([], [], None, None, None, None)._idval(DictWrapper(), "a", 6) + == "a6" + ) + def test_notset_idval(self) -> None: """Test that a NOTSET value (used by an empty parameterset) generates a proper ID.