Skip to content

Commit 0ba774a

Browse files
authored
warn for async generator functions (#5734)
warn for async generator functions
2 parents 0a62c4a + 2f1b192 commit 0ba774a

File tree

5 files changed

+68
-15
lines changed

5 files changed

+68
-15
lines changed

changelog/5734.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Skip async generator test functions, and update the warning message to refer to ``async def`` functions.

src/_pytest/compat.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,16 @@ def is_generator(func):
4040

4141

4242
def iscoroutinefunction(func):
43-
"""Return True if func is a decorated coroutine function.
43+
"""
44+
Return True if func is a coroutine function (a function defined with async
45+
def syntax, and doesn't contain yield), or a function decorated with
46+
@asyncio.coroutine.
4447
45-
Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
46-
which in turns also initializes the "logging" module as side-effect (see issue #8).
48+
Note: copied and modified from Python 3.5's builtin couroutines.py to avoid
49+
importing asyncio directly, which in turns also initializes the "logging"
50+
module as a side-effect (see issue #8).
4751
"""
48-
return getattr(func, "_is_coroutine", False) or (
49-
hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
50-
)
52+
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
5153

5254

5355
def getlocation(function, curdir):

src/_pytest/python.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from _pytest.compat import getimfunc
2424
from _pytest.compat import getlocation
2525
from _pytest.compat import is_generator
26+
from _pytest.compat import iscoroutinefunction
2627
from _pytest.compat import NOTSET
2728
from _pytest.compat import REGEX_TYPE
2829
from _pytest.compat import safe_getattr
@@ -151,15 +152,16 @@ def pytest_configure(config):
151152
@hookimpl(trylast=True)
152153
def pytest_pyfunc_call(pyfuncitem):
153154
testfunction = pyfuncitem.obj
154-
iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None)
155-
if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
156-
msg = "Coroutine functions are not natively supported and have been skipped.\n"
155+
if iscoroutinefunction(testfunction) or (
156+
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction)
157+
):
158+
msg = "async def functions are not natively supported and have been skipped.\n"
157159
msg += "You need to install a suitable plugin for your async framework, for example:\n"
158160
msg += " - pytest-asyncio\n"
159161
msg += " - pytest-trio\n"
160162
msg += " - pytest-tornasync"
161163
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid)))
162-
skip(msg="coroutine function and no async plugin installed (see warnings)")
164+
skip(msg="async def function and no async plugin installed (see warnings)")
163165
funcargs = pyfuncitem.funcargs
164166
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
165167
testfunction(**testargs)

testing/acceptance_test.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,11 +1199,39 @@ async def test_2():
11991199
[
12001200
"test_async.py::test_1",
12011201
"test_async.py::test_2",
1202-
"*Coroutine functions are not natively supported*",
1202+
"*async def functions are not natively supported*",
12031203
"*2 skipped, 2 warnings in*",
12041204
]
12051205
)
12061206
# ensure our warning message appears only once
12071207
assert (
1208-
result.stdout.str().count("Coroutine functions are not natively supported") == 1
1208+
result.stdout.str().count("async def functions are not natively supported") == 1
1209+
)
1210+
1211+
1212+
@pytest.mark.filterwarnings("default")
1213+
@pytest.mark.skipif(
1214+
sys.version_info < (3, 6), reason="async gen syntax available in Python 3.6+"
1215+
)
1216+
def test_warn_on_async_gen_function(testdir):
1217+
testdir.makepyfile(
1218+
test_async="""
1219+
async def test_1():
1220+
yield
1221+
async def test_2():
1222+
yield
1223+
"""
1224+
)
1225+
result = testdir.runpytest()
1226+
result.stdout.fnmatch_lines(
1227+
[
1228+
"test_async.py::test_1",
1229+
"test_async.py::test_2",
1230+
"*async def functions are not natively supported*",
1231+
"*2 skipped, 2 warnings in*",
1232+
]
1233+
)
1234+
# ensure our warning message appears only once
1235+
assert (
1236+
result.stdout.str().count("async def functions are not natively supported") == 1
12091237
)

testing/test_compat.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ def test_is_generator_asyncio():
9191
result.stdout.fnmatch_lines(["*1 passed*"])
9292

9393

94-
@pytest.mark.skipif(
95-
sys.version_info < (3, 5), reason="async syntax available in Python 3.5+"
96-
)
9794
def test_is_generator_async_syntax(testdir):
9895
testdir.makepyfile(
9996
"""
@@ -113,6 +110,29 @@ async def bar():
113110
result.stdout.fnmatch_lines(["*1 passed*"])
114111

115112

113+
@pytest.mark.skipif(
114+
sys.version_info < (3, 6), reason="async gen syntax available in Python 3.6+"
115+
)
116+
def test_is_generator_async_gen_syntax(testdir):
117+
testdir.makepyfile(
118+
"""
119+
from _pytest.compat import is_generator
120+
def test_is_generator_py36():
121+
async def foo():
122+
yield
123+
await foo()
124+
125+
async def bar():
126+
yield
127+
128+
assert not is_generator(foo)
129+
assert not is_generator(bar)
130+
"""
131+
)
132+
result = testdir.runpytest()
133+
result.stdout.fnmatch_lines(["*1 passed*"])
134+
135+
116136
class ErrorsHelper:
117137
@property
118138
def raise_exception(self):

0 commit comments

Comments
 (0)