summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml2
-rw-r--r--changelog/7110.bugfix.rst1
-rw-r--r--src/_pytest/compat.py7
-rw-r--r--src/_pytest/python.py30
-rw-r--r--src/_pytest/unittest.py19
-rw-r--r--testing/example_scripts/unittest/test_unittest_asyncio.py9
-rw-r--r--testing/example_scripts/unittest/test_unittest_asynctest.py22
-rw-r--r--testing/test_unittest.py11
-rw-r--r--tox.ini5
9 files changed, 70 insertions, 36 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 80317f1c1..722835583 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -70,7 +70,7 @@ jobs:
- name: "windows-py38"
python: "3.8"
os: windows-latest
- tox_env: "py38-twisted"
+ tox_env: "py38-unittestextras"
use_coverage: true
- name: "ubuntu-py35"
diff --git a/changelog/7110.bugfix.rst b/changelog/7110.bugfix.rst
new file mode 100644
index 000000000..935f6ea3c
--- /dev/null
+++ b/changelog/7110.bugfix.rst
@@ -0,0 +1 @@
+Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again.
diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py
index 8aff8d57d..cf051182f 100644
--- a/src/_pytest/compat.py
+++ b/src/_pytest/compat.py
@@ -93,6 +93,13 @@ def iscoroutinefunction(func: object) -> bool:
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
+def is_async_function(func: object) -> bool:
+ """Return True if the given function seems to be an async function or async generator"""
+ return iscoroutinefunction(func) or (
+ sys.version_info >= (3, 6) and inspect.isasyncgenfunction(func)
+ )
+
+
def getlocation(function, curdir=None) -> str:
function = get_real_func(function)
fn = py.path.local(inspect.getfile(function))
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index 2b9bf4f5b..e1bd62f0b 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -34,8 +34,8 @@ from _pytest.compat import get_default_arg_names
from _pytest.compat import get_real_func
from _pytest.compat import getimfunc
from _pytest.compat import getlocation
+from _pytest.compat import is_async_function
from _pytest.compat import is_generator
-from _pytest.compat import iscoroutinefunction
from _pytest.compat import NOTSET
from _pytest.compat import REGEX_TYPE
from _pytest.compat import safe_getattr
@@ -159,7 +159,7 @@ def pytest_configure(config):
)
-def async_warn(nodeid: str) -> None:
+def async_warn_and_skip(nodeid: str) -> None:
msg = "async def 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"
@@ -175,33 +175,13 @@ def async_warn(nodeid: str) -> None:
@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem: "Function"):
testfunction = pyfuncitem.obj
-
- try:
- # ignoring type as the import is invalid in py37 and mypy thinks its a error
- from unittest import IsolatedAsyncioTestCase # type: ignore
- except ImportError:
- async_ok_in_stdlib = False
- else:
- async_ok_in_stdlib = isinstance(
- getattr(testfunction, "__self__", None), IsolatedAsyncioTestCase
- )
-
- if (
- iscoroutinefunction(testfunction)
- or (sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction))
- ) and not async_ok_in_stdlib:
- async_warn(pyfuncitem.nodeid)
+ if is_async_function(testfunction):
+ async_warn_and_skip(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__"):
- if async_ok_in_stdlib:
- # todo: investigate moving this to the unittest plugin
- # by a test call result hook
- testcase = testfunction.__self__
- testcase._callMaybeAsync(lambda: result)
- else:
- async_warn(pyfuncitem.nodeid)
+ async_warn_and_skip(pyfuncitem.nodeid)
return True
diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py
index 2047876e5..e461248b7 100644
--- a/src/_pytest/unittest.py
+++ b/src/_pytest/unittest.py
@@ -6,6 +6,7 @@ import traceback
import _pytest._code
import pytest
from _pytest.compat import getimfunc
+from _pytest.compat import is_async_function
from _pytest.config import hookimpl
from _pytest.outcomes import exit
from _pytest.outcomes import fail
@@ -227,13 +228,17 @@ class TestCaseFunction(Function):
self._needs_explicit_tearDown = True
raise _GetOutOf_testPartExecutor(exc)
- setattr(self._testcase, self._testcase._testMethodName, wrapped_testMethod)
- try:
- self._testcase(result=self)
- except _GetOutOf_testPartExecutor as exc:
- raise exc.args[0] from exc.args[0]
- finally:
- delattr(self._testcase, self._testcase._testMethodName)
+ # let the unittest framework handle async functions
+ if is_async_function(self.obj):
+ self._testcase(self)
+ else:
+ setattr(self._testcase, self._testcase._testMethodName, wrapped_testMethod)
+ try:
+ self._testcase(result=self)
+ except _GetOutOf_testPartExecutor as exc:
+ raise exc.args[0] from exc.args[0]
+ finally:
+ delattr(self._testcase, self._testcase._testMethodName)
def _prunetraceback(self, excinfo):
Function._prunetraceback(self, excinfo)
diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py
index 16eec1026..76eebf74a 100644
--- a/testing/example_scripts/unittest/test_unittest_asyncio.py
+++ b/testing/example_scripts/unittest/test_unittest_asyncio.py
@@ -1,7 +1,13 @@
from unittest import IsolatedAsyncioTestCase # type: ignore
+teardowns = []
+
+
class AsyncArguments(IsolatedAsyncioTestCase):
+ async def asyncTearDown(self):
+ teardowns.append(None)
+
async def test_something_async(self):
async def addition(x, y):
return x + y
@@ -13,3 +19,6 @@ class AsyncArguments(IsolatedAsyncioTestCase):
return x + y
self.assertEqual(await addition(2, 2), 3)
+
+ def test_teardowns(self):
+ assert len(teardowns) == 2
diff --git a/testing/example_scripts/unittest/test_unittest_asynctest.py b/testing/example_scripts/unittest/test_unittest_asynctest.py
new file mode 100644
index 000000000..bddbe250a
--- /dev/null
+++ b/testing/example_scripts/unittest/test_unittest_asynctest.py
@@ -0,0 +1,22 @@
+"""Issue #7110"""
+import asyncio
+
+import asynctest
+
+
+teardowns = []
+
+
+class Test(asynctest.TestCase):
+ async def tearDown(self):
+ teardowns.append(None)
+
+ async def test_error(self):
+ await asyncio.sleep(0)
+ self.fail("failing on purpose")
+
+ async def test_ok(self):
+ await asyncio.sleep(0)
+
+ def test_teardowns(self):
+ assert len(teardowns) == 2
diff --git a/testing/test_unittest.py b/testing/test_unittest.py
index de51f7bd1..a026dc3f6 100644
--- a/testing/test_unittest.py
+++ b/testing/test_unittest.py
@@ -1136,4 +1136,13 @@ def test_async_support(testdir):
testdir.copy_example("unittest/test_unittest_asyncio.py")
reprec = testdir.inline_run()
- reprec.assertoutcome(failed=1, passed=1)
+ reprec.assertoutcome(failed=1, passed=2)
+
+
+def test_asynctest_support(testdir):
+ """Check asynctest support (#7110)"""
+ pytest.importorskip("asynctest")
+
+ testdir.copy_example("unittest/test_unittest_asynctest.py")
+ reprec = testdir.inline_run()
+ reprec.assertoutcome(failed=1, passed=2)
diff --git a/tox.ini b/tox.ini
index 3a280abb7..8f23e3cf9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,7 +10,7 @@ envlist =
py37
py38
pypy3
- py37-{pexpect,xdist,twisted,numpy,pluggymaster}
+ py37-{pexpect,xdist,unittestextras,numpy,pluggymaster}
doctesting
py37-freeze
docs
@@ -49,7 +49,8 @@ deps =
pexpect: pexpect
pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master
pygments
- twisted: twisted
+ unittestextras: twisted
+ unittestextras: asynctest
xdist: pytest-xdist>=1.13
{env:_PYTEST_TOX_EXTRA_DEP:}