Merge remote-tracking branch 'upstream/master' into mm

Conflicts:
	src/_pytest/outcomes.py
This commit is contained in:
Bruno Oliveira 2019-08-15 10:03:52 -03:00
commit d7f082519a
30 changed files with 219 additions and 108 deletions

View File

@ -1,6 +1,6 @@
exclude: doc/en/example/py2py3/test_py2.py
repos:
- repo: https://github.com/python/black
- repo: https://github.com/psf/black
rev: 19.3b0
hooks:
- 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.
#. Target ``master`` for bugfixes and doc 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``::
tox -e linting,py37

View File

@ -26,7 +26,7 @@
:target: https://dev.azure.com/pytest-dev/pytest
.. 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
: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):
print("setting up %s" % function)
print("setting up", function)
def test_func1():

View File

@ -177,7 +177,7 @@ class TestRaises:
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
items = [1, 2, 3]
print("items is %r" % items)
print("items is {!r}".format(items))
a, b = items.pop()
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")]
if 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:
@ -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())
plat = sys.platform
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.
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}])
def test_basic_objects(python1, python2, 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(
[
"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.",
]
)
def reportinfo(self):
return self.fspath, 0, "usecase: %s" % self.name
return self.fspath, 0, "usecase: {}".format(self.name)
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):
items = [1, 2, 3]
print("items is %r" % items)
print("items is {!r}".format(items))
> a, b = items.pop()
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:
previousfailed = getattr(item.parent, "_previousfailed", 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
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:
# let's also access a fixture for the fun of it
if "tmpdir" in item.fixturenames:
extra = " (%s)" % item.funcargs["tmpdir"]
extra = " ({})".format(item.funcargs["tmpdir"])
else:
extra = ""

View File

@ -629,7 +629,7 @@ through the special :py:class:`request <FixtureRequest>` object:
def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection
print("finalizing %s" % smtp_connection)
print("finalizing {}".format(smtp_connection))
smtp_connection.close()
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"])
def modarg(request):
param = request.param
print(" SETUP modarg %s" % param)
print(" SETUP modarg", param)
yield param
print(" TEARDOWN modarg %s" % param)
print(" TEARDOWN modarg", param)
@pytest.fixture(scope="function", params=[1, 2])
def otherarg(request):
param = request.param
print(" SETUP otherarg %s" % param)
print(" SETUP otherarg", param)
yield param
print(" TEARDOWN otherarg %s" % param)
print(" TEARDOWN otherarg", param)
def test_0(otherarg):
print(" RUN test0 with otherarg %s" % otherarg)
print(" RUN test0 with otherarg", otherarg)
def test_1(modarg):
print(" RUN test1 with modarg %s" % modarg)
print(" RUN test1 with modarg", modarg)
def test_2(otherarg, modarg):

View File

@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash
$ 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`:

View File

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

View File

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

View File

@ -46,14 +46,16 @@ def is_generator(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)
)
Return True if func is a coroutine function (a function defined with async
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):
@ -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.
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
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
revisit fixtures. The fixture mechanism should ask the node for
the fixture names, and not try to obtain directly from the
function object well after collection has occurred.
"""
# The parameters attribute of a Signature object contains an
# 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
)
if not name:
name = function.__name__
# If this function should be treated as a bound method even though
# it's passed as an unbound method or function, remove the first
# parameter name.
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:]
# Remove any names that will be replaced with mocks.
@ -251,7 +257,7 @@ def get_real_method(obj, holder):
try:
is_method = hasattr(obj, "__func__")
obj = get_real_func(obj)
except Exception:
except Exception: # pragma: no cover
return obj
if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
obj = obj.__get__(holder)

View File

@ -695,7 +695,9 @@ class Config:
def _do_configure(self):
assert not self._configured
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):
if self._configured:

View File

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

View File

@ -157,14 +157,21 @@ xfail.Exception = XFailed # type: ignore
def importorskip(
modname: str, minversion: Optional[str] = None, reason: Optional[str] = None
) -> Any:
"""Imports and returns the requested module ``modname``, or skip the current test
if the module cannot be imported.
"""Imports and returns the requested module ``modname``, or skip the
current test if the module cannot be imported.
:param str modname: the name of the module to import
:param str minversion: if given, the imported module ``__version__`` attribute must be
at least this minimal version, otherwise the test is still skipped.
:param str reason: if given, this reason is shown as the message when the module
cannot be imported.
:param str minversion: if given, the imported module ``__version__``
attribute must be at least this minimal version, otherwise the test is
still skipped.
: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

View File

@ -630,6 +630,12 @@ class Testdir:
return p
def copy_example(self, name=None):
"""Copy file from project's directory into the testdir.
:param str name: The name of the file for copy.
:return: path to the copied directory (inside ``self.tmpdir``).
"""
import warnings
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 getlocation
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
@ -150,19 +151,25 @@ def pytest_configure(config):
@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj
iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None)
if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
msg = "Coroutine functions are not natively supported and have been skipped.\n"
def async_warn():
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 += " - pytest-asyncio\n"
msg += " - pytest-trio\n"
msg += " - pytest-tornasync"
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
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

View File

@ -1189,6 +1189,8 @@ def test_warn_on_async_function(testdir):
pass
async def test_2():
pass
def test_3():
return test_2()
"""
)
result = testdir.runpytest()
@ -1196,11 +1198,43 @@ def test_warn_on_async_function(testdir):
[
"test_async.py::test_1",
"test_async.py::test_2",
"*Coroutine functions are not natively supported*",
"*2 skipped, 2 warnings in*",
"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("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
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")
def test_dont_collect_non_function_callable(testdir):
"""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
def test_getfuncargnames():
def test_getfuncargnames_functions():
"""Test getfuncargnames for normal functions"""
def f():
pass
@ -30,18 +32,56 @@ def test_getfuncargnames():
assert fixtures.getfuncargnames(j) == ("arg1", "arg2")
def test_getfuncargnames_methods():
"""Test getfuncargnames for normal methods"""
class A:
def f(self, arg1, arg2="hello"):
pass
assert fixtures.getfuncargnames(A().f) == ("arg1",)
def test_getfuncargnames_staticmethod():
"""Test getfuncargnames for staticmethods"""
class A:
@staticmethod
def static(arg1, arg2):
def static(arg1, arg2, x=1):
pass
assert fixtures.getfuncargnames(A().f) == ("arg1",)
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")
class TestFillFixtures:
def test_fillfuncargs_exposed(self):

View File

@ -1,4 +1,5 @@
import sys
from functools import partial
from functools import wraps
import pytest
@ -72,6 +73,16 @@ def test_get_real_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):
testdir.makepyfile(
"""
@ -91,9 +102,6 @@ def test_is_generator_asyncio(testdir):
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(
"""
@ -113,6 +121,29 @@ def test_is_generator_async_syntax(testdir):
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:
@property
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
correctly. See #1219.
"""
@ -74,6 +74,7 @@ class TestPasteCapture:
"*Sending information to Paste Service*",
]
)
assert len(pastebinlist) == 1
class TestPaste:

View File

@ -622,3 +622,21 @@ def test_group_warnings_by_message(testdir):
warning_code = 'warnings.warn(UserWarning("foo"))'
assert warning_code in result.stdout.str()
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"