From 66eff85e5424f0274e15081eb4185a2e23b4c167 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:48:15 +0200 Subject: [PATCH 1/7] docs: use double quotes for cross-platform compatibility in example code --- doc/en/example/markers.rst | 2 +- doc/en/how-to/usage.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 159ff2cd1..babcd9e2f 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -80,7 +80,7 @@ keyword arguments, e.g. to run only tests marked with ``device`` and the specifi .. code-block:: pytest - $ pytest -v -m 'device(serial="123")' + $ pytest -v -m "device(serial='123')" =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache diff --git a/doc/en/how-to/usage.rst b/doc/en/how-to/usage.rst index 05ee04600..0e0a0310f 100644 --- a/doc/en/how-to/usage.rst +++ b/doc/en/how-to/usage.rst @@ -88,7 +88,7 @@ with the ``phase`` keyword argument set to ``1``: .. code-block:: bash - pytest -m slow(phase=1) + pytest -m "slow(phase=1)" For more information see :ref:`marks `. From 73bc35ce2b22723a87c26cad6aa7835dcdc060cf Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:49:59 +0200 Subject: [PATCH 2/7] docs(expression.py): correct grammar definition of lexer --- src/_pytest/mark/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 3f4071dce..3b7f4c51b 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -5,7 +5,7 @@ The grammar is: expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident ( '(' name '=' value ( ', ' name '=' value )* ')')* +not_expr: 'not' not_expr | '(' expr ')' | ident ('(' name '=' value ( ', ' name '=' value )* ')')? ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ From 3cce243774b03125bb123a7b69f684ea0abba34b Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:50:26 +0200 Subject: [PATCH 3/7] docs(expression.py): fix typo --- src/_pytest/mark/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 3b7f4c51b..2067110d2 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -12,7 +12,7 @@ ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ The semantics are: - Empty expression evaluates to False. -- ident evaluates to True of False according to a provided matcher function. +- ident evaluates to True or False according to a provided matcher function. - or/and/not evaluate according to the usual boolean semantics. """ From 540ede34397600794e2979f2ee952bb999496582 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:57:05 +0200 Subject: [PATCH 4/7] docs(expression.py): describe new `name` & `value` productions --- src/_pytest/mark/expression.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 2067110d2..0daaaf22f 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -8,12 +8,15 @@ and_expr: not_expr ('and' not_expr)* not_expr: 'not' not_expr | '(' expr ')' | ident ('(' name '=' value ( ', ' name '=' value )* ')')? ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ +name: a valid ident, but not a reserved keyword +value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' The semantics are: - Empty expression evaluates to False. - ident evaluates to True or False according to a provided matcher function. - or/and/not evaluate according to the usual boolean semantics. +- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function. """ from __future__ import annotations From dd5719695376b19c2ad9db0789a08f459ad7c9fb Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 20:44:50 +0200 Subject: [PATCH 5/7] perf(expression): define `TokenType.STRING` as "string literal" for clearer errors --- src/_pytest/mark/expression.py | 2 +- testing/test_mark_expression.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 0daaaf22f..ce98e9c7e 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -51,7 +51,7 @@ class TokenType(enum.Enum): IDENT = "identifier" EOF = "end of input" EQUAL = "=" - STRING = "str" + STRING = "string literal" COMMA = "," diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index c31ab4470..b5f1f330a 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -228,6 +228,7 @@ def test_invalid_idents(ident: str) -> None: r'escaping with "\\" not supported in marker expression', ), ("mark(empty_list=[])", r'unexpected character/s "\[\]"'), + ("'str'", "expected not OR left parenthesis OR identifier; got string literal"), ), ) def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? From 3d07791c36804a0ebcd6b0446f852e8e6745d30d Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 20:49:21 +0200 Subject: [PATCH 6/7] chore: remove obsolete `TODO`s --- testing/test_mark.py | 2 +- testing/test_mark_expression.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/test_mark.py b/testing/test_mark.py index 6a94cc9f7..89eef7920 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -235,7 +235,7 @@ def test_mark_option( @pytest.mark.parametrize( ("expr", "expected_passed"), - [ # TODO: improve/sort out + [ ("car(color='red')", ["test_one"]), ("car(color='red') or car(color='blue')", ["test_one", "test_two"]), ("car and not car(temp=5)", ["test_one", "test_three"]), diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index b5f1f330a..f8f5f9221 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -231,7 +231,7 @@ def test_invalid_idents(ident: str) -> None: ("'str'", "expected not OR left parenthesis OR identifier; got string literal"), ), ) -def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? +def test_invalid_kwarg_name_or_value( expr: str, expected_error_msg: str, mark_matcher: MarkMatcher ) -> None: with pytest.raises(ParseError, match=expected_error_msg): @@ -290,7 +290,7 @@ def test_keyword_expressions_with_numbers( ("builtin_matchers_mark(z=1)", False), ), ) -def test_builtin_matchers_keyword_expressions( # TODO: naming when decided +def test_builtin_matchers_keyword_expressions( expr: str, expected: bool, mark_matcher: MarkMatcher ) -> None: assert evaluate(expr, mark_matcher) is expected From 36b384afc7c2457a2772abcef1f3270a7067d0cb Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Tue, 25 Jun 2024 09:16:57 +0200 Subject: [PATCH 7/7] docs(expression.py): simplify grammar documentation by defining `kwargs` separately --- src/_pytest/mark/expression.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index ce98e9c7e..89cc0e94d 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -5,9 +5,10 @@ The grammar is: expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident ('(' name '=' value ( ', ' name '=' value )* ')')? +not_expr: 'not' not_expr | '(' expr ')' | ident kwargs? ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ +kwargs: ('(' name '=' value ( ', ' name '=' value )* ')') name: a valid ident, but not a reserved keyword value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None'