Merge master into features (#5744)

Merge master into features
This commit is contained in:
Bruno Oliveira 2019-08-15 19:12:37 -03:00 committed by GitHub
commit 0f11a7a73d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 219 additions and 108 deletions

View File

@ -1,6 +1,6 @@
exclude: doc/en/example/py2py3/test_py2.py exclude: doc/en/example/py2py3/test_py2.py
repos: repos:
- repo: https://github.com/python/black - repo: https://github.com/psf/black
rev: 19.3b0 rev: 19.3b0
hooks: hooks:
- id: black - id: black

View File

@ -167,7 +167,7 @@ Short version
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed. #. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
#. Target ``master`` for bugfixes and doc changes. #. Target ``master`` for bugfixes and doc changes.
#. Target ``features`` for new features or functionality changes. #. Target ``features`` for new features or functionality changes.
#. Follow **PEP-8** for naming and `black <https://github.com/python/black>`_ for formatting. #. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
#. Tests are run using ``tox``:: #. Tests are run using ``tox``::
tox -e linting,py37 tox -e linting,py37

View File

@ -26,7 +26,7 @@
:target: https://dev.azure.com/pytest-dev/pytest :target: https://dev.azure.com/pytest-dev/pytest
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/python/black :target: https://github.com/psf/black
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg .. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
:target: https://www.codetriage.com/pytest-dev/pytest :target: https://www.codetriage.com/pytest-dev/pytest

View File

@ -0,0 +1 @@
Warnings issued during ``pytest_configure`` are explicitly not treated as errors, even if configured as such, because it otherwise completely breaks pytest.

1
changelog/5669.doc.rst Normal file
View File

@ -0,0 +1 @@
Add docstring for ``Testdir.copy_example``.

View File

@ -0,0 +1 @@
Fix collection of ``staticmethod`` objects defined with ``functools.partial``.

View File

@ -0,0 +1 @@
Skip async generator test functions, and update the warning message to refer to ``async def`` functions.

View File

@ -57,7 +57,7 @@ is that you can use print statements for debugging:
def setup_function(function): def setup_function(function):
print("setting up %s" % function) print("setting up", function)
def test_func1(): def test_func1():

View File

@ -177,7 +177,7 @@ class TestRaises:
def test_reinterpret_fails_with_print_for_the_fun_of_it(self): def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
items = [1, 2, 3] items = [1, 2, 3]
print("items is %r" % items) print("items is {!r}".format(items))
a, b = items.pop() a, b = items.pop()
def test_some_error(self): def test_some_error(self):

View File

@ -384,7 +384,7 @@ specifies via named environments:
envnames = [mark.args[0] for mark in item.iter_markers(name="env")] envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
if envnames: if envnames:
if item.config.getoption("-E") not in envnames: if item.config.getoption("-E") not in envnames:
pytest.skip("test requires env in %r" % envnames) pytest.skip("test requires env in {!r}".format(envnames))
A test file using this local plugin: A test file using this local plugin:
@ -578,7 +578,7 @@ for your particular platform, you could use the following plugin:
supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers()) supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
plat = sys.platform plat = sys.platform
if supported_platforms and plat not in supported_platforms: if supported_platforms and plat not in supported_platforms:
pytest.skip("cannot run on platform %s" % (plat)) pytest.skip("cannot run on platform {}".format(plat))
then tests will be skipped if they were specified for a different platform. then tests will be skipped if they were specified for a different platform.
Let's do a little test file to show how this looks like: Let's do a little test file to show how this looks like:

View File

@ -69,4 +69,4 @@ class Python:
@pytest.mark.parametrize("obj", [42, {}, {1: 3}]) @pytest.mark.parametrize("obj", [42, {}, {1: 3}])
def test_basic_objects(python1, python2, obj): def test_basic_objects(python1, python2, obj):
python1.dumps(obj) python1.dumps(obj)
python2.load_and_is_true("obj == %s" % obj) python2.load_and_is_true("obj == {}".format(obj))

View File

@ -33,13 +33,13 @@ class YamlItem(pytest.Item):
return "\n".join( return "\n".join(
[ [
"usecase execution failed", "usecase execution failed",
" spec failed: %r: %r" % excinfo.value.args[1:3], " spec failed: {1!r}: {2!r}".format(*excinfo.value.args),
" no further details known at this point.", " no further details known at this point.",
] ]
) )
def reportinfo(self): def reportinfo(self):
return self.fspath, 0, "usecase: %s" % self.name return self.fspath, 0, "usecase: {}".format(self.name)
class YamlException(Exception): class YamlException(Exception):

View File

@ -434,7 +434,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_reinterpret_fails_with_print_for_the_fun_of_it(self): def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
items = [1, 2, 3] items = [1, 2, 3]
print("items is %r" % items) print("items is {!r}".format(items))
> a, b = items.pop() > a, b = items.pop()
E TypeError: 'int' object is not iterable E TypeError: 'int' object is not iterable

View File

@ -478,7 +478,7 @@ an ``incremental`` marker which is to be used on classes:
if "incremental" in item.keywords: if "incremental" in item.keywords:
previousfailed = getattr(item.parent, "_previousfailed", None) previousfailed = getattr(item.parent, "_previousfailed", None)
if previousfailed is not None: if previousfailed is not None:
pytest.xfail("previous test failed (%s)" % previousfailed.name) pytest.xfail("previous test failed ({})".format(previousfailed.name))
These two hook implementations work together to abort incremental-marked These two hook implementations work together to abort incremental-marked
tests in a class. Here is a test module example: tests in a class. Here is a test module example:
@ -684,7 +684,7 @@ case we just write some information out to a ``failures`` file:
with open("failures", mode) as f: with open("failures", mode) as f:
# let's also access a fixture for the fun of it # let's also access a fixture for the fun of it
if "tmpdir" in item.fixturenames: if "tmpdir" in item.fixturenames:
extra = " (%s)" % item.funcargs["tmpdir"] extra = " ({})".format(item.funcargs["tmpdir"])
else: else:
extra = "" extra = ""

View File

@ -629,7 +629,7 @@ through the special :py:class:`request <FixtureRequest>` object:
def smtp_connection(request): def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection yield smtp_connection
print("finalizing %s" % smtp_connection) print("finalizing {}".format(smtp_connection))
smtp_connection.close() smtp_connection.close()
The main change is the declaration of ``params`` with The main change is the declaration of ``params`` with
@ -902,25 +902,25 @@ to show the setup/teardown flow:
@pytest.fixture(scope="module", params=["mod1", "mod2"]) @pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request): def modarg(request):
param = request.param param = request.param
print(" SETUP modarg %s" % param) print(" SETUP modarg", param)
yield param yield param
print(" TEARDOWN modarg %s" % param) print(" TEARDOWN modarg", param)
@pytest.fixture(scope="function", params=[1, 2]) @pytest.fixture(scope="function", params=[1, 2])
def otherarg(request): def otherarg(request):
param = request.param param = request.param
print(" SETUP otherarg %s" % param) print(" SETUP otherarg", param)
yield param yield param
print(" TEARDOWN otherarg %s" % param) print(" TEARDOWN otherarg", param)
def test_0(otherarg): def test_0(otherarg):
print(" RUN test0 with otherarg %s" % otherarg) print(" RUN test0 with otherarg", otherarg)
def test_1(modarg): def test_1(modarg):
print(" RUN test1 with modarg %s" % modarg) print(" RUN test1 with modarg", modarg)
def test_2(otherarg, modarg): def test_2(otherarg, modarg):

View File

@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash .. code-block:: bash
$ pytest --version $ pytest --version
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.x/site-packages/pytest.py
.. _`simpletest`: .. _`simpletest`:

View File

@ -27,6 +27,8 @@ pytest.skip
.. autofunction:: _pytest.outcomes.skip(msg, [allow_module_level=False]) .. autofunction:: _pytest.outcomes.skip(msg, [allow_module_level=False])
.. _`pytest.importorskip ref`:
pytest.importorskip pytest.importorskip
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -179,16 +179,15 @@ information.
Skipping on a missing import dependency Skipping on a missing import dependency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can use the following helper at module level You can skip tests on a missing import by using :ref:`pytest.importorskip ref`
or within a test or test setup function: at module level, within a test, or test setup function.
.. code-block:: python .. code-block:: python
docutils = pytest.importorskip("docutils") docutils = pytest.importorskip("docutils")
If ``docutils`` cannot be imported here, this will lead to a If ``docutils`` cannot be imported here, this will lead to a skip outcome of
skip outcome of the test. You can also skip based on the the test. You can also skip based on the version number of a library:
version number of a library:
.. code-block:: python .. code-block:: python

View File

@ -46,14 +46,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=None): def getlocation(function, curdir=None):
@ -84,7 +86,7 @@ def num_mock_patch_args(function):
) )
def getfuncargnames(function, is_method=False, cls=None): def getfuncargnames(function, *, name: str = "", is_method=False, cls=None):
"""Returns the names of a function's mandatory arguments. """Returns the names of a function's mandatory arguments.
This should return the names of all function arguments that: This should return the names of all function arguments that:
@ -97,11 +99,12 @@ def getfuncargnames(function, is_method=False, cls=None):
be treated as a bound method even though it's not unless, only in be treated as a bound method even though it's not unless, only in
the case of cls, the function is a static method. the case of cls, the function is a static method.
The name parameter should be the original name in which the function was collected.
@RonnyPfannschmidt: This function should be refactored when we @RonnyPfannschmidt: This function should be refactored when we
revisit fixtures. The fixture mechanism should ask the node for revisit fixtures. The fixture mechanism should ask the node for
the fixture names, and not try to obtain directly from the the fixture names, and not try to obtain directly from the
function object well after collection has occurred. function object well after collection has occurred.
""" """
# The parameters attribute of a Signature object contains an # The parameters attribute of a Signature object contains an
# ordered mapping of parameter names to Parameter instances. This # ordered mapping of parameter names to Parameter instances. This
@ -124,11 +127,14 @@ def getfuncargnames(function, is_method=False, cls=None):
) )
and p.default is Parameter.empty and p.default is Parameter.empty
) )
if not name:
name = function.__name__
# If this function should be treated as a bound method even though # If this function should be treated as a bound method even though
# it's passed as an unbound method or function, remove the first # it's passed as an unbound method or function, remove the first
# parameter name. # parameter name.
if is_method or ( if is_method or (
cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod) cls and not isinstance(cls.__dict__.get(name, None), staticmethod)
): ):
arg_names = arg_names[1:] arg_names = arg_names[1:]
# Remove any names that will be replaced with mocks. # Remove any names that will be replaced with mocks.
@ -251,7 +257,7 @@ def get_real_method(obj, holder):
try: try:
is_method = hasattr(obj, "__func__") is_method = hasattr(obj, "__func__")
obj = get_real_func(obj) obj = get_real_func(obj)
except Exception: except Exception: # pragma: no cover
return obj return obj
if is_method and hasattr(obj, "__get__") and callable(obj.__get__): if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
obj = obj.__get__(holder) obj = obj.__get__(holder)

View File

@ -695,7 +695,9 @@ class Config:
def _do_configure(self): def _do_configure(self):
assert not self._configured assert not self._configured
self._configured = True self._configured = True
self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) with warnings.catch_warnings():
warnings.simplefilter("default")
self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
def _ensure_unconfigure(self): def _ensure_unconfigure(self):
if self._configured: if self._configured:

View File

@ -818,7 +818,7 @@ class FixtureDef:
where=baseid, where=baseid,
) )
self.params = params self.params = params
self.argnames = getfuncargnames(func, is_method=unittest) self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
self.unittest = unittest self.unittest = unittest
self.ids = ids self.ids = ids
self._finalizers = [] self._finalizers = []
@ -1142,7 +1142,7 @@ class FixtureManager:
def getfixtureinfo(self, node, func, cls, funcargs=True): def getfixtureinfo(self, node, func, cls, funcargs=True):
if funcargs and not getattr(node, "nofuncargs", False): if funcargs and not getattr(node, "nofuncargs", False):
argnames = getfuncargnames(func, cls=cls) argnames = getfuncargnames(func, name=node.name, cls=cls)
else: else:
argnames = () argnames = ()

View File

@ -157,14 +157,21 @@ xfail.Exception = XFailed # type: ignore
def importorskip( def importorskip(
modname: str, minversion: Optional[str] = None, reason: Optional[str] = None modname: str, minversion: Optional[str] = None, reason: Optional[str] = None
) -> Any: ) -> Any:
"""Imports and returns the requested module ``modname``, or skip the current test """Imports and returns the requested module ``modname``, or skip the
if the module cannot be imported. current test if the module cannot be imported.
:param str modname: the name of the module to import :param str modname: the name of the module to import
:param str minversion: if given, the imported module ``__version__`` attribute must be :param str minversion: if given, the imported module's ``__version__``
at least this minimal version, otherwise the test is still skipped. attribute must be at least this minimal version, otherwise the test is
:param str reason: if given, this reason is shown as the message when the module still skipped.
cannot be imported. :param str reason: if given, this reason is shown as the message when the
module cannot be imported.
:returns: The imported module. This should be assigned to its canonical
name.
Example::
docutils = pytest.importorskip("docutils")
""" """
import warnings import warnings

View File

@ -630,6 +630,12 @@ class Testdir:
return p return p
def copy_example(self, name=None): def copy_example(self, name=None):
"""Copy file from project's directory into the testdir.
:param str name: The name of the file to copy.
:return: path to the copied directory (inside ``self.tmpdir``).
"""
import warnings import warnings
from _pytest.warning_types import PYTESTER_COPY_EXAMPLE from _pytest.warning_types import PYTESTER_COPY_EXAMPLE

View File

@ -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
@ -150,19 +151,25 @@ def pytest_configure(config):
@hookimpl(trylast=True) @hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj def async_warn():
iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None) msg = "async def functions are not natively supported and have been skipped.\n"
if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
msg = "Coroutine 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)")
testfunction = pyfuncitem.obj
if iscoroutinefunction(testfunction) or (
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction)
):
async_warn()
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) result = testfunction(**testargs)
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
async_warn()
return True return True

View File

@ -1189,6 +1189,8 @@ def test_warn_on_async_function(testdir):
pass pass
async def test_2(): async def test_2():
pass pass
def test_3():
return test_2()
""" """
) )
result = testdir.runpytest() result = testdir.runpytest()
@ -1196,11 +1198,43 @@ 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*", "test_async.py::test_3",
"*2 skipped, 2 warnings in*", "*async def functions are not natively supported*",
"*3 skipped, 3 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
def test_3():
return test_2()
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"test_async.py::test_1",
"test_async.py::test_2",
"test_async.py::test_3",
"*async def functions are not natively supported*",
"*3 skipped, 3 warnings in*",
]
)
# ensure our warning message appears only once
assert (
result.stdout.str().count("async def functions are not natively supported") == 1
) )

View File

@ -1143,52 +1143,6 @@ def test_unorderable_types(testdir):
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_collect_functools_partial(testdir):
"""
Test that collection of functools.partial object works, and arguments
to the wrapped functions are dealt with correctly (see #811).
"""
testdir.makepyfile(
"""
import functools
import pytest
@pytest.fixture
def fix1():
return 'fix1'
@pytest.fixture
def fix2():
return 'fix2'
def check1(i, fix1):
assert i == 2
assert fix1 == 'fix1'
def check2(fix1, i):
assert i == 2
assert fix1 == 'fix1'
def check3(fix1, i, fix2):
assert i == 2
assert fix1 == 'fix1'
assert fix2 == 'fix2'
test_ok_1 = functools.partial(check1, i=2)
test_ok_2 = functools.partial(check1, i=2, fix1='fix1')
test_ok_3 = functools.partial(check1, 2)
test_ok_4 = functools.partial(check2, i=2)
test_ok_5 = functools.partial(check3, i=2)
test_ok_6 = functools.partial(check3, i=2, fix1='fix1')
test_fail_1 = functools.partial(check2, 2)
test_fail_2 = functools.partial(check3, 2)
"""
)
result = testdir.inline_run()
result.assertoutcome(passed=6, failed=2)
@pytest.mark.filterwarnings("default") @pytest.mark.filterwarnings("default")
def test_dont_collect_non_function_callable(testdir): def test_dont_collect_non_function_callable(testdir):
"""Test for issue https://github.com/pytest-dev/pytest/issues/331 """Test for issue https://github.com/pytest-dev/pytest/issues/331

View File

@ -9,7 +9,9 @@ from _pytest.pathlib import Path
from _pytest.pytester import get_public_names from _pytest.pytester import get_public_names
def test_getfuncargnames(): def test_getfuncargnames_functions():
"""Test getfuncargnames for normal functions"""
def f(): def f():
pass pass
@ -30,18 +32,56 @@ def test_getfuncargnames():
assert fixtures.getfuncargnames(j) == ("arg1", "arg2") assert fixtures.getfuncargnames(j) == ("arg1", "arg2")
def test_getfuncargnames_methods():
"""Test getfuncargnames for normal methods"""
class A: class A:
def f(self, arg1, arg2="hello"): def f(self, arg1, arg2="hello"):
pass pass
assert fixtures.getfuncargnames(A().f) == ("arg1",)
def test_getfuncargnames_staticmethod():
"""Test getfuncargnames for staticmethods"""
class A:
@staticmethod @staticmethod
def static(arg1, arg2): def static(arg1, arg2, x=1):
pass pass
assert fixtures.getfuncargnames(A().f) == ("arg1",)
assert fixtures.getfuncargnames(A.static, cls=A) == ("arg1", "arg2") assert fixtures.getfuncargnames(A.static, cls=A) == ("arg1", "arg2")
def test_getfuncargnames_partial():
"""Check getfuncargnames for methods defined with functools.partial (#5701)"""
import functools
def check(arg1, arg2, i):
pass
class T:
test_ok = functools.partial(check, i=2)
values = fixtures.getfuncargnames(T().test_ok, name="test_ok")
assert values == ("arg1", "arg2")
def test_getfuncargnames_staticmethod_partial():
"""Check getfuncargnames for staticmethods defined with functools.partial (#5701)"""
import functools
def check(arg1, arg2, i):
pass
class T:
test_ok = staticmethod(functools.partial(check, i=2))
values = fixtures.getfuncargnames(T().test_ok, name="test_ok")
assert values == ("arg1", "arg2")
@pytest.mark.pytester_example_path("fixtures/fill_fixtures") @pytest.mark.pytester_example_path("fixtures/fill_fixtures")
class TestFillFixtures: class TestFillFixtures:
def test_fillfuncargs_exposed(self): def test_fillfuncargs_exposed(self):

View File

@ -1,4 +1,5 @@
import sys import sys
from functools import partial
from functools import wraps from functools import wraps
import pytest import pytest
@ -72,6 +73,16 @@ def test_get_real_func():
assert get_real_func(wrapped_func2) is wrapped_func assert get_real_func(wrapped_func2) is wrapped_func
def test_get_real_func_partial():
"""Test get_real_func handles partial instances correctly"""
def foo(x):
return x
assert get_real_func(foo) is foo
assert get_real_func(partial(foo)) is foo
def test_is_generator_asyncio(testdir): def test_is_generator_asyncio(testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
@ -91,9 +102,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 +121,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):

View File

@ -55,7 +55,7 @@ class TestPasteCapture:
] ]
) )
def test_non_ascii_paste_text(self, testdir): def test_non_ascii_paste_text(self, testdir, pastebinlist):
"""Make sure that text which contains non-ascii characters is pasted """Make sure that text which contains non-ascii characters is pasted
correctly. See #1219. correctly. See #1219.
""" """
@ -74,6 +74,7 @@ class TestPasteCapture:
"*Sending information to Paste Service*", "*Sending information to Paste Service*",
] ]
) )
assert len(pastebinlist) == 1
class TestPaste: class TestPaste:

View File

@ -622,3 +622,21 @@ def test_group_warnings_by_message(testdir):
warning_code = 'warnings.warn(UserWarning("foo"))' warning_code = 'warnings.warn(UserWarning("foo"))'
assert warning_code in result.stdout.str() assert warning_code in result.stdout.str()
assert result.stdout.str().count(warning_code) == 1 assert result.stdout.str().count(warning_code) == 1
def test_pytest_configure_warning(testdir, recwarn):
"""Issue 5115."""
testdir.makeconftest(
"""
def pytest_configure():
import warnings
warnings.warn("from pytest_configure")
"""
)
result = testdir.runpytest()
assert result.ret == 5
assert "INTERNALERROR" not in result.stderr.str()
warning = recwarn.pop()
assert str(warning.message) == "from pytest_configure"