From b2d2796657b0682dcf3a7212bdfb6cfcc58059e4 Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Fri, 20 Feb 2026 05:27:57 +0300 Subject: [PATCH 1/3] raises: add missing | metacharacter to is_fully_escaped and unescape The pipe character is a regex metacharacter (alternation operator) but is_fully_escaped did not include it, so a match pattern like '^foo|bar$' would be wrongly identified as a fully-escaped literal. This causes rawmatch to be set incorrectly, leading to misleading diff output on match failure. Also add | to the unescape function's character class for consistency. --- src/_pytest/raises.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py index 7c246fde280..75eea7d8cc9 100644 --- a/src/_pytest/raises.py +++ b/src/_pytest/raises.py @@ -363,14 +363,14 @@ 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 = "{}()+.*?^$[]" + metacharacters = "{}()+.*?^$[]|" return not any( c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s) ) def unescape(s: str) -> str: - return re.sub(r"\\([{}()+-.*?^$\[\]\s\\])", r"\1", s) + return re.sub(r"\\([{}()+-.*?^$\[\]\s\\|])", r"\1", s) # These classes conceptually differ from ExceptionInfo in that ExceptionInfo is tied, and From 9ef95be4ee9ab747667747ba9d6d33257e27dd48 Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:43:36 +0300 Subject: [PATCH 2/3] Add regression test for pipe metacharacter in is_fully_escaped Test that | is recognized as a regex metacharacter, not treated as a literal. Also verify that unescape handles escaped pipes correctly. --- testing/python/raises.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/testing/python/raises.py b/testing/python/raises.py index 6b2a765e7fb..7778f2caf08 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -430,3 +430,21 @@ def test_raises_match_compiled_regex(self) -> None: pattern_with_flags = re.compile(r"INVALID LITERAL", re.IGNORECASE) with pytest.raises(ValueError, match=pattern_with_flags): int("asdf") + + def test_pipe_metacharacter_not_treated_as_literal(self) -> None: + """Regression test: | (pipe) must be recognized as a regex metacharacter. + + When match='^foo|bar$' is used, is_fully_escaped should recognize this + as a real regex (not a fully-escaped literal), so that pytest does not + attempt an exact-string diff against it. + """ + from _pytest.raises import is_fully_escaped, unescape + + # Pipe is a metacharacter and should not be treated as escaped + assert not is_fully_escaped("foo|bar") + + # Escaped pipe should be treated as a literal + assert is_fully_escaped(r"foo\|bar") + + # unescape should remove the backslash from an escaped pipe + assert unescape(r"foo\|bar") == "foo|bar" From 67f1959450e38045c36bf3e5c720f45e7ee4a265 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:44:06 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/python/raises.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/python/raises.py b/testing/python/raises.py index 7778f2caf08..374a9d4882c 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -438,7 +438,8 @@ def test_pipe_metacharacter_not_treated_as_literal(self) -> None: as a real regex (not a fully-escaped literal), so that pytest does not attempt an exact-string diff against it. """ - from _pytest.raises import is_fully_escaped, unescape + from _pytest.raises import is_fully_escaped + from _pytest.raises import unescape # Pipe is a metacharacter and should not be treated as escaped assert not is_fully_escaped("foo|bar")