From 407d9841428f037e0ada71f7ca028f7d1171c6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Tue, 30 Jan 2024 16:20:30 +0100 Subject: [PATCH] Fix an edge case where ExceptionInfo._stringify_exception could crash pytest.raises (#11879) Co-authored-by: Bruno Oliveira Co-authored-by: Zac Hatfield-Dodds --- AUTHORS | 1 + changelog/11879.bugfix.rst | 1 + src/_pytest/_code/code.py | 13 ++++++++++++- testing/python/raises.py | 13 +++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changelog/11879.bugfix.rst diff --git a/AUTHORS b/AUTHORS index bea582046..879097622 100644 --- a/AUTHORS +++ b/AUTHORS @@ -93,6 +93,7 @@ Christopher Dignam Christopher Gilling Claire Cecil Claudio Madotto +Clément M.T. Robert CrazyMerlyn Cristian Vera Cyrus Maden diff --git a/changelog/11879.bugfix.rst b/changelog/11879.bugfix.rst new file mode 100644 index 000000000..70b6cce72 --- /dev/null +++ b/changelog/11879.bugfix.rst @@ -0,0 +1 @@ +Fix an edge case where ``ExceptionInfo._stringify_exception`` could crash :func:`pytest.raises`. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index d99b52366..39bbc3c14 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -699,10 +699,21 @@ class ExceptionInfo(Generic[E]): return fmt.repr_excinfo(self) def _stringify_exception(self, exc: BaseException) -> str: + try: + notes = getattr(exc, "__notes__", []) + except KeyError: + # Workaround for https://github.com/python/cpython/issues/98778 on + # Python <= 3.9, and some 3.10 and 3.11 patch versions. + HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) + if sys.version_info[:2] <= (3, 11) and isinstance(exc, HTTPError): + notes = [] + else: + raise + return "\n".join( [ str(exc), - *getattr(exc, "__notes__", []), + *notes, ] ) diff --git a/testing/python/raises.py b/testing/python/raises.py index 47b22f28a..369a66a9f 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -302,3 +302,16 @@ class TestRaises: with pytest.raises(("hello", NotAnException)): # type: ignore[arg-type] pass # pragma: no cover assert "must be a BaseException type, not str" in str(excinfo.value) + + def test_issue_11872(self) -> None: + """Regression test for #11872. + + urllib.error.HTTPError on Python<=3.9 raises KeyError instead of + AttributeError on invalid attribute access. + + https://github.com/python/cpython/issues/98778 + """ + from urllib.error import HTTPError + + with pytest.raises(HTTPError, match="Not Found"): + raise HTTPError(code=404, msg="Not Found", fp=None, hdrs=None, url="") # type: ignore [arg-type]