Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master into features #5744

Merged
merged 21 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b095e0d
Improve docs of pytest.importorskip
timhoffm Aug 9, 2019
8ffa3aa
Improve docs of pytest.importorskip (#5718)
nicoddemus Aug 9, 2019
e0ce8b7
pytester: add docstrings for Testdir.copy_example
martbln Aug 9, 2019
ee936b2
pytester: fix docstrings
martbln Aug 10, 2019
f7e81da
5669: pytester: add docstrings for Testdir.copy_example (#5719)
nicoddemus Aug 10, 2019
0767f08
Update URL: python/black → psf/black
jdufresne Aug 10, 2019
a77c83a
Merge pull request #5726 from jdufresne/black
blueyed Aug 10, 2019
0db9dad
test_non_ascii_paste_text: mock call to urlopen
blueyed Aug 10, 2019
3eb4973
remove %s formatting from docs
Aug 12, 2019
3b3ce0e
remove %s formatting from docs (#5733)
nicoddemus Aug 12, 2019
a295a3d
test_non_ascii_paste_text: mock call to urlopen (#5728)
nicoddemus Aug 12, 2019
7183335
Capture warnings during ``pytest_configure``
Aug 12, 2019
2f1b192
Issue a warning for async gen functions
graingert Aug 12, 2019
1372558
Fix collection of staticmethods defined with functools.partial
graingert Aug 6, 2019
0a62c4a
Merge pull request #5729 from Stranger6667/issue-5115
nicoddemus Aug 15, 2019
0ba774a
warn for async generator functions (#5734)
nicoddemus Aug 15, 2019
6b9d729
also warn on awaitable or async iterable test results
graingert Aug 15, 2019
28c6c5b
check that tests that are partial staticmethods are supported (#5701)
nicoddemus Aug 15, 2019
2d613a0
Async result warn (#5742)
nicoddemus Aug 15, 2019
d7f0825
Merge remote-tracking branch 'upstream/master' into mm
nicoddemus Aug 15, 2019
1049a38
Fix wording as suggested in review of #5741
nicoddemus Aug 15, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wut?
Rather leave it show up in reports..

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