From 807c01451867cb107900a5a9ce0d07f261770d99 Mon Sep 17 00:00:00 2001 From: polkapolka <7219835+polkapolka@users.noreply.github.com> Date: Mon, 20 May 2024 22:14:51 +0000 Subject: [PATCH 1/4] changed warning raised by async def to error --- src/_pytest/config/exceptions.py | 4 ---- src/_pytest/python.py | 16 ++++++++++++++-- src/_pytest/warning_types.py | 2 +- testing/acceptance_test.py | 31 +++++++++++-------------------- testing/test_unittest.py | 2 +- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/_pytest/config/exceptions.py b/src/_pytest/config/exceptions.py index 4031ea732f3..95c412734be 100644 --- a/src/_pytest/config/exceptions.py +++ b/src/_pytest/config/exceptions.py @@ -1,7 +1,3 @@ -from typing import final - - -@final class UsageError(Exception): """Error in pytest usage or invocation.""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4887614de3b..bf20ca31d13 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -54,6 +54,7 @@ from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser +from _pytest.config.exceptions import UsageError from _pytest.deprecated import check_ispytest from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureRequest @@ -77,7 +78,6 @@ from _pytest.stash import StashKey from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestReturnNotNoneWarning -from _pytest.warning_types import PytestUnhandledCoroutineWarning if TYPE_CHECKING: @@ -138,6 +138,16 @@ def pytest_configure(config: Config) -> None: ) +@final +class PytestUnhandledCoroutineError(UsageError): + """An unraisable exception resulted in an error. + + Unraisable exceptions are exceptions raised in :meth:`__del__ ` + implementations and similar situations when the exception cannot be raised + as normal. + """ + + def async_warn_and_skip(nodeid: str) -> None: msg = "async def functions are not natively supported and have been skipped.\n" msg += ( @@ -148,7 +158,9 @@ def async_warn_and_skip(nodeid: str) -> None: msg += " - pytest-tornasync\n" msg += " - pytest-trio\n" msg += " - pytest-twisted" - warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid))) + raise PytestUnhandledCoroutineError( + msg.format(nodeid) + ) # TODO: This is the warning to look at skip(reason="async def function and no async plugin installed (see warnings)") diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index a5884f29582..d2e542651fe 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -77,7 +77,7 @@ def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": @final -class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): +class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): # TODO: look at this """Warning emitted for an unhandled coroutine. A coroutine was encountered when collecting test functions, but was not diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index ac7fab3d27a..12c8a50dd98 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1233,7 +1233,7 @@ def test_usage_error_code(pytester: Pytester) -> None: assert result.ret == ExitCode.USAGE_ERROR -def test_warn_on_async_function(pytester: Pytester) -> None: +def test_error_on_async_function(pytester: Pytester) -> None: # TODO: Change this # In the below we .close() the coroutine only to avoid # "RuntimeWarning: coroutine 'test_2' was never awaited" # which messes with other tests. @@ -1249,23 +1249,19 @@ def test_3(): return coro """ ) - result = pytester.runpytest("-Wdefault") + result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "test_async.py::test_1", - "test_async.py::test_2", - "test_async.py::test_3", + "*test_async.py::test_1*", + "*test_async.py::test_2*", + "*test_async.py::test_3*", "*async def functions are not natively supported*", - "*3 skipped, 3 warnings in*", ] ) - # ensure our warning message appears only once - assert ( - result.stdout.str().count("async def functions are not natively supported") == 1 - ) + result.assert_outcomes(failed=3) -def test_warn_on_async_gen_function(pytester: Pytester) -> None: +def test_error_on_async_gen_function(pytester: Pytester) -> None: # TODO: Change this pytester.makepyfile( test_async=""" async def test_1(): @@ -1276,20 +1272,15 @@ def test_3(): return test_2() """ ) - result = pytester.runpytest("-Wdefault") + result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "test_async.py::test_1", - "test_async.py::test_2", - "test_async.py::test_3", + "*test_async.py::test_1*", + "*test_async.py::test_2*", + "*test_async.py::test_3*", "*async def functions are not natively supported*", - "*3 skipped, 3 warnings in*", ] ) - # ensure our warning message appears only once - assert ( - result.stdout.str().count("async def functions are not natively supported") == 1 - ) def test_pdb_can_be_rewritten(pytester: Pytester) -> None: diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 003a74d3849..e2a2904a3ba 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1309,7 +1309,7 @@ def test_1(self): assert tracked == [] -def test_async_support(pytester: Pytester) -> None: +def test_async_support(pytester: Pytester) -> None: # TODO: Change this pytest.importorskip("unittest.async_case") pytester.copy_example("unittest/test_unittest_asyncio.py") From 216ec3c0ba87b50af11a3fd6f633b69105a7708e Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 25 Oct 2024 13:36:55 +0200 Subject: [PATCH 2/4] Remove PytestReturnNotNoneWarning and PytestUnhandledCoroutineWarning. Make tests fail instead of raising warning/exception. fix tests. add changelog. --- src/_pytest/config/exceptions.py | 3 ++ src/_pytest/python.py | 48 +++++++++++--------------------- src/_pytest/warning_types.py | 18 ------------ src/pytest/__init__.py | 4 --- testing/acceptance_test.py | 12 ++++---- testing/test_unittest.py | 2 +- 6 files changed, 28 insertions(+), 59 deletions(-) diff --git a/src/_pytest/config/exceptions.py b/src/_pytest/config/exceptions.py index 147afb03e26..90108eca904 100644 --- a/src/_pytest/config/exceptions.py +++ b/src/_pytest/config/exceptions.py @@ -1,6 +1,9 @@ from __future__ import annotations +from typing import final + +@final class UsageError(Exception): """Error in pytest usage or invocation.""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 180ef439dd6..d48a6c4a9fb 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -51,7 +51,6 @@ from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser -from _pytest.config.exceptions import UsageError from _pytest.deprecated import check_ispytest from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureRequest @@ -74,7 +73,6 @@ from _pytest.scope import Scope from _pytest.stash import StashKey from _pytest.warning_types import PytestCollectionWarning -from _pytest.warning_types import PytestReturnNotNoneWarning if TYPE_CHECKING: @@ -135,48 +133,36 @@ def pytest_configure(config: Config) -> None: ) -@final -class PytestUnhandledCoroutineError(UsageError): - """An unraisable exception resulted in an error. - - Unraisable exceptions are exceptions raised in :meth:`__del__ ` - implementations and similar situations when the exception cannot be raised - as normal. - """ - - -def async_warn_and_skip(nodeid: str) -> None: - msg = "async def functions are not natively supported and have been skipped.\n" - msg += ( +def async_fail(nodeid: str) -> None: + msg = ( + "async def functions are not natively supported.\n" "You need to install a suitable plugin for your async framework, for example:\n" + " - anyio\n" + " - pytest-asyncio\n" + " - pytest-tornasync\n" + " - pytest-trio\n" + " - pytest-twisted" ) - msg += " - anyio\n" - msg += " - pytest-asyncio\n" - msg += " - pytest-tornasync\n" - msg += " - pytest-trio\n" - msg += " - pytest-twisted" - raise PytestUnhandledCoroutineError( - msg.format(nodeid) - ) # TODO: This is the warning to look at - skip(reason="async def function and no async plugin installed (see warnings)") + fail(msg, pytrace=False) @hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: testfunction = pyfuncitem.obj if is_async_function(testfunction): - async_warn_and_skip(pyfuncitem.nodeid) + async_fail(pyfuncitem.nodeid) funcargs = pyfuncitem.funcargs testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} result = testfunction(**testargs) if hasattr(result, "__await__") or hasattr(result, "__aiter__"): - async_warn_and_skip(pyfuncitem.nodeid) + async_fail(pyfuncitem.nodeid) elif result is not None: - warnings.warn( - PytestReturnNotNoneWarning( - f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a " - "future version of pytest. Did you mean to use `assert` instead of `return`?" - ) + fail( + ( + f"Expected None, but test returned {result!r}. " + "Did you mean to use `assert` instead of `return`?" + ), + pytrace=False, ) return True diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index cf189a407c1..b8e9998cd2e 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -56,12 +56,6 @@ class PytestRemovedIn9Warning(PytestDeprecationWarning): __module__ = "pytest" -class PytestReturnNotNoneWarning(PytestWarning): - """Warning emitted when a test function is returning value other than None.""" - - __module__ = "pytest" - - @final class PytestExperimentalApiWarning(PytestWarning, FutureWarning): """Warning category used to denote experiments in pytest. @@ -77,18 +71,6 @@ def simple(cls, apiname: str) -> PytestExperimentalApiWarning: return cls(f"{apiname} is an experimental api that may change over time") -@final -class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): # TODO: look at this - """Warning emitted for an unhandled coroutine. - - A coroutine was encountered when collecting test functions, but was not - handled by any async-aware plugin. - Coroutine test functions are not natively supported. - """ - - __module__ = "pytest" - - @final class PytestUnknownMarkWarning(PytestWarning): """Warning emitted on use of unknown markers. diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 92152b7c7b4..5ab2a22b0c0 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -78,8 +78,6 @@ from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestExperimentalApiWarning from _pytest.warning_types import PytestRemovedIn9Warning -from _pytest.warning_types import PytestReturnNotNoneWarning -from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning from _pytest.warning_types import PytestUnknownMarkWarning from _pytest.warning_types import PytestUnraisableExceptionWarning @@ -142,10 +140,8 @@ "PytestDeprecationWarning", "PytestExperimentalApiWarning", "PytestRemovedIn9Warning", - "PytestReturnNotNoneWarning", "Pytester", "PytestPluginManager", - "PytestUnhandledCoroutineWarning", "PytestUnhandledThreadExceptionWarning", "PytestUnknownMarkWarning", "PytestUnraisableExceptionWarning", diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 6d0537f092c..7a8d871144a 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1235,7 +1235,7 @@ def test_usage_error_code(pytester: Pytester) -> None: assert result.ret == ExitCode.USAGE_ERROR -def test_error_on_async_function(pytester: Pytester) -> None: # TODO: Change this +def test_error_on_async_function(pytester: Pytester) -> None: # In the below we .close() the coroutine only to avoid # "RuntimeWarning: coroutine 'test_2' was never awaited" # which messes with other tests. @@ -1254,16 +1254,16 @@ def test_3(): result = pytester.runpytest() result.stdout.fnmatch_lines( [ + "*async def functions are not natively supported*", "*test_async.py::test_1*", "*test_async.py::test_2*", "*test_async.py::test_3*", - "*async def functions are not natively supported*", ] ) result.assert_outcomes(failed=3) -def test_error_on_async_gen_function(pytester: Pytester) -> None: # TODO: Change this +def test_error_on_async_gen_function(pytester: Pytester) -> None: pytester.makepyfile( test_async=""" async def test_1(): @@ -1277,12 +1277,13 @@ def test_3(): result = pytester.runpytest() result.stdout.fnmatch_lines( [ + "*async def functions are not natively supported*", "*test_async.py::test_1*", "*test_async.py::test_2*", "*test_async.py::test_3*", - "*async def functions are not natively supported*", ] ) + result.assert_outcomes(failed=3) def test_pdb_can_be_rewritten(pytester: Pytester) -> None: @@ -1368,7 +1369,7 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None: popen.stderr.close() -def test_function_return_non_none_warning(pytester: Pytester) -> None: +def test_function_return_non_none_error(pytester: Pytester) -> None: pytester.makepyfile( """ def test_stuff(): @@ -1376,6 +1377,7 @@ def test_stuff(): """ ) res = pytester.runpytest() + res.assert_outcomes(failed=1) res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"]) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 1f7b2f091c0..56224c08228 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1314,7 +1314,7 @@ def test_1(self): assert tracked == [] -def test_async_support(pytester: Pytester) -> None: # TODO: Change this +def test_async_support(pytester: Pytester) -> None: pytest.importorskip("unittest.async_case") pytester.copy_example("unittest/test_unittest_asyncio.py") From c7eae3a9575e2822e560156aef9530ec96ae5427 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 25 Oct 2024 13:37:54 +0200 Subject: [PATCH 3/4] add changelog entries --- changelog/11372.improvement.rst | 1 + changelog/12346.misc.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/11372.improvement.rst create mode 100644 changelog/12346.misc.rst diff --git a/changelog/11372.improvement.rst b/changelog/11372.improvement.rst new file mode 100644 index 00000000000..f4b5c3c6f6b --- /dev/null +++ b/changelog/11372.improvement.rst @@ -0,0 +1 @@ +Async tests will now fail, instead of warning+skipping, if you don't have any suitable plugin installed. diff --git a/changelog/12346.misc.rst b/changelog/12346.misc.rst new file mode 100644 index 00000000000..7013cf734c8 --- /dev/null +++ b/changelog/12346.misc.rst @@ -0,0 +1 @@ +Tests will now fail, instead of raising a warning, if they return any value other than None. From 29490af255630b3e4c949e50c3ead1756820707c Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 25 Oct 2024 14:52:34 +0200 Subject: [PATCH 4/4] fix docs --- doc/en/changelog.rst | 2 +- doc/en/deprecations.rst | 2 +- doc/en/reference/reference.rst | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 9f30c86be3a..0a32f88c6ff 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -1462,7 +1462,7 @@ pytest 7.2.0 (2022-10-23) Deprecations ------------ -- `#10012 `_: Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8. +- `#10012 `_: Update ``pytest.PytestUnhandledCoroutineWarning`` to a deprecation; it will raise an error in pytest 8. - `#10396 `_: pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 153d5195476..e55f0d71c2e 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -251,7 +251,7 @@ Returning non-None value in test functions .. deprecated:: 7.2 -A :class:`pytest.PytestReturnNotNoneWarning` is now emitted if a test function returns something other than `None`. +A ``pytest.PytestReturnNotNoneWarning`` is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example: diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index b6ec5c65e1e..71bf6a8986d 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1229,15 +1229,9 @@ Custom warnings generated in some situations such as improper usage or deprecate .. autoclass:: pytest.PytestExperimentalApiWarning :show-inheritance: -.. autoclass:: pytest.PytestReturnNotNoneWarning - :show-inheritance: - .. autoclass:: pytest.PytestRemovedIn9Warning :show-inheritance: -.. autoclass:: pytest.PytestUnhandledCoroutineWarning - :show-inheritance: - .. autoclass:: pytest.PytestUnknownMarkWarning :show-inheritance: