diff --git a/changelog/14392.bugfix.rst b/changelog/14392.bugfix.rst new file mode 100644 index 00000000000..b67ff0ffec9 --- /dev/null +++ b/changelog/14392.bugfix.rst @@ -0,0 +1 @@ +Fixed a bug in :func:`pytest.raises(match=...) ` "fully escaped" detection, causing the regex diff display to be shown in some instances when the raw string diff display should be shown instead. diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py index 82fe2c41c96..77a32dcac84 100644 --- a/src/_pytest/raises.py +++ b/src/_pytest/raises.py @@ -345,9 +345,10 @@ def _check_raw_type( def is_fully_escaped(s: str) -> bool: # we know we won't compile with re.VERBOSE, so whitespace doesn't need to be escaped metacharacters = "{}()+.*?^$[]|" - return not any( - c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s) - ) + # Strip all escape sequences (backslash + any char), then check if any + # metacharacter remains unescaped in the resulting string. + stripped = re.sub(r"\\.", "", s) + return not any(c in metacharacters for c in stripped) def unescape(s: str) -> str: diff --git a/testing/python/raises.py b/testing/python/raises.py index f74d747c0df..52336b122a1 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -444,3 +444,19 @@ def test_pipe_is_treated_as_regex_metacharacter(self) -> None: assert not is_fully_escaped("foo|bar") assert is_fully_escaped(r"foo\|bar") assert unescape(r"foo\|bar") == "foo|bar" + + def test_consecutive_backslashes_in_escape_check(self) -> None: + """Consecutive backslashes escape each other, leaving the metachar unescaped.""" + from _pytest.raises import is_fully_escaped + + # r"\." -> one backslash escapes the dot -> fully escaped + assert is_fully_escaped(r"\.") + # r"\\." -> two backslashes: the first escapes the second, dot is unescaped + assert not is_fully_escaped(r"\\.") + # r"\\\." -> three backslashes: pair escapes pair, last escapes dot -> fully escaped + assert is_fully_escaped(r"\\\.") + # Same idea with pipe metachar + # "\\\\|" is the string \\| (2 backslashes + pipe): even count, pipe is unescaped + assert not is_fully_escaped("\\\\|") + # r"\\\\|" is the string \\\\| (4 backslashes + pipe): even count, pipe is unescaped + assert not is_fully_escaped(r"\\\\|")