diff --git a/docs/source/reference/changelog.rst b/docs/source/reference/changelog.rst index 82987a05..c976b8da 100644 --- a/docs/source/reference/changelog.rst +++ b/docs/source/reference/changelog.rst @@ -6,6 +6,7 @@ Changelog =================== - Adds an optional `loop_scope` keyword argument to `pytest.mark.asyncio`. This argument controls which event loop is used to run the marked async test. `#706`_, `#871 `_ - Deprecates the optional `scope` keyword argument to `pytest.mark.asyncio` for API consistency with ``pytest_asyncio.fixture``. Users are encouraged to use the `loop_scope` keyword argument, which does exactly the same. +- Raises an error when passing `scope` or `loop_scope` as a positional argument to ``@pytest.mark.asyncio``. `#812 `_ 0.23.8 (2024-07-17) diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index aaa1e8f3..a90e125e 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -1004,6 +1004,10 @@ def pytest_runtest_setup(item: pytest.Item) -> None: def _get_marked_loop_scope(asyncio_marker: Mark) -> _ScopeName: assert asyncio_marker.name == "asyncio" + if asyncio_marker.args or ( + asyncio_marker.kwargs and set(asyncio_marker.kwargs) - {"loop_scope", "scope"} + ): + raise ValueError("mark.asyncio accepts only a keyword argument 'scope'.") if "scope" in asyncio_marker.kwargs: if "loop_scope" in asyncio_marker.kwargs: raise pytest.UsageError(_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR) diff --git a/tests/markers/test_invalid_arguments.py b/tests/markers/test_invalid_arguments.py new file mode 100644 index 00000000..a4f4b070 --- /dev/null +++ b/tests/markers/test_invalid_arguments.py @@ -0,0 +1,85 @@ +from textwrap import dedent + +import pytest + + +def test_no_error_when_scope_passed_as_sole_keyword_argument( + pytester: pytest.Pytester, +): + pytester.makepyfile( + dedent( + """\ + import pytest + + @pytest.mark.asyncio(loop_scope="session") + async def test_anything(): + pass + """ + ) + ) + result = pytester.runpytest_subprocess() + result.assert_outcomes(passed=1) + result.stdout.no_fnmatch_line("*ValueError*") + + +def test_error_when_scope_passed_as_positional_argument( + pytester: pytest.Pytester, +): + pytester.makepyfile( + dedent( + """\ + import pytest + + @pytest.mark.asyncio("session") + async def test_anything(): + pass + """ + ) + ) + result = pytester.runpytest_subprocess() + result.assert_outcomes(errors=1) + result.stdout.fnmatch_lines( + ["*ValueError: mark.asyncio accepts only a keyword argument*"] + ) + + +def test_error_when_wrong_keyword_argument_is_passed( + pytester: pytest.Pytester, +): + pytester.makepyfile( + dedent( + """\ + import pytest + + @pytest.mark.asyncio(cope="session") + async def test_anything(): + pass + """ + ) + ) + result = pytester.runpytest_subprocess() + result.assert_outcomes(errors=1) + result.stdout.fnmatch_lines( + ["*ValueError: mark.asyncio accepts only a keyword argument 'scope'*"] + ) + + +def test_error_when_additional_keyword_arguments_are_passed( + pytester: pytest.Pytester, +): + pytester.makepyfile( + dedent( + """\ + import pytest + + @pytest.mark.asyncio(loop_scope="session", more="stuff") + async def test_anything(): + pass + """ + ) + ) + result = pytester.runpytest_subprocess() + result.assert_outcomes(errors=1) + result.stdout.fnmatch_lines( + ["*ValueError: mark.asyncio accepts only a keyword argument*"] + )