From 25c65616f4455ac78be1027c2fb4debef827c4e6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 8 Aug 2021 11:50:15 +0300 Subject: [PATCH] mark/expression: allow backslash characters in identifiers Fixes #8983. --- changelog/8983.bugfix.rst | 2 ++ src/_pytest/mark/expression.py | 4 ++-- testing/test_mark_expression.py | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 changelog/8983.bugfix.rst diff --git a/changelog/8983.bugfix.rst b/changelog/8983.bugfix.rst new file mode 100644 index 000000000..403d421d6 --- /dev/null +++ b/changelog/8983.bugfix.rst @@ -0,0 +1,2 @@ +The test selection options ``pytest -k`` and ``pytest -m`` now support matching names containing backslash (`\\`) characters. +Backslashes are treated literally, not as escape characters (the values being matched against are already escaped). diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 72e2ed4bd..20b424e16 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -6,7 +6,7 @@ expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* not_expr: 'not' not_expr | '(' expr ')' | ident -ident: (\w|:|\+|-|\.|\[|\])+ +ident: (\w|:|\+|-|\.|\[|\]|\\)+ The semantics are: @@ -88,7 +88,7 @@ class Scanner: yield Token(TokenType.RPAREN, ")", pos) pos += 1 else: - match = re.match(r"(:?\w|:|\+|-|\.|\[|\])+", input[pos:]) + match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\)+", input[pos:]) if match: value = match.group(0) if value == "or": diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index d37324f51..c758ee667 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -66,6 +66,20 @@ def test_syntax_oddeties(expr: str, expected: bool) -> None: assert evaluate(expr, matcher) is expected +def test_backslash_not_treated_specially() -> None: + r"""When generating nodeids, if the source name contains special characters + like a newline, they are escaped into two characters like \n. Therefore, a + user will never need to insert a literal newline, only \n (two chars). So + mark expressions themselves do not support escaping, instead they treat + backslashes as regular identifier characters.""" + matcher = {r"\nfoo\n"}.__contains__ + + assert evaluate(r"\nfoo\n", matcher) + assert not evaluate(r"foo", matcher) + with pytest.raises(ParseError): + evaluate("\nfoo\n", matcher) + + @pytest.mark.parametrize( ("expr", "column", "message"), ( @@ -129,6 +143,7 @@ def test_syntax_errors(expr: str, column: int, message: str) -> None: ":::", "a:::c", "a+-b", + r"\nhe\\l\lo\n\t\rbye", "אבגד", "aaאבגדcc", "a[bcd]", @@ -156,7 +171,6 @@ def test_valid_idents(ident: str) -> None: "ident", ( "/", - "\\", "^", "*", "=",