diff --git a/changelog/2224.feature.rst b/changelog/2224.feature.rst new file mode 100644 index 000000000..6f0df93ae --- /dev/null +++ b/changelog/2224.feature.rst @@ -0,0 +1,4 @@ +``async`` test functions are skipped and a warning is emitted when a suitable +async plugin is not installed (such as ``pytest-asyncio`` or ``pytest-trio``). + +Previously ``async`` functions would not execute at all but still be marked as "passed". diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 0b4fb9a24..5b289c7c8 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -43,6 +43,7 @@ from _pytest.mark import MARK_GEN from _pytest.mark.structures import get_unpacked_marks from _pytest.mark.structures import normalize_mark_list from _pytest.outcomes import fail +from _pytest.outcomes import skip from _pytest.pathlib import parts from _pytest.warning_types import PytestWarning @@ -156,6 +157,15 @@ def pytest_configure(config): @hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj + iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None) + if iscoroutinefunction is not None and iscoroutinefunction(testfunction): + msg = "Coroutine functions are not natively supported and have been skipped.\n" + msg += "You need to install a suitable plugin for your async framework, for example:\n" + msg += " - pytest-asyncio\n" + msg += " - pytest-trio\n" + msg += " - pytest-tornasync" + warnings.warn(PytestWarning(msg.format(pyfuncitem.nodeid))) + skip(msg="coroutine function and no async plugin installed (see warnings)") funcargs = pyfuncitem.funcargs testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} testfunction(**testargs) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 17111369b..39bd2fd77 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1179,3 +1179,31 @@ def test_fixture_mock_integration(testdir): def test_usage_error_code(testdir): result = testdir.runpytest("-unknown-option-") assert result.ret == EXIT_USAGEERROR + + +@pytest.mark.skipif( + sys.version_info[:2] < (3, 5), reason="async def syntax python 3.5+ only" +) +@pytest.mark.filterwarnings("default") +def test_warn_on_async_function(testdir): + testdir.makepyfile( + test_async=""" + async def test_1(): + pass + async def test_2(): + pass + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "test_async.py::test_1", + "test_async.py::test_2", + "*Coroutine functions are not natively supported*", + "*2 skipped, 2 warnings in*", + ] + ) + # ensure our warning message appears only once + assert ( + result.stdout.str().count("Coroutine functions are not natively supported") == 1 + )