diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2768037bcb..4d127d3c58 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5ef418e0b5..8e59191abd 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -167,7 +167,7 @@ Short version #. Enable and install `pre-commit `_ 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 `_ for formatting. +#. Follow **PEP-8** for naming and `black `_ for formatting. #. Tests are run using ``tox``:: tox -e linting,py37 diff --git a/README.rst b/README.rst index 301e495388..4bf9cce2f4 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/changelog/5115.bugfix.rst b/changelog/5115.bugfix.rst new file mode 100644 index 0000000000..af75499a39 --- /dev/null +++ b/changelog/5115.bugfix.rst @@ -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. diff --git a/changelog/5669.doc.rst b/changelog/5669.doc.rst new file mode 100644 index 0000000000..0ec9626ae5 --- /dev/null +++ b/changelog/5669.doc.rst @@ -0,0 +1 @@ +Add docstring for ``Testdir.copy_example``. diff --git a/changelog/5701.bugfix.rst b/changelog/5701.bugfix.rst new file mode 100644 index 0000000000..b654e74479 --- /dev/null +++ b/changelog/5701.bugfix.rst @@ -0,0 +1 @@ +Fix collection of ``staticmethod`` objects defined with ``functools.partial``. diff --git a/changelog/5734.bugfix.rst b/changelog/5734.bugfix.rst new file mode 100644 index 0000000000..dc20e6b523 --- /dev/null +++ b/changelog/5734.bugfix.rst @@ -0,0 +1 @@ +Skip async generator test functions, and update the warning message to refer to ``async def`` functions. diff --git a/doc/en/capture.rst b/doc/en/capture.rst index 356da26785..55714c25b4 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -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(): diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index 129362cd7f..26454e48d7 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -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): diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index a1fe658740..909f23a2e1 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -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: diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py index 3dc1d9b29d..9db6879eda 100644 --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -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)) diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index c94620e97b..93d8285bfa 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -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): diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index ae8928c51b..05d06ecb6b 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -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 diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index dbfba0fa09..b4baa2b9b8 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -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 = "" diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index ea5e2dc86e..b494ec0fe1 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -629,7 +629,7 @@ through the special :py:class:`request ` 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): diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 149bd33f16..f1c28769f0 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -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`: diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 7e47ffce07..991050c232 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -27,6 +27,8 @@ pytest.skip .. autofunction:: _pytest.outcomes.skip(msg, [allow_module_level=False]) +.. _`pytest.importorskip ref`: + pytest.importorskip ~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index eb00a6dfb1..57f565472e 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -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 diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index df049991b1..2d11231a4d 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -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): @@ -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) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index dbcc4cc67d..b861563e99 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -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: diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 1d84a9b0ea..d5f9ad2d3f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -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 = [] @@ -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 = () diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 5e331a323d..13eb0a295e 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -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 ``__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 diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 5835f01d15..d9ceb0e786 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -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 for copy. + :return: path to the copied directory (inside ``self.tmpdir``). + + """ import warnings from _pytest.warning_types import PYTESTER_COPY_EXAMPLE diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4e6ae85134..913a93bc02 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -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 @@ -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 diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 427c647855..ad9c37737a 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1189,6 +1189,39 @@ async def test_1(): pass async def test_2(): pass + 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 + ) + + +@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() @@ -1196,11 +1229,12 @@ async def test_2(): [ "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 ) diff --git a/testing/python/collect.py b/testing/python/collect.py index 61fc585799..e6dd3e8708 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1143,52 +1143,6 @@ class Test(object): 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 diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index e5bc25a1c8..cfe2ed37d9 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -9,7 +9,9 @@ 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 j(arg1, arg2, arg3="hello"): 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): diff --git a/testing/test_compat.py b/testing/test_compat.py index 9e7d05c5df..94dac439d4 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,4 +1,5 @@ import sys +from functools import partial from functools import wraps import pytest @@ -72,6 +73,16 @@ def 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(): 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 @@ async def bar(): 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): diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index fd443ed40d..9afa1e23f3 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -55,7 +55,7 @@ def test_skip(): ] ) - 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 @@ def test(): "*Sending information to Paste Service*", ] ) + assert len(pastebinlist) == 1 class TestPaste: diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 4d58c99e38..077636c52d 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -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"