Skip to content

Commit 531b76a

Browse files
authored
Merge pull request #3931 from nicoddemus/internal-warnings
Use standard warnings for internal pytest warnings
2 parents 29bfa5e + f63c683 commit 531b76a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1041
-377
lines changed

changelog/2452.feature.rst

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Internal pytest warnings are now issued using the standard ``warnings`` module, making it possible to use
2+
the standard warnings filters to manage those warnings. This introduces ``PytestWarning``,
3+
``PytestDeprecationWarning`` and ``RemovedInPytest4Warning`` warning types as part of the public API.
4+
5+
Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`_ for more info.

changelog/2452.removal.rst

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
``Config.warn`` has been deprecated, it should be replaced by calls to the standard ``warnings.warn``.
2+
3+
``Node.warn`` now supports two signatures:
4+
5+
* ``node.warn(PytestWarning("some message"))``: is now the recommended way to call this function. The warning
6+
instance must be a ``PytestWarning`` or subclass instance.
7+
8+
* ``node.warn("CI", "some message")``: this code/message form is now deprecated and should be converted to
9+
the warning instance form above.
10+
11+
``RemovedInPytest4Warning`` and ``PytestExperimentalApiWarning`` are now part of the public API and should be accessed
12+
using ``pytest.RemovedInPytest4Warning`` and ``pytest.PytestExperimentalApiWarning``.

changelog/2908.feature.rst

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is
2+
configured. This makes pytest more compliant with
3+
`PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_. See
4+
`the docs <https://docs.pytest.org/en/latest/warnings.html#deprecationwarning-and-pendingdeprecationwarning>`_ for
5+
more info.

changelog/3251.feture.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Warnings are now captured and displayed during test collection.

changelog/3936.removal.rst

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``@pytest.mark.filterwarnings`` second parameter is no longer regex-escaped,
2+
making it possible to actually use regular expressions to check the warning message.
3+
4+
**Note**: regex-escaping the match string was an implementation oversight that might break test suites which depend
5+
on the old behavior.

doc/en/reference.rst

+2
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,8 @@ Session related reporting hooks:
611611
.. autofunction:: pytest_terminal_summary
612612
.. autofunction:: pytest_fixture_setup
613613
.. autofunction:: pytest_fixture_post_finalizer
614+
.. autofunction:: pytest_logwarning
615+
.. autofunction:: pytest_warning_captured
614616

615617
And here is the central hook for reporting about
616618
test execution:

doc/en/warnings.rst

+97-14
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ Running pytest now produces this output::
3636
-- Docs: https://docs.pytest.org/en/latest/warnings.html
3737
=================== 1 passed, 1 warnings in 0.12 seconds ===================
3838

39-
Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.
40-
4139
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
4240
them into errors::
4341

@@ -78,6 +76,53 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P
7876
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
7977
documentation for other examples and advanced usage.
8078

79+
Disabling warning summary
80+
-------------------------
81+
82+
Although not recommended, you can use the ``--disable-warnings`` command-line option to suppress the
83+
warning summary entirely from the test run output.
84+
85+
Disabling warning capture entirely
86+
----------------------------------
87+
88+
This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
89+
90+
.. code-block:: ini
91+
92+
[pytest]
93+
addopts = -p no:warnings
94+
95+
Or passing ``-p no:warnings`` in the command-line. This might be useful if your test suites handles warnings
96+
using an external system.
97+
98+
99+
.. _`deprecation-warnings`:
100+
101+
DeprecationWarning and PendingDeprecationWarning
102+
------------------------------------------------
103+
104+
.. versionadded:: 3.8
105+
106+
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` if no other warning filters
107+
are configured.
108+
109+
To disable showing ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings, you might define any warnings
110+
filter either in the command-line or in the ini file, or you can use:
111+
112+
.. code-block:: ini
113+
114+
[pytest]
115+
filterwarnings =
116+
ignore::DeprecationWarning
117+
ignore::PendingDeprecationWarning
118+
119+
.. note::
120+
This makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
121+
be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all
122+
warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves
123+
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
124+
for an example of that).
125+
81126

82127
.. _`filterwarnings`:
83128

@@ -144,18 +189,6 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
144189
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
145190

146191

147-
Disabling warning capture
148-
-------------------------
149-
150-
This feature is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
151-
152-
.. code-block:: ini
153-
154-
[pytest]
155-
addopts = -p no:warnings
156-
157-
Or passing ``-p no:warnings`` in the command-line.
158-
159192
.. _`asserting warnings`:
160193

161194
.. _assertwarnings:
@@ -296,3 +329,53 @@ You can also use it as a contextmanager::
296329
def test_global():
297330
with pytest.deprecated_call():
298331
myobject.deprecated_method()
332+
333+
334+
Internal pytest warnings
335+
------------------------
336+
337+
.. versionadded:: 3.8
338+
339+
pytest may generate its own warnings in some situations, such as improper usage or deprecated features.
340+
341+
For example, pytest will emit a warning if it encounters a class that matches :confval:`python_classes` but also
342+
defines an ``__init__`` constructor, as this prevents the class from being instantiated:
343+
344+
.. code-block:: python
345+
346+
# content of test_pytest_warnings.py
347+
class Test:
348+
def __init__(self):
349+
pass
350+
351+
def test_foo(self):
352+
assert 1 == 1
353+
354+
::
355+
356+
$ pytest test_pytest_warnings.py -q
357+
======================================== warnings summary =========================================
358+
test_pytest_warnings.py:1
359+
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
360+
class Test:
361+
362+
-- Docs: http://doc.pytest.org/en/latest/warnings.html
363+
1 warnings in 0.01 seconds
364+
365+
366+
367+
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.
368+
369+
Following our :ref:`backwards-compatibility`, deprecated features will be kept *at least* two minor releases. After that,
370+
they will changed so they by default raise errors instead of just warnings, so users can adapt to it on their own time
371+
if not having done so until now. In a later release the deprecated feature will be removed completely.
372+
373+
The following warning types ares used by pytest and are part of the public API:
374+
375+
.. autoclass:: pytest.PytestWarning
376+
377+
.. autoclass:: pytest.PytestDeprecationWarning
378+
379+
.. autoclass:: pytest.RemovedInPytest4Warning
380+
381+
.. autoclass:: pytest.PytestExperimentalApiWarning

doc/en/writing_plugins.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ additionally it is possible to copy examples for a example folder before running
419419
420420
============================= warnings summary =============================
421421
test_example.py::test_plugin
422-
$REGENDOC_TMPDIR/test_example.py:4: PytestExerimentalApiWarning: testdir.copy_example is an experimental api that may change over time
422+
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
423423
testdir.copy_example("test_example.py")
424424
425425
-- Docs: https://docs.pytest.org/en/latest/warnings.html

src/_pytest/assertion/rewrite.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,12 @@ def mark_rewrite(self, *names):
209209
self._must_rewrite.update(names)
210210

211211
def _warn_already_imported(self, name):
212-
self.config.warn(
213-
"P1", "Module already imported so cannot be rewritten: %s" % name
212+
from _pytest.warning_types import PytestWarning
213+
from _pytest.warnings import _issue_config_warning
214+
215+
_issue_config_warning(
216+
PytestWarning("Module already imported so cannot be rewritten: %s" % name),
217+
self.config,
214218
)
215219

216220
def load_module(self, name):
@@ -746,13 +750,17 @@ def visit_Assert(self, assert_):
746750
the expression is false.
747751
748752
"""
749-
if isinstance(assert_.test, ast.Tuple) and self.config is not None:
750-
fslocation = (self.module_path, assert_.lineno)
751-
self.config.warn(
752-
"R1",
753-
"assertion is always true, perhaps " "remove parentheses?",
754-
fslocation=fslocation,
753+
if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
754+
from _pytest.warning_types import PytestWarning
755+
import warnings
756+
757+
warnings.warn_explicit(
758+
PytestWarning("assertion is always true, perhaps remove parentheses?"),
759+
category=None,
760+
filename=str(self.module_path),
761+
lineno=assert_.lineno,
755762
)
763+
756764
self.statements = []
757765
self.variables = []
758766
self.variable_counter = itertools.count()

src/_pytest/cacheprovider.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,27 @@
3333
@attr.s
3434
class Cache(object):
3535
_cachedir = attr.ib(repr=False)
36-
_warn = attr.ib(repr=False)
36+
_config = attr.ib(repr=False)
3737

3838
@classmethod
3939
def for_config(cls, config):
4040
cachedir = cls.cache_dir_from_config(config)
4141
if config.getoption("cacheclear") and cachedir.exists():
4242
shutil.rmtree(str(cachedir))
4343
cachedir.mkdir()
44-
return cls(cachedir, config.warn)
44+
return cls(cachedir, config)
4545

4646
@staticmethod
4747
def cache_dir_from_config(config):
4848
return paths.resolve_from_str(config.getini("cache_dir"), config.rootdir)
4949

5050
def warn(self, fmt, **args):
51-
self._warn(code="I9", message=fmt.format(**args) if args else fmt)
51+
from _pytest.warnings import _issue_config_warning
52+
from _pytest.warning_types import PytestWarning
53+
54+
_issue_config_warning(
55+
PytestWarning(fmt.format(**args) if args else fmt), self._config
56+
)
5257

5358
def makedir(self, name):
5459
""" return a directory path object with the given name. If the

src/_pytest/config/__init__.py

+33-4
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ def _prepareconfig(args=None, plugins=None):
176176
else:
177177
pluginmanager.register(plugin)
178178
if warning:
179-
config.warn("C1", warning)
179+
from _pytest.warnings import _issue_config_warning
180+
181+
_issue_config_warning(warning, config=config)
180182
return pluginmanager.hook.pytest_cmdline_parse(
181183
pluginmanager=pluginmanager, args=args
182184
)
@@ -417,7 +419,12 @@ def _importconftest(self, conftestpath):
417419
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
418420
)
419421

420-
warnings.warn(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST)
422+
warnings.warn_explicit(
423+
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
424+
category=None,
425+
filename=str(conftestpath),
426+
lineno=0,
427+
)
421428
except Exception:
422429
raise ConftestImportFailure(conftestpath, sys.exc_info())
423430

@@ -602,7 +609,29 @@ def _ensure_unconfigure(self):
602609
fin()
603610

604611
def warn(self, code, message, fslocation=None, nodeid=None):
605-
""" generate a warning for this test session. """
612+
"""
613+
.. deprecated:: 3.8
614+
615+
Use :py:func:`warnings.warn` or :py:func:`warnings.warn_explicit` directly instead.
616+
617+
Generate a warning for this test session.
618+
"""
619+
from _pytest.warning_types import RemovedInPytest4Warning
620+
621+
if isinstance(fslocation, (tuple, list)) and len(fslocation) > 2:
622+
filename, lineno = fslocation[:2]
623+
else:
624+
filename = "unknown file"
625+
lineno = 0
626+
msg = "config.warn has been deprecated, use warnings.warn instead"
627+
if nodeid:
628+
msg = "{}: {}".format(nodeid, msg)
629+
warnings.warn_explicit(
630+
RemovedInPytest4Warning(msg),
631+
category=None,
632+
filename=filename,
633+
lineno=lineno,
634+
)
606635
self.hook.pytest_logwarning.call_historic(
607636
kwargs=dict(
608637
code=code, message=message, fslocation=fslocation, nodeid=nodeid
@@ -667,8 +696,8 @@ def _initini(self, args):
667696
r = determine_setup(
668697
ns.inifilename,
669698
ns.file_or_dir + unknown_args,
670-
warnfunc=self.warn,
671699
rootdir_cmd_arg=ns.rootdir or None,
700+
config=self,
672701
)
673702
self.rootdir, self.inifile, self.inicfg = r
674703
self._parser.extra_info["rootdir"] = self.rootdir

0 commit comments

Comments
 (0)