Issue a warning for async gen functions
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
This commit is contained in:
		
							parent
							
								
									a295a3ddaf
								
							
						
					
					
						commit
						2f1b192fe6
					
				| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					Skip async generator test functions, and update the warning message to refer to ``async def`` functions.
 | 
				
			||||||
| 
						 | 
					@ -40,14 +40,16 @@ def is_generator(func):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def iscoroutinefunction(func):
 | 
					def iscoroutinefunction(func):
 | 
				
			||||||
    """Return True if func is a decorated coroutine function.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
 | 
					 | 
				
			||||||
    which in turns also initializes the "logging" module as side-effect (see issue #8).
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return getattr(func, "_is_coroutine", False) or (
 | 
					    Return True if func is a coroutine function (a function defined with async
 | 
				
			||||||
        hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
 | 
					    def syntax, and doesn't contain yield), or a function decorated with
 | 
				
			||||||
    )
 | 
					    @asyncio.coroutine.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Note: copied and modified from Python 3.5's builtin couroutines.py to avoid
 | 
				
			||||||
 | 
					    importing asyncio directly, which in turns also initializes the "logging"
 | 
				
			||||||
 | 
					    module as a side-effect (see issue #8).
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getlocation(function, curdir):
 | 
					def getlocation(function, curdir):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ from _pytest.compat import getfslineno
 | 
				
			||||||
from _pytest.compat import getimfunc
 | 
					from _pytest.compat import getimfunc
 | 
				
			||||||
from _pytest.compat import getlocation
 | 
					from _pytest.compat import getlocation
 | 
				
			||||||
from _pytest.compat import is_generator
 | 
					from _pytest.compat import is_generator
 | 
				
			||||||
 | 
					from _pytest.compat import iscoroutinefunction
 | 
				
			||||||
from _pytest.compat import NOTSET
 | 
					from _pytest.compat import NOTSET
 | 
				
			||||||
from _pytest.compat import REGEX_TYPE
 | 
					from _pytest.compat import REGEX_TYPE
 | 
				
			||||||
from _pytest.compat import safe_getattr
 | 
					from _pytest.compat import safe_getattr
 | 
				
			||||||
| 
						 | 
					@ -151,15 +152,16 @@ def pytest_configure(config):
 | 
				
			||||||
@hookimpl(trylast=True)
 | 
					@hookimpl(trylast=True)
 | 
				
			||||||
def pytest_pyfunc_call(pyfuncitem):
 | 
					def pytest_pyfunc_call(pyfuncitem):
 | 
				
			||||||
    testfunction = pyfuncitem.obj
 | 
					    testfunction = pyfuncitem.obj
 | 
				
			||||||
    iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None)
 | 
					    if iscoroutinefunction(testfunction) or (
 | 
				
			||||||
    if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
 | 
					        sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction)
 | 
				
			||||||
        msg = "Coroutine functions are not natively supported and have been skipped.\n"
 | 
					    ):
 | 
				
			||||||
 | 
					        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"
 | 
					        msg += "You need to install a suitable plugin for your async framework, for example:\n"
 | 
				
			||||||
        msg += "  - pytest-asyncio\n"
 | 
					        msg += "  - pytest-asyncio\n"
 | 
				
			||||||
        msg += "  - pytest-trio\n"
 | 
					        msg += "  - pytest-trio\n"
 | 
				
			||||||
        msg += "  - pytest-tornasync"
 | 
					        msg += "  - pytest-tornasync"
 | 
				
			||||||
        warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid)))
 | 
					        warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid)))
 | 
				
			||||||
        skip(msg="coroutine function and no async plugin installed (see warnings)")
 | 
					        skip(msg="async def function and no async plugin installed (see warnings)")
 | 
				
			||||||
    funcargs = pyfuncitem.funcargs
 | 
					    funcargs = pyfuncitem.funcargs
 | 
				
			||||||
    testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
 | 
					    testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
 | 
				
			||||||
    testfunction(**testargs)
 | 
					    testfunction(**testargs)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1199,11 +1199,39 @@ def test_warn_on_async_function(testdir):
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            "test_async.py::test_1",
 | 
					            "test_async.py::test_1",
 | 
				
			||||||
            "test_async.py::test_2",
 | 
					            "test_async.py::test_2",
 | 
				
			||||||
            "*Coroutine functions are not natively supported*",
 | 
					            "*async def functions are not natively supported*",
 | 
				
			||||||
            "*2 skipped, 2 warnings in*",
 | 
					            "*2 skipped, 2 warnings in*",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    # ensure our warning message appears only once
 | 
					    # ensure our warning message appears only once
 | 
				
			||||||
    assert (
 | 
					    assert (
 | 
				
			||||||
        result.stdout.str().count("Coroutine functions are not natively supported") == 1
 | 
					        result.stdout.str().count("async def functions are not natively supported") == 1
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.filterwarnings("default")
 | 
				
			||||||
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
 | 
					    sys.version_info < (3, 6), reason="async gen syntax available in Python 3.6+"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_warn_on_async_gen_function(testdir):
 | 
				
			||||||
 | 
					    testdir.makepyfile(
 | 
				
			||||||
 | 
					        test_async="""
 | 
				
			||||||
 | 
					        async def test_1():
 | 
				
			||||||
 | 
					            yield
 | 
				
			||||||
 | 
					        async def test_2():
 | 
				
			||||||
 | 
					            yield
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    result = testdir.runpytest()
 | 
				
			||||||
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "test_async.py::test_1",
 | 
				
			||||||
 | 
					            "test_async.py::test_2",
 | 
				
			||||||
 | 
					            "*async def functions are not natively supported*",
 | 
				
			||||||
 | 
					            "*2 skipped, 2 warnings in*",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # ensure our warning message appears only once
 | 
				
			||||||
 | 
					    assert (
 | 
				
			||||||
 | 
					        result.stdout.str().count("async def functions are not natively supported") == 1
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,9 +91,6 @@ def test_is_generator_asyncio(testdir):
 | 
				
			||||||
    result.stdout.fnmatch_lines(["*1 passed*"])
 | 
					    result.stdout.fnmatch_lines(["*1 passed*"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(
 | 
					 | 
				
			||||||
    sys.version_info < (3, 5), reason="async syntax available in Python 3.5+"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
def test_is_generator_async_syntax(testdir):
 | 
					def test_is_generator_async_syntax(testdir):
 | 
				
			||||||
    testdir.makepyfile(
 | 
					    testdir.makepyfile(
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -113,6 +110,29 @@ def test_is_generator_async_syntax(testdir):
 | 
				
			||||||
    result.stdout.fnmatch_lines(["*1 passed*"])
 | 
					    result.stdout.fnmatch_lines(["*1 passed*"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
 | 
					    sys.version_info < (3, 6), reason="async gen syntax available in Python 3.6+"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_is_generator_async_gen_syntax(testdir):
 | 
				
			||||||
 | 
					    testdir.makepyfile(
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        from _pytest.compat import is_generator
 | 
				
			||||||
 | 
					        def test_is_generator_py36():
 | 
				
			||||||
 | 
					            async def foo():
 | 
				
			||||||
 | 
					                yield
 | 
				
			||||||
 | 
					                await foo()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async def bar():
 | 
				
			||||||
 | 
					                yield
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            assert not is_generator(foo)
 | 
				
			||||||
 | 
					            assert not is_generator(bar)
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    result = testdir.runpytest()
 | 
				
			||||||
 | 
					    result.stdout.fnmatch_lines(["*1 passed*"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ErrorsHelper:
 | 
					class ErrorsHelper:
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def raise_exception(self):
 | 
					    def raise_exception(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue