Warning subclasses (#5179)

Warning subclasses
This commit is contained in:
Bruno Oliveira 2019-04-29 17:57:49 -03:00 committed by GitHub
commit fc2ad1dbed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 124 additions and 37 deletions

View File

@ -0,0 +1,14 @@
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:
* ``PytestAssertRewriteWarning``
* ``PytestCacheWarning``
* ``PytestCollectionWarning``
* ``PytestConfigWarning``
* ``PytestUnhandledCoroutineWarning``
* ``PytestUnknownMarkWarning``

View File

@ -415,8 +415,20 @@ The following warning types ares used by pytest and are part of the public API:
.. autoclass:: pytest.PytestWarning
.. autoclass:: pytest.PytestAssertRewriteWarning
.. autoclass:: pytest.PytestCacheWarning
.. autoclass:: pytest.PytestCollectionWarning
.. autoclass:: pytest.PytestConfigWarning
.. autoclass:: pytest.PytestDeprecationWarning
.. autoclass:: pytest.RemovedInPytest4Warning
.. autoclass:: pytest.PytestExperimentalApiWarning
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
.. autoclass:: pytest.PytestUnknownMarkWarning
.. autoclass:: pytest.RemovedInPytest4Warning

View File

@ -268,11 +268,13 @@ class AssertionRewritingHook(object):
self._marked_for_rewrite_cache.clear()
def _warn_already_imported(self, name):
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestAssertRewriteWarning
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning("Module already imported so cannot be rewritten: %s" % name),
PytestAssertRewriteWarning(
"Module already imported so cannot be rewritten: %s" % name
),
self.config.hook,
stacklevel=5,
)
@ -819,11 +821,13 @@ class AssertionRewriter(ast.NodeVisitor):
"""
if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestAssertRewriteWarning
import warnings
warnings.warn_explicit(
PytestWarning("assertion is always true, perhaps remove parentheses?"),
PytestAssertRewriteWarning(
"assertion is always true, perhaps remove parentheses?"
),
category=None,
filename=str(self.module_path),
lineno=assert_.lineno,
@ -887,10 +891,10 @@ class AssertionRewriter(ast.NodeVisitor):
val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE])
send_warning = ast.parse(
"""
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestAssertRewriteWarning
from warnings import warn_explicit
warn_explicit(
PytestWarning('asserting the value None, please use "assert is None"'),
PytestAssertRewriteWarning('asserting the value None, please use "assert is None"'),
category=None,
filename={filename!r},
lineno={lineno},

View File

@ -60,10 +60,10 @@ class Cache(object):
def warn(self, fmt, **args):
from _pytest.warnings import _issue_warning_captured
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestCacheWarning
_issue_warning_captured(
PytestWarning(fmt.format(**args) if args else fmt),
PytestCacheWarning(fmt.format(**args) if args else fmt),
self._config.hook,
stacklevel=3,
)

View File

@ -32,7 +32,7 @@ from _pytest.compat import lru_cache
from _pytest.compat import safe_str
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestConfigWarning
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
@ -307,7 +307,7 @@ class PytestPluginManager(PluginManager):
def register(self, plugin, name=None):
if name in ["pytest_catchlog", "pytest_capturelog"]:
warnings.warn(
PytestWarning(
PytestConfigWarning(
"{} plugin has been merged into the core, "
"please remove it from your requirements.".format(
name.replace("_", "-")
@ -574,7 +574,7 @@ class PytestPluginManager(PluginManager):
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning("skipped plugin %r: %s" % (modname, e.msg)),
PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)),
self.hook,
stacklevel=1,
)
@ -863,7 +863,7 @@ class Config(object):
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning(
PytestConfigWarning(
"could not load initial conftests: {}".format(e.path)
),
self.hook,

View File

@ -307,9 +307,11 @@ def record_xml_attribute(request):
The fixture is callable with ``(name, value)``, with value being
automatically xml-encoded
"""
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestExperimentalApiWarning, PytestWarning
request.node.warn(PytestWarning("record_xml_attribute is an experimental feature"))
request.node.warn(
PytestExperimentalApiWarning("record_xml_attribute is an experimental feature")
)
# Declare noop
def add_attr_noop(name, value):

View File

@ -12,7 +12,7 @@ from ..compat import MappingMixin
from ..compat import NOTSET
from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS
from _pytest.outcomes import fail
from _pytest.warning_types import UnknownMarkWarning
from _pytest.warning_types import PytestUnknownMarkWarning
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
@ -318,7 +318,7 @@ class MarkGenerator(object):
"Unknown pytest.mark.%s - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
"https://docs.pytest.org/en/latest/mark.html" % name,
UnknownMarkWarning,
PytestUnknownMarkWarning,
)
return MarkDecorator(Mark(name, (), {}))

View File

@ -45,7 +45,8 @@ from _pytest.mark.structures import normalize_mark_list
from _pytest.outcomes import fail
from _pytest.outcomes import skip
from _pytest.pathlib import parts
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
def pyobj_property(name):
@ -171,7 +172,7 @@ def pytest_pyfunc_call(pyfuncitem):
msg += " - pytest-asyncio\n"
msg += " - pytest-trio\n"
msg += " - pytest-tornasync"
warnings.warn(PytestWarning(msg.format(pyfuncitem.nodeid)))
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid)))
skip(msg="coroutine function and no async plugin installed (see warnings)")
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
@ -221,7 +222,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
if not (isfunction(obj) or isfunction(get_real_func(obj))):
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
message=PytestWarning(
message=PytestCollectionWarning(
"cannot collect %r because it is not a function." % name
),
category=None,
@ -233,7 +234,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
res = Function(name, parent=collector)
reason = deprecated.YIELD_TESTS.format(name=name)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestWarning(reason))
res.warn(PytestCollectionWarning(reason))
else:
res = list(collector._genfunctions(name, obj))
outcome.force_result(res)
@ -721,7 +722,7 @@ class Class(PyCollector):
return []
if hasinit(self.obj):
self.warn(
PytestWarning(
PytestCollectionWarning(
"cannot collect test class %r because it has a "
"__init__ constructor" % self.obj.__name__
)
@ -729,7 +730,7 @@ class Class(PyCollector):
return []
elif hasnew(self.obj):
self.warn(
PytestWarning(
PytestCollectionWarning(
"cannot collect test class %r because it has a "
"__new__ constructor" % self.obj.__name__
)

View File

@ -9,12 +9,35 @@ class PytestWarning(UserWarning):
"""
class UnknownMarkWarning(PytestWarning):
class PytestAssertRewriteWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
Warning emitted on use of unknown markers.
See https://docs.pytest.org/en/latest/mark.html for details.
Warning emitted by the pytest assert rewrite module.
"""
class PytestCacheWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
Warning emitted by the cache plugin in various situations.
"""
class PytestConfigWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
Warning emitted for configuration issues.
"""
class PytestCollectionWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
Warning emitted when pytest is not able to collect a file or symbol in a module.
"""
@ -26,14 +49,6 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
"""
class RemovedInPytest4Warning(PytestDeprecationWarning):
"""
Bases: :class:`pytest.PytestDeprecationWarning`.
Warning class for features scheduled to be removed in pytest 4.0.
"""
class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
"""
Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`.
@ -51,6 +66,33 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
)
class PytestUnhandledCoroutineWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
Warning emitted when pytest encounters a test function which is a coroutine,
but it was not handled by any async-aware plugin. Coroutine test functions
are not natively supported.
"""
class PytestUnknownMarkWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
Warning emitted on use of unknown markers.
See https://docs.pytest.org/en/latest/mark.html for details.
"""
class RemovedInPytest4Warning(PytestDeprecationWarning):
"""
Bases: :class:`pytest.PytestDeprecationWarning`.
Warning class for features scheduled to be removed in pytest 4.0.
"""
@attr.s
class UnformattedWarning(object):
"""Used to hold warnings that need to format their message at runtime, as opposed to a direct message.

View File

@ -35,8 +35,14 @@ from _pytest.python_api import approx
from _pytest.python_api import raises
from _pytest.recwarn import deprecated_call
from _pytest.recwarn import warns
from _pytest.warning_types import PytestAssertRewriteWarning
from _pytest.warning_types import PytestCacheWarning
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestConfigWarning
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestExperimentalApiWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
from _pytest.warning_types import PytestUnknownMarkWarning
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import RemovedInPytest4Warning
@ -66,8 +72,14 @@ __all__ = [
"Module",
"Package",
"param",
"PytestAssertRewriteWarning",
"PytestCacheWarning",
"PytestCollectionWarning",
"PytestConfigWarning",
"PytestDeprecationWarning",
"PytestExperimentalApiWarning",
"PytestUnhandledCoroutineWarning",
"PytestUnknownMarkWarning",
"PytestWarning",
"raises",
"register_assert_rewrite",

View File

@ -630,7 +630,7 @@ def test_removed_in_pytest4_warning_as_error(testdir, change_default):
class TestAssertionWarnings:
@staticmethod
def assert_result_warns(result, msg):
result.stdout.fnmatch_lines(["*PytestWarning: %s*" % msg])
result.stdout.fnmatch_lines(["*PytestAssertRewriteWarning: %s*" % msg])
def test_tuple_warning(self, testdir):
testdir.makepyfile(

View File

@ -169,7 +169,7 @@ filterwarnings =
# Do not cause SyntaxError for invalid escape sequences in py37.
default:invalid escape sequence:DeprecationWarning
# ignore use of unregistered marks, because we use many to test the implementation
ignore::_pytest.warning_types.UnknownMarkWarning
ignore::_pytest.warning_types.PytestUnknownMarkWarning
pytester_example_dir = testing/example_scripts
markers =
issue