Skip to content

Commit da615a4

Browse files
committed
Merge pull request #7144 from nicoddemus/async-testcase-7110
1 parent 3256288 commit da615a4

File tree

9 files changed

+70
-36
lines changed

9 files changed

+70
-36
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
- name: "windows-py38"
7171
python: "3.8"
7272
os: windows-latest
73-
tox_env: "py38-twisted"
73+
tox_env: "py38-unittestextras"
7474
use_coverage: true
7575

7676
- name: "ubuntu-py35"

changelog/7110.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again.

src/_pytest/compat.py

+7
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ def syntax, and doesn't contain yield), or a function decorated with
9393
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
9494

9595

96+
def is_async_function(func: object) -> bool:
97+
"""Return True if the given function seems to be an async function or async generator"""
98+
return iscoroutinefunction(func) or (
99+
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(func)
100+
)
101+
102+
96103
def getlocation(function, curdir=None) -> str:
97104
function = get_real_func(function)
98105
fn = py.path.local(inspect.getfile(function))

src/_pytest/python.py

+5-25
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
from _pytest.compat import get_real_func
3535
from _pytest.compat import getimfunc
3636
from _pytest.compat import getlocation
37+
from _pytest.compat import is_async_function
3738
from _pytest.compat import is_generator
38-
from _pytest.compat import iscoroutinefunction
3939
from _pytest.compat import NOTSET
4040
from _pytest.compat import REGEX_TYPE
4141
from _pytest.compat import safe_getattr
@@ -159,7 +159,7 @@ def pytest_configure(config):
159159
)
160160

161161

162-
def async_warn(nodeid: str) -> None:
162+
def async_warn_and_skip(nodeid: str) -> None:
163163
msg = "async def functions are not natively supported and have been skipped.\n"
164164
msg += (
165165
"You need to install a suitable plugin for your async framework, for example:\n"
@@ -175,33 +175,13 @@ def async_warn(nodeid: str) -> None:
175175
@hookimpl(trylast=True)
176176
def pytest_pyfunc_call(pyfuncitem: "Function"):
177177
testfunction = pyfuncitem.obj
178-
179-
try:
180-
# ignoring type as the import is invalid in py37 and mypy thinks its a error
181-
from unittest import IsolatedAsyncioTestCase # type: ignore
182-
except ImportError:
183-
async_ok_in_stdlib = False
184-
else:
185-
async_ok_in_stdlib = isinstance(
186-
getattr(testfunction, "__self__", None), IsolatedAsyncioTestCase
187-
)
188-
189-
if (
190-
iscoroutinefunction(testfunction)
191-
or (sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction))
192-
) and not async_ok_in_stdlib:
193-
async_warn(pyfuncitem.nodeid)
178+
if is_async_function(testfunction):
179+
async_warn_and_skip(pyfuncitem.nodeid)
194180
funcargs = pyfuncitem.funcargs
195181
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
196182
result = testfunction(**testargs)
197183
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
198-
if async_ok_in_stdlib:
199-
# todo: investigate moving this to the unittest plugin
200-
# by a test call result hook
201-
testcase = testfunction.__self__
202-
testcase._callMaybeAsync(lambda: result)
203-
else:
204-
async_warn(pyfuncitem.nodeid)
184+
async_warn_and_skip(pyfuncitem.nodeid)
205185
return True
206186

207187

src/_pytest/unittest.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import _pytest._code
77
import pytest
88
from _pytest.compat import getimfunc
9+
from _pytest.compat import is_async_function
910
from _pytest.config import hookimpl
1011
from _pytest.outcomes import exit
1112
from _pytest.outcomes import fail
@@ -227,13 +228,17 @@ def wrapped_testMethod(*args, **kwargs):
227228
self._needs_explicit_tearDown = True
228229
raise _GetOutOf_testPartExecutor(exc)
229230

230-
setattr(self._testcase, self._testcase._testMethodName, wrapped_testMethod)
231-
try:
232-
self._testcase(result=self)
233-
except _GetOutOf_testPartExecutor as exc:
234-
raise exc.args[0] from exc.args[0]
235-
finally:
236-
delattr(self._testcase, self._testcase._testMethodName)
231+
# let the unittest framework handle async functions
232+
if is_async_function(self.obj):
233+
self._testcase(self)
234+
else:
235+
setattr(self._testcase, self._testcase._testMethodName, wrapped_testMethod)
236+
try:
237+
self._testcase(result=self)
238+
except _GetOutOf_testPartExecutor as exc:
239+
raise exc.args[0] from exc.args[0]
240+
finally:
241+
delattr(self._testcase, self._testcase._testMethodName)
237242

238243
def _prunetraceback(self, excinfo):
239244
Function._prunetraceback(self, excinfo)

testing/example_scripts/unittest/test_unittest_asyncio.py

+9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
from unittest import IsolatedAsyncioTestCase # type: ignore
22

33

4+
teardowns = []
5+
6+
47
class AsyncArguments(IsolatedAsyncioTestCase):
8+
async def asyncTearDown(self):
9+
teardowns.append(None)
10+
511
async def test_something_async(self):
612
async def addition(x, y):
713
return x + y
@@ -13,3 +19,6 @@ async def addition(x, y):
1319
return x + y
1420

1521
self.assertEqual(await addition(2, 2), 3)
22+
23+
def test_teardowns(self):
24+
assert len(teardowns) == 2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Issue #7110"""
2+
import asyncio
3+
4+
import asynctest
5+
6+
7+
teardowns = []
8+
9+
10+
class Test(asynctest.TestCase):
11+
async def tearDown(self):
12+
teardowns.append(None)
13+
14+
async def test_error(self):
15+
await asyncio.sleep(0)
16+
self.fail("failing on purpose")
17+
18+
async def test_ok(self):
19+
await asyncio.sleep(0)
20+
21+
def test_teardowns(self):
22+
assert len(teardowns) == 2

testing/test_unittest.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1136,4 +1136,13 @@ def test_async_support(testdir):
11361136

11371137
testdir.copy_example("unittest/test_unittest_asyncio.py")
11381138
reprec = testdir.inline_run()
1139-
reprec.assertoutcome(failed=1, passed=1)
1139+
reprec.assertoutcome(failed=1, passed=2)
1140+
1141+
1142+
def test_asynctest_support(testdir):
1143+
"""Check asynctest support (#7110)"""
1144+
pytest.importorskip("asynctest")
1145+
1146+
testdir.copy_example("unittest/test_unittest_asynctest.py")
1147+
reprec = testdir.inline_run()
1148+
reprec.assertoutcome(failed=1, passed=2)

tox.ini

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ envlist =
1111
py38
1212
pypy
1313
pypy3
14-
py37-{pexpect,xdist,twisted,numpy,pluggymaster}
14+
py37-{pexpect,xdist,unittestextras,numpy,pluggymaster}
1515
doctesting
1616
py37-freeze
1717
docs
@@ -50,7 +50,8 @@ deps =
5050
pexpect: pexpect
5151
pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master
5252
pygments
53-
twisted: twisted
53+
unittestextras: twisted
54+
unittestextras: asynctest
5455
xdist: pytest-xdist>=1.13
5556
{env:_PYTEST_TOX_EXTRA_DEP:}
5657

0 commit comments

Comments
 (0)