diff --git a/AUTHORS b/AUTHORS index 4cdf231b14..040214e33b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -164,6 +164,7 @@ Kyle Altendorf Lawrence Mitchell Lee Kamentsky Lev Maximov +Lewis Cowles Llandy Riveron Del Risco Loic Esteve Lukas Bednar diff --git a/changelog/7489.improvement.rst b/changelog/7489.improvement.rst new file mode 100644 index 0000000000..ffa4e0013c --- /dev/null +++ b/changelog/7489.improvement.rst @@ -0,0 +1 @@ +The :func:`pytest.raises` function has a clearer error message when ``match`` matches the obtained string but is not regex-escaped. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index eb85d941cd..05083c7c97 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -609,9 +609,11 @@ def match(self, regexp: "Union[str, Pattern]") -> "Literal[True]": If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - assert re.search( - regexp, str(self.value) - ), "Pattern {!r} does not match {!r}".format(regexp, str(self.value)) + regextest = re.search(regexp, str(self.value)) + msg = "Regex pattern {!r} does not match {!r}." + if regexp == str(self.value): + msg += " Did you mean to `re.escape()` the regex?" + assert regextest, msg.format(regexp, str(self.value)) # Return True to allow for "assert excinfo.match()". return True diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 060f52cc7a..78b55e26e0 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -423,7 +423,7 @@ def test_division_zero(): result = testdir.runpytest() assert result.ret != 0 - exc_msg = "Pattern '[[]123[]]+' does not match 'division by zero'" + exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." result.stdout.fnmatch_lines(["E * AssertionError: {}".format(exc_msg)]) result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") diff --git a/testing/python/raises.py b/testing/python/raises.py index 3f378d015a..12d44495c9 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -197,7 +197,7 @@ def test_raises_match(self) -> None: int("asdf") msg = "with base 16" - expr = "Pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\"".format( + expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format( msg ) with pytest.raises(AssertionError, match=re.escape(expr)): @@ -223,7 +223,19 @@ def test_match_failure_string_quoting(self): with pytest.raises(AssertionError, match="'foo"): raise AssertionError("'bar") (msg,) = excinfo.value.args - assert msg == 'Pattern "\'foo" does not match "\'bar"' + assert msg == 'Regex pattern "\'foo" does not match "\'bar".' + + def test_match_failure_exact_string_message(self): + message = "Oh here is a message with (42) numbers in parameters" + with pytest.raises(AssertionError) as excinfo: + with pytest.raises(AssertionError, match=message): + raise AssertionError(message) + (msg,) = excinfo.value.args + assert msg == ( + "Regex pattern 'Oh here is a message with (42) numbers in " + "parameters' does not match 'Oh here is a message with (42) " + "numbers in parameters'. Did you mean to `re.escape()` the regex?" + ) def test_raises_match_wrong_type(self): """Raising an exception with the wrong type and match= given.