From 47c0fc3d78a6db8bbe6c298dec76084142f59171 Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Thu, 10 Aug 2023 16:36:22 -0700 Subject: [PATCH 1/9] tests --- testing/test_assertrewrite.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index e1b71ded6..d85c8bed6 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -685,6 +685,21 @@ class TestAssertionRewrite: assert msg is not None assert " < 0" in msg + def test_assert_handling_raise_in__iter__(self) -> None: + def f() -> None: + class A: + def __iter__(self): + raise TypeError("user message") + + def __eq__(self, o: object) -> bool: + return self is o + + assert A() == A() + + msg = getmsg(f) + assert msg is not None + assert "Unexpected exception" in msg + def test_formatchar(self) -> None: def f() -> None: assert "%test" == "test" # type: ignore[comparison-overlap] From e938580257e8120dabbea789d5b7a3dd3354d0a3 Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:37:48 -0700 Subject: [PATCH 2/9] check for user-generated exceptions --- src/_pytest/assertion/util.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index fc5dfdbd5..47244b688 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -132,8 +132,14 @@ def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) - except TypeError: - return False + except Exception as e: + if ( + isinstance(e, TypeError) + and (len(e.args) == 1) + and "iter() returned non-iterator of type" in str(e.args[0]) + ): + return False + raise ValueError(f"Unexpected exception {e!r} while testing object {obj!r}") def has_default_eq( @@ -195,13 +201,20 @@ def assertrepr_compare( explanation = _notin_text(left, right, verbose) except outcomes.Exit: raise - except Exception: - explanation = [ - "(pytest_assertion plugin: representation of details failed: {}.".format( - _pytest._code.ExceptionInfo.from_current()._getreprcrash() - ), - " Probably an object has a faulty __repr__.)", - ] + except Exception as e: + if ( + isinstance(e, ValueError) + and (len(e.args) == 1) + and ("Unexpected exception" in str(e.args[0])) + ): + explanation = [e.args[0]] + else: + explanation = [ + "(pytest_assertion plugin: representation of details failed: {}.".format( + _pytest._code.ExceptionInfo.from_current()._getreprcrash() + ), + " Probably an object has a faulty __repr__.)", + ] if not explanation: return None From b9cb87d862bb3f841d67723ac5717ce90b219dc2 Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:43:01 -0700 Subject: [PATCH 3/9] simplify code / take out user-gen typeerror case --- src/_pytest/assertion/util.py | 8 ++------ testing/test_assertrewrite.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 47244b688..22eec1809 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -132,13 +132,9 @@ def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) + except TypeError: + return False except Exception as e: - if ( - isinstance(e, TypeError) - and (len(e.args) == 1) - and "iter() returned non-iterator of type" in str(e.args[0]) - ): - return False raise ValueError(f"Unexpected exception {e!r} while testing object {obj!r}") diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index d85c8bed6..b4e3e5d53 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -689,7 +689,7 @@ class TestAssertionRewrite: def f() -> None: class A: def __iter__(self): - raise TypeError("user message") + raise ValueError() def __eq__(self, o: object) -> bool: return self is o From d2dc8a70b593b61a013ae3eba99bb3f10e82eb65 Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:48:53 -0700 Subject: [PATCH 4/9] changelog --- changelog/7966.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/7966.bugfix.rst diff --git a/changelog/7966.bugfix.rst b/changelog/7966.bugfix.rst new file mode 100644 index 000000000..40cad46c6 --- /dev/null +++ b/changelog/7966.bugfix.rst @@ -0,0 +1 @@ +Assertion rewrite mechanism now gives a seperate, more detailed error message from failures within __iter__. From ec1053cc162250e42ae220ebe91c3bf00b22b084 Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Fri, 11 Aug 2023 10:03:19 -0700 Subject: [PATCH 5/9] error msg --- src/_pytest/assertion/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 22eec1809..613a40b97 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -135,7 +135,9 @@ def isiterable(obj: Any) -> bool: except TypeError: return False except Exception as e: - raise ValueError(f"Unexpected exception {e!r} while testing object {obj!r}") + raise ValueError( + f"Unexpected exception {e!r} while testing object {obj!r}. Probably an issue with __iter__" + ) def has_default_eq( From c0cf822ca1d5d6ac74f46ddccbe377c254c6084a Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Fri, 11 Aug 2023 10:03:19 -0700 Subject: [PATCH 6/9] improve error msg and test --- src/_pytest/assertion/util.py | 9 ++++++--- testing/test_assertrewrite.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 613a40b97..d20c2363e 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -136,7 +136,10 @@ def isiterable(obj: Any) -> bool: return False except Exception as e: raise ValueError( - f"Unexpected exception {e!r} while testing object {obj!r}. Probably an issue with __iter__" + [ + f"pytest_assertion plugin: unexpected exception {e!r} while testing object {obj!r}", + ", probably from __iter__", + ] ) @@ -203,9 +206,9 @@ def assertrepr_compare( if ( isinstance(e, ValueError) and (len(e.args) == 1) - and ("Unexpected exception" in str(e.args[0])) + and ("__iter__" in str(e.args[0])) ): - explanation = [e.args[0]] + explanation = e.args[0] else: explanation = [ "(pytest_assertion plugin: representation of details failed: {}.".format( diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index b4e3e5d53..62c6bb8d4 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -698,7 +698,7 @@ class TestAssertionRewrite: msg = getmsg(f) assert msg is not None - assert "Unexpected exception" in msg + assert "__iter__" in msg and "__repr__" not in msg def test_formatchar(self) -> None: def f() -> None: From 049eec8474d3238b056a42fa07558c4293a97f05 Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Sun, 20 Aug 2023 14:04:20 -0700 Subject: [PATCH 7/9] Revert iter raises checks This reverts commit e938580257e8120dabbea789d5b7a3dd3354d0a3. Revert "improve error msg and test" This reverts commit c0cf822ca1d5d6ac74f46ddccbe377c254c6084a. Revert "error msg" This reverts commit ec1053cc162250e42ae220ebe91c3bf00b22b084. Revert "changelog" This reverts commit d2dc8a70b593b61a013ae3eba99bb3f10e82eb65. Revert "simplify code / take out user-gen typeerror case" This reverts commit b9cb87d862bb3f841d67723ac5717ce90b219dc2. --- changelog/7966.bugfix.rst | 1 - src/_pytest/assertion/util.py | 28 +++++++--------------------- testing/test_assertrewrite.py | 4 ++-- 3 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 changelog/7966.bugfix.rst diff --git a/changelog/7966.bugfix.rst b/changelog/7966.bugfix.rst deleted file mode 100644 index 40cad46c6..000000000 --- a/changelog/7966.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Assertion rewrite mechanism now gives a seperate, more detailed error message from failures within __iter__. diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index d20c2363e..fc5dfdbd5 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -134,13 +134,6 @@ def isiterable(obj: Any) -> bool: return not istext(obj) except TypeError: return False - except Exception as e: - raise ValueError( - [ - f"pytest_assertion plugin: unexpected exception {e!r} while testing object {obj!r}", - ", probably from __iter__", - ] - ) def has_default_eq( @@ -202,20 +195,13 @@ def assertrepr_compare( explanation = _notin_text(left, right, verbose) except outcomes.Exit: raise - except Exception as e: - if ( - isinstance(e, ValueError) - and (len(e.args) == 1) - and ("__iter__" in str(e.args[0])) - ): - explanation = e.args[0] - else: - explanation = [ - "(pytest_assertion plugin: representation of details failed: {}.".format( - _pytest._code.ExceptionInfo.from_current()._getreprcrash() - ), - " Probably an object has a faulty __repr__.)", - ] + except Exception: + explanation = [ + "(pytest_assertion plugin: representation of details failed: {}.".format( + _pytest._code.ExceptionInfo.from_current()._getreprcrash() + ), + " Probably an object has a faulty __repr__.)", + ] if not explanation: return None diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 62c6bb8d4..d85c8bed6 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -689,7 +689,7 @@ class TestAssertionRewrite: def f() -> None: class A: def __iter__(self): - raise ValueError() + raise TypeError("user message") def __eq__(self, o: object) -> bool: return self is o @@ -698,7 +698,7 @@ class TestAssertionRewrite: msg = getmsg(f) assert msg is not None - assert "__iter__" in msg and "__repr__" not in msg + assert "Unexpected exception" in msg def test_formatchar(self) -> None: def f() -> None: From 61133ba83d0f24c40aa6b0c1473919c7aff3b16f Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Sun, 20 Aug 2023 14:46:09 -0700 Subject: [PATCH 8/9] un-iterable fix --- changelog/7966.bugfix.rst | 1 + src/_pytest/assertion/util.py | 2 +- testing/test_assertrewrite.py | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog/7966.bugfix.rst diff --git a/changelog/7966.bugfix.rst b/changelog/7966.bugfix.rst new file mode 100644 index 000000000..de0557680 --- /dev/null +++ b/changelog/7966.bugfix.rst @@ -0,0 +1 @@ +Removes unhelpful error message from assertion rewrite mechanism when exceptions raised in __iter__ methods, and instead treats them as un-iterable. diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index fc5dfdbd5..39ca5403e 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -132,7 +132,7 @@ def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) - except TypeError: + except Exception: return False diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index d85c8bed6..2c908133d 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -689,16 +689,19 @@ class TestAssertionRewrite: def f() -> None: class A: def __iter__(self): - raise TypeError("user message") + raise ValueError() def __eq__(self, o: object) -> bool: return self is o + def __repr__(self): + return "" + assert A() == A() msg = getmsg(f) assert msg is not None - assert "Unexpected exception" in msg + assert " == " in msg def test_formatchar(self) -> None: def f() -> None: From d1722d5c189ff7a1baccadfb514d05402104ccee Mon Sep 17 00:00:00 2001 From: Reagan Lee <96998476+reaganjlee@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:49:39 -0700 Subject: [PATCH 9/9] fix test for codecov --- testing/test_assertrewrite.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 2c908133d..08813c4dc 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -685,8 +685,9 @@ class TestAssertionRewrite: assert msg is not None assert " < 0" in msg - def test_assert_handling_raise_in__iter__(self) -> None: - def f() -> None: + def test_assert_handling_raise_in__iter__(self, pytester: Pytester) -> None: + pytester.makepyfile( + """\ class A: def __iter__(self): raise ValueError() @@ -698,10 +699,10 @@ class TestAssertionRewrite: return "" assert A() == A() - - msg = getmsg(f) - assert msg is not None - assert " == " in msg + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines(["*E*assert == "]) def test_formatchar(self) -> None: def f() -> None: