From 451aef65ac5a3b78441f188995978f9ad5eea812 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 14 Mar 2020 18:50:43 +0100 Subject: [PATCH] prepare tests and disable warnings for asyncio unittest cases shoehorn unittest async results into python test result interpretation changelog --- changelog/6924.bugfix.rst | 1 + src/_pytest/python.py | 26 ++++++++++++++++--- testing/example_scripts/pytest.ini | 2 ++ .../unittest/test_unittest_asyncio.py | 15 +++++++++++ testing/test_unittest.py | 8 ++++++ 5 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 changelog/6924.bugfix.rst create mode 100644 testing/example_scripts/pytest.ini create mode 100644 testing/example_scripts/unittest/test_unittest_asyncio.py diff --git a/changelog/6924.bugfix.rst b/changelog/6924.bugfix.rst new file mode 100644 index 00000000000..7283370a0e1 --- /dev/null +++ b/changelog/6924.bugfix.rst @@ -0,0 +1 @@ +Ensure a ``unittest.IsolatedAsyncioTestCase`` is actually awaited. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e260761794d..1f6a095c4e1 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -175,15 +175,33 @@ def async_warn(nodeid: str) -> None: @hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem: "Function"): testfunction = pyfuncitem.obj - if iscoroutinefunction(testfunction) or ( - sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction) - ): + + 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) 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(pyfuncitem.nodeid) + 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) return True diff --git a/testing/example_scripts/pytest.ini b/testing/example_scripts/pytest.ini new file mode 100644 index 00000000000..ec5fe0e83a7 --- /dev/null +++ b/testing/example_scripts/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +# dummy pytest.ini to ease direct running of example scripts diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py new file mode 100644 index 00000000000..16eec1026ff --- /dev/null +++ b/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -0,0 +1,15 @@ +from unittest import IsolatedAsyncioTestCase # type: ignore + + +class AsyncArguments(IsolatedAsyncioTestCase): + async def test_something_async(self): + async def addition(x, y): + return x + y + + self.assertEqual(await addition(2, 2), 4) + + async def test_something_async_fails(self): + async def addition(x, y): + return x + y + + self.assertEqual(await addition(2, 2), 3) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index c5fc20239b1..de51f7bd104 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1129,3 +1129,11 @@ def test(self): result = testdir.runpytest("--trace", str(p1)) assert len(calls) == 2 assert result.ret == 0 + + +def test_async_support(testdir): + pytest.importorskip("unittest.async_case") + + testdir.copy_example("unittest/test_unittest_asyncio.py") + reprec = testdir.inline_run() + reprec.assertoutcome(failed=1, passed=1)