Skip to content

Commit

Permalink
Merge master into features (#5744)
Browse files Browse the repository at this point in the history
Merge master into features
  • Loading branch information
nicoddemus committed Aug 15, 2019
2 parents 0822a1e + 1049a38 commit 0f11a7a
Show file tree
Hide file tree
Showing 30 changed files with 218 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions changelog/5115.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions changelog/5669.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add docstring for ``Testdir.copy_example``.
1 change: 1 addition & 0 deletions changelog/5701.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix collection of ``staticmethod`` objects defined with ``functools.partial``.
1 change: 1 addition & 0 deletions changelog/5734.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Skip async generator test functions, and update the warning message to refer to ``async def`` functions.
2 changes: 1 addition & 1 deletion doc/en/capture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion doc/en/example/assertion/failure_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def test_tupleerror(self):

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):
Expand Down
4 changes: 2 additions & 2 deletions doc/en/example/markers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion doc/en/example/multipython.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ def load_and_is_true(self, expression):
@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))
4 changes: 2 additions & 2 deletions doc/en/example/nonpython/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ def repr_failure(self, excinfo):
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):
Expand Down
2 changes: 1 addition & 1 deletion doc/en/example/reportingdemo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions doc/en/example/simple.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 = ""
Expand Down
14 changes: 7 additions & 7 deletions doc/en/fixture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion doc/en/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down
2 changes: 2 additions & 0 deletions doc/en/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pytest.skip

.. autofunction:: _pytest.outcomes.skip(msg, [allow_module_level=False])

.. _`pytest.importorskip ref`:

pytest.importorskip
~~~~~~~~~~~~~~~~~~~

Expand Down
9 changes: 4 additions & 5 deletions doc/en/skipping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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
Expand Down
26 changes: 16 additions & 10 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ def is_generator(func):


def iscoroutinefunction(func):
"""Return True if func is a decorated coroutine function.
"""
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 import asyncio directly,
which in turns also initializes the "logging" module as side-effect (see issue #8).
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 getattr(func, "_is_coroutine", False) or (
hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
)
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)


def getlocation(function, curdir=None):
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,9 @@ def add_cleanup(self, func):
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:
Expand Down
4 changes: 2 additions & 2 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ def __init__(
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 = []
Expand Down Expand Up @@ -1142,7 +1142,7 @@ def _get_direct_parametrize_args(self, node):

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 = ()

Expand Down
19 changes: 13 additions & 6 deletions src/_pytest/outcomes.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,21 @@ def xfail(reason: str = "") -> "NoReturn":
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's ``__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

Expand Down
6 changes: 6 additions & 0 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,12 @@ def mkpydir(self, name):
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 to copy.
:return: path to the copied directory (inside ``self.tmpdir``).
"""
import warnings
from _pytest.warning_types import PYTESTER_COPY_EXAMPLE

Expand Down
19 changes: 13 additions & 6 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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
Expand Down Expand Up @@ -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


Expand Down

0 comments on commit 0f11a7a

Please sign in to comment.