Skip to content

Commit fc2ad1d

Browse files
authored
Warning subclasses (#5179)
Warning subclasses
2 parents fb6dad6 + 915ecb0 commit fc2ad1d

File tree

12 files changed

+122
-35
lines changed

12 files changed

+122
-35
lines changed

changelog/5177.feature.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Introduce new specific warning ``PytestWarning`` subclasses to make it easier to filter warnings based on the class, rather than on the message. The new subclasses are:
2+
3+
4+
* ``PytestAssertRewriteWarning``
5+
6+
* ``PytestCacheWarning``
7+
8+
* ``PytestCollectionWarning``
9+
10+
* ``PytestConfigWarning``
11+
12+
* ``PytestUnhandledCoroutineWarning``
13+
14+
* ``PytestUnknownMarkWarning``

doc/en/warnings.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,20 @@ The following warning types ares used by pytest and are part of the public API:
415415

416416
.. autoclass:: pytest.PytestWarning
417417

418-
.. autoclass:: pytest.PytestDeprecationWarning
418+
.. autoclass:: pytest.PytestAssertRewriteWarning
419419

420-
.. autoclass:: pytest.RemovedInPytest4Warning
420+
.. autoclass:: pytest.PytestCacheWarning
421+
422+
.. autoclass:: pytest.PytestCollectionWarning
423+
424+
.. autoclass:: pytest.PytestConfigWarning
425+
426+
.. autoclass:: pytest.PytestDeprecationWarning
421427

422428
.. autoclass:: pytest.PytestExperimentalApiWarning
429+
430+
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
431+
432+
.. autoclass:: pytest.PytestUnknownMarkWarning
433+
434+
.. autoclass:: pytest.RemovedInPytest4Warning

src/_pytest/assertion/rewrite.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,13 @@ def mark_rewrite(self, *names):
268268
self._marked_for_rewrite_cache.clear()
269269

270270
def _warn_already_imported(self, name):
271-
from _pytest.warning_types import PytestWarning
271+
from _pytest.warning_types import PytestAssertRewriteWarning
272272
from _pytest.warnings import _issue_warning_captured
273273

274274
_issue_warning_captured(
275-
PytestWarning("Module already imported so cannot be rewritten: %s" % name),
275+
PytestAssertRewriteWarning(
276+
"Module already imported so cannot be rewritten: %s" % name
277+
),
276278
self.config.hook,
277279
stacklevel=5,
278280
)
@@ -819,11 +821,13 @@ def visit_Assert(self, assert_):
819821
820822
"""
821823
if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
822-
from _pytest.warning_types import PytestWarning
824+
from _pytest.warning_types import PytestAssertRewriteWarning
823825
import warnings
824826

825827
warnings.warn_explicit(
826-
PytestWarning("assertion is always true, perhaps remove parentheses?"),
828+
PytestAssertRewriteWarning(
829+
"assertion is always true, perhaps remove parentheses?"
830+
),
827831
category=None,
828832
filename=str(self.module_path),
829833
lineno=assert_.lineno,
@@ -887,10 +891,10 @@ def warn_about_none_ast(self, node, module_path, lineno):
887891
val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE])
888892
send_warning = ast.parse(
889893
"""
890-
from _pytest.warning_types import PytestWarning
894+
from _pytest.warning_types import PytestAssertRewriteWarning
891895
from warnings import warn_explicit
892896
warn_explicit(
893-
PytestWarning('asserting the value None, please use "assert is None"'),
897+
PytestAssertRewriteWarning('asserting the value None, please use "assert is None"'),
894898
category=None,
895899
filename={filename!r},
896900
lineno={lineno},

src/_pytest/cacheprovider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ def cache_dir_from_config(config):
6060

6161
def warn(self, fmt, **args):
6262
from _pytest.warnings import _issue_warning_captured
63-
from _pytest.warning_types import PytestWarning
63+
from _pytest.warning_types import PytestCacheWarning
6464

6565
_issue_warning_captured(
66-
PytestWarning(fmt.format(**args) if args else fmt),
66+
PytestCacheWarning(fmt.format(**args) if args else fmt),
6767
self._config.hook,
6868
stacklevel=3,
6969
)

src/_pytest/config/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from _pytest.compat import safe_str
3333
from _pytest.outcomes import fail
3434
from _pytest.outcomes import Skipped
35-
from _pytest.warning_types import PytestWarning
35+
from _pytest.warning_types import PytestConfigWarning
3636

3737
hookimpl = HookimplMarker("pytest")
3838
hookspec = HookspecMarker("pytest")
@@ -307,7 +307,7 @@ def parse_hookspec_opts(self, module_or_class, name):
307307
def register(self, plugin, name=None):
308308
if name in ["pytest_catchlog", "pytest_capturelog"]:
309309
warnings.warn(
310-
PytestWarning(
310+
PytestConfigWarning(
311311
"{} plugin has been merged into the core, "
312312
"please remove it from your requirements.".format(
313313
name.replace("_", "-")
@@ -574,7 +574,7 @@ def import_plugin(self, modname, consider_entry_points=False):
574574
from _pytest.warnings import _issue_warning_captured
575575

576576
_issue_warning_captured(
577-
PytestWarning("skipped plugin %r: %s" % (modname, e.msg)),
577+
PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)),
578578
self.hook,
579579
stacklevel=1,
580580
)
@@ -863,7 +863,7 @@ def _preparse(self, args, addopts=True):
863863
from _pytest.warnings import _issue_warning_captured
864864

865865
_issue_warning_captured(
866-
PytestWarning(
866+
PytestConfigWarning(
867867
"could not load initial conftests: {}".format(e.path)
868868
),
869869
self.hook,

src/_pytest/junitxml.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,11 @@ def record_xml_attribute(request):
307307
The fixture is callable with ``(name, value)``, with value being
308308
automatically xml-encoded
309309
"""
310-
from _pytest.warning_types import PytestWarning
310+
from _pytest.warning_types import PytestExperimentalApiWarning, PytestWarning
311311

312-
request.node.warn(PytestWarning("record_xml_attribute is an experimental feature"))
312+
request.node.warn(
313+
PytestExperimentalApiWarning("record_xml_attribute is an experimental feature")
314+
)
313315

314316
# Declare noop
315317
def add_attr_noop(name, value):

src/_pytest/mark/structures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from ..compat import NOTSET
1313
from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS
1414
from _pytest.outcomes import fail
15-
from _pytest.warning_types import UnknownMarkWarning
15+
from _pytest.warning_types import PytestUnknownMarkWarning
1616

1717
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
1818

@@ -318,7 +318,7 @@ def __getattr__(self, name):
318318
"Unknown pytest.mark.%s - is this a typo? You can register "
319319
"custom marks to avoid this warning - for details, see "
320320
"https://docs.pytest.org/en/latest/mark.html" % name,
321-
UnknownMarkWarning,
321+
PytestUnknownMarkWarning,
322322
)
323323

324324
return MarkDecorator(Mark(name, (), {}))

src/_pytest/python.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
from _pytest.outcomes import fail
4646
from _pytest.outcomes import skip
4747
from _pytest.pathlib import parts
48-
from _pytest.warning_types import PytestWarning
48+
from _pytest.warning_types import PytestCollectionWarning
49+
from _pytest.warning_types import PytestUnhandledCoroutineWarning
4950

5051

5152
def pyobj_property(name):
@@ -171,7 +172,7 @@ def pytest_pyfunc_call(pyfuncitem):
171172
msg += " - pytest-asyncio\n"
172173
msg += " - pytest-trio\n"
173174
msg += " - pytest-tornasync"
174-
warnings.warn(PytestWarning(msg.format(pyfuncitem.nodeid)))
175+
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid)))
175176
skip(msg="coroutine function and no async plugin installed (see warnings)")
176177
funcargs = pyfuncitem.funcargs
177178
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
@@ -221,7 +222,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
221222
if not (isfunction(obj) or isfunction(get_real_func(obj))):
222223
filename, lineno = getfslineno(obj)
223224
warnings.warn_explicit(
224-
message=PytestWarning(
225+
message=PytestCollectionWarning(
225226
"cannot collect %r because it is not a function." % name
226227
),
227228
category=None,
@@ -233,7 +234,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
233234
res = Function(name, parent=collector)
234235
reason = deprecated.YIELD_TESTS.format(name=name)
235236
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
236-
res.warn(PytestWarning(reason))
237+
res.warn(PytestCollectionWarning(reason))
237238
else:
238239
res = list(collector._genfunctions(name, obj))
239240
outcome.force_result(res)
@@ -721,15 +722,15 @@ def collect(self):
721722
return []
722723
if hasinit(self.obj):
723724
self.warn(
724-
PytestWarning(
725+
PytestCollectionWarning(
725726
"cannot collect test class %r because it has a "
726727
"__init__ constructor" % self.obj.__name__
727728
)
728729
)
729730
return []
730731
elif hasnew(self.obj):
731732
self.warn(
732-
PytestWarning(
733+
PytestCollectionWarning(
733734
"cannot collect test class %r because it has a "
734735
"__new__ constructor" % self.obj.__name__
735736
)

src/_pytest/warning_types.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,43 @@ class PytestWarning(UserWarning):
99
"""
1010

1111

12-
class UnknownMarkWarning(PytestWarning):
12+
class PytestAssertRewriteWarning(PytestWarning):
1313
"""
1414
Bases: :class:`PytestWarning`.
1515
16-
Warning emitted on use of unknown markers.
17-
See https://docs.pytest.org/en/latest/mark.html for details.
16+
Warning emitted by the pytest assert rewrite module.
1817
"""
1918

2019

21-
class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
20+
class PytestCacheWarning(PytestWarning):
2221
"""
23-
Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`.
22+
Bases: :class:`PytestWarning`.
2423
25-
Warning class for features that will be removed in a future version.
24+
Warning emitted by the cache plugin in various situations.
2625
"""
2726

2827

29-
class RemovedInPytest4Warning(PytestDeprecationWarning):
28+
class PytestConfigWarning(PytestWarning):
3029
"""
31-
Bases: :class:`pytest.PytestDeprecationWarning`.
30+
Bases: :class:`PytestWarning`.
3231
33-
Warning class for features scheduled to be removed in pytest 4.0.
32+
Warning emitted for configuration issues.
33+
"""
34+
35+
36+
class PytestCollectionWarning(PytestWarning):
37+
"""
38+
Bases: :class:`PytestWarning`.
39+
40+
Warning emitted when pytest is not able to collect a file or symbol in a module.
41+
"""
42+
43+
44+
class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
45+
"""
46+
Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`.
47+
48+
Warning class for features that will be removed in a future version.
3449
"""
3550

3651

@@ -51,6 +66,33 @@ def simple(cls, apiname):
5166
)
5267

5368

69+
class PytestUnhandledCoroutineWarning(PytestWarning):
70+
"""
71+
Bases: :class:`PytestWarning`.
72+
73+
Warning emitted when pytest encounters a test function which is a coroutine,
74+
but it was not handled by any async-aware plugin. Coroutine test functions
75+
are not natively supported.
76+
"""
77+
78+
79+
class PytestUnknownMarkWarning(PytestWarning):
80+
"""
81+
Bases: :class:`PytestWarning`.
82+
83+
Warning emitted on use of unknown markers.
84+
See https://docs.pytest.org/en/latest/mark.html for details.
85+
"""
86+
87+
88+
class RemovedInPytest4Warning(PytestDeprecationWarning):
89+
"""
90+
Bases: :class:`pytest.PytestDeprecationWarning`.
91+
92+
Warning class for features scheduled to be removed in pytest 4.0.
93+
"""
94+
95+
5496
@attr.s
5597
class UnformattedWarning(object):
5698
"""Used to hold warnings that need to format their message at runtime, as opposed to a direct message.

src/pytest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@
3535
from _pytest.python_api import raises
3636
from _pytest.recwarn import deprecated_call
3737
from _pytest.recwarn import warns
38+
from _pytest.warning_types import PytestAssertRewriteWarning
39+
from _pytest.warning_types import PytestCacheWarning
40+
from _pytest.warning_types import PytestCollectionWarning
41+
from _pytest.warning_types import PytestConfigWarning
3842
from _pytest.warning_types import PytestDeprecationWarning
3943
from _pytest.warning_types import PytestExperimentalApiWarning
44+
from _pytest.warning_types import PytestUnhandledCoroutineWarning
45+
from _pytest.warning_types import PytestUnknownMarkWarning
4046
from _pytest.warning_types import PytestWarning
4147
from _pytest.warning_types import RemovedInPytest4Warning
4248

@@ -66,8 +72,14 @@
6672
"Module",
6773
"Package",
6874
"param",
75+
"PytestAssertRewriteWarning",
76+
"PytestCacheWarning",
77+
"PytestCollectionWarning",
78+
"PytestConfigWarning",
6979
"PytestDeprecationWarning",
7080
"PytestExperimentalApiWarning",
81+
"PytestUnhandledCoroutineWarning",
82+
"PytestUnknownMarkWarning",
7183
"PytestWarning",
7284
"raises",
7385
"register_assert_rewrite",

0 commit comments

Comments
 (0)