Skip to content

Commit

Permalink
Merge pull request #2130 from malinoff/fix-2129
Browse files Browse the repository at this point in the history
Use inspect to properly detect generators. Fixes #2129
  • Loading branch information
nicoddemus committed Dec 28, 2016
2 parents b4295aa + caee5ce commit 718f0b0
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 6 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

*

*
* pytest no longer recognizes coroutine functions as yield tests (`#2129`_).
Thanks to `@malinoff`_ for the PR.

* Improve error message when pytest.warns fails (`#2150`_). The type(s) of the
expected warnings and the list of caught warnings is added to the
Expand All @@ -21,6 +22,9 @@
.. _#2150: https://github.com/pytest-dev/pytest/issues/2150
.. _#2148: https://github.com/pytest-dev/pytest/issues/2148

.. _@malinoff: https://github.com/malinoff

.. _#2129: https://github.com/pytest-dev/pytest/issues/2129

3.0.5 (2016-12-05)
==================
Expand Down
18 changes: 13 additions & 5 deletions _pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Only available in Python 3.4+ or as a backport
enum = None


_PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3

Expand All @@ -42,11 +43,18 @@ def _format_args(func):


def is_generator(func):
try:
return _pytest._code.getrawcode(func).co_flags & 32 # generator function
except AttributeError: # builtin functions have no bytecode
# assume them to not be generators
return False
genfunc = inspect.isgeneratorfunction(func)
return genfunc and not 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
(hasattr(inspect, 'iscoroutinefunction') and inspect.iscoroutinefunction(func)))


def getlocation(function, curdir):
Expand Down
50 changes: 50 additions & 0 deletions testing/test_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import sys

import pytest
from _pytest.compat import is_generator


def test_is_generator():
def zap():
yield

def foo():
pass

assert is_generator(zap)
assert not is_generator(foo)


@pytest.mark.skipif(sys.version_info < (3, 4), reason='asyncio available in Python 3.4+')
def test_is_generator_asyncio(testdir):
testdir.makepyfile("""
from _pytest.compat import is_generator
import asyncio
@asyncio.coroutine
def baz():
yield from [1,2,3]
def test_is_generator_asyncio():
assert not is_generator(baz)
""")
# avoid importing asyncio into pytest's own process, which in turn imports logging (#8)
result = testdir.runpytest_subprocess()
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):
testdir.makepyfile("""
from _pytest.compat import is_generator
def test_is_generator_py35():
async def foo():
await foo()
async def bar():
pass
assert not is_generator(foo)
assert not is_generator(bar)
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['*1 passed*'])

0 comments on commit 718f0b0

Please sign in to comment.