diff --git a/AUTHORS b/AUTHORS index 6885ec6e793..523b8f6d657 100644 --- a/AUTHORS +++ b/AUTHORS @@ -209,6 +209,7 @@ Israël Hallé Itxaso Aizpurua Iwan Briquemont Jaap Broekhuizen +JaeHyuck Sa Jake VanderPlas Jakob van Santen Jakub Mitoraj diff --git a/changelog/11225.improvement.rst b/changelog/11225.improvement.rst new file mode 100644 index 00000000000..687dea6f9dc --- /dev/null +++ b/changelog/11225.improvement.rst @@ -0,0 +1 @@ +:func:`pytest.warns` showed "Regex pattern did not match" instead of "DID NOT WARN" when warnings were emitted but the ``match`` pattern did not match. diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index 38e18b042d8..b4605ae46c4 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -363,7 +363,9 @@ Some examples: ... Traceback (most recent call last): ... - Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + Failed: Regex pattern did not match. + Regex: ... + Emitted warnings: ...UserWarning... >>> with warns(UserWarning, match=re.escape("issue with foo() func")): ... warnings.warn("issue with foo() func") diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index e3db717bfe4..da263acb68f 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -139,7 +139,9 @@ def warns( ... warnings.warn("this is not here", UserWarning) Traceback (most recent call last): ... - Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + Failed: Regex pattern did not match. + Regex: ... + Emitted warnings: ...UserWarning... **Using with** ``pytest.mark.parametrize`` @@ -321,10 +323,17 @@ def found_str() -> str: f" Emitted warnings: {found_str()}." ) elif not any(self.matches(w) for w in self): + escape_hint = "" + if isinstance(self.match_expr, str) and any( + self.match_expr == str(w.message) + for w in self + if issubclass(w.category, self.expected_warning) + ): + escape_hint = "\n Did you mean to `re.escape()` the regex?" fail( - f"DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.\n" - f" Regex: {self.match_expr}\n" - f" Emitted warnings: {found_str()}." + "Regex pattern did not match.\n" + f" Regex: {self.match_expr!r}\n" + f" Emitted warnings: {found_str()}.{escape_hint}" ) finally: # Whether or not any warnings matched, we want to re-emit all unmatched warnings. diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 384f2b66a15..3f89a8ed21c 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -243,7 +243,9 @@ def test_deprecated_call_supports_match(self) -> None: warnings.warn("value must be 42", DeprecationWarning) with pytest.deprecated_call(): - with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.raises( + pytest.fail.Exception, match="Regex pattern did not match" + ): with pytest.deprecated_call(match=r"must be \d+$"): warnings.warn("this is not here", DeprecationWarning) @@ -406,7 +408,9 @@ def test_match_regex(self) -> None: def test_one_from_multiple_warns(self) -> None: with pytest.warns(): - with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.raises( + pytest.fail.Exception, match="Regex pattern did not match" + ): with pytest.warns(UserWarning, match=r"aaa"): with pytest.warns(UserWarning, match=r"aaa"): warnings.warn("cccccccccc", UserWarning) @@ -415,11 +419,38 @@ def test_one_from_multiple_warns(self) -> None: def test_none_of_multiple_warns(self) -> None: with pytest.warns(): - with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.raises( + pytest.fail.Exception, match="Regex pattern did not match" + ): with pytest.warns(UserWarning, match=r"aaa"): warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("cccccccccc", UserWarning) + def test_warns_match_failure_message_detail(self) -> None: + with pytest.warns(): + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(UserWarning, match=r"must be \d+$"): + warnings.warn("this is not here", UserWarning) + msg = str(excinfo.value) + assert "Regex pattern did not match" in msg + assert "this is not here" in msg + assert "DID NOT WARN" not in msg + + def test_warns_match_re_escape_hint(self) -> None: + with pytest.warns(): + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(UserWarning, match="foo (bar)"): + warnings.warn("foo (bar)", UserWarning) + assert "re.escape()" in str(excinfo.value) + + def test_warns_match_re_escape_hint_no_false_positive(self) -> None: + with pytest.warns(): + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(DeprecationWarning, match="foo (bar)"): + warnings.warn("some deprecation msg", DeprecationWarning) + warnings.warn("foo (bar)", UserWarning) + assert "re.escape()" not in str(excinfo.value) + @pytest.mark.filterwarnings("ignore") def test_can_capture_previously_warned(self) -> None: def f() -> int: @@ -589,7 +620,7 @@ def __init__(self, a, b): pass with pytest.warns(CustomWarning): - with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.raises(pytest.fail.Exception, match="Regex pattern did not match"): with pytest.warns(CustomWarning, match="not gonna match"): a, b = 1, 2 warnings.warn(CustomWarning(a, b))