From 53cd7fd2ea2cba07820009a4cfba1e09fa35fcfa Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 28 Apr 2019 10:38:25 -0300 Subject: [PATCH] Introduce new warning subclasses Fix #5177 --- doc/en/warnings.rst | 14 ++++++-- src/_pytest/assertion/rewrite.py | 16 +++++---- src/_pytest/cacheprovider.py | 4 +-- src/_pytest/config/__init__.py | 8 ++--- src/_pytest/junitxml.py | 6 ++-- src/_pytest/python.py | 13 +++---- src/_pytest/warning_types.py | 60 +++++++++++++++++++++++++++----- src/pytest.py | 12 ++++++- testing/test_warnings.py | 2 +- 9 files changed, 102 insertions(+), 33 deletions(-) diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 54740b970a9..83d2d6b1596 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -415,10 +415,20 @@ The following warning types ares used by pytest and are part of the public API: .. autoclass:: pytest.PytestWarning -.. autoclass:: pytest.PytestDeprecationWarning +.. autoclass:: pytest.PytestAssertRewriteWarning -.. autoclass:: pytest.RemovedInPytest4Warning +.. autoclass:: pytest.PytestCacheWarning + +.. autoclass:: pytest.PytestCollectionWarning + +.. autoclass:: pytest.PytestConfigWarning + +.. autoclass:: pytest.PytestDeprecationWarning .. autoclass:: pytest.PytestExperimentalApiWarning +.. autoclass:: pytest.PytestUnhandledCoroutineWarning + .. autoclass:: pytest.PytestUnknownMarkWarning + +.. autoclass:: pytest.RemovedInPytest4Warning diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 7cb1acb2e48..24d96b72247 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -268,11 +268,13 @@ def mark_rewrite(self, *names): 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 @@ def visit_Assert(self, assert_): """ 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 @@ def warn_about_none_ast(self, node, module_path, lineno): 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}, diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 63503ed2e6d..1b7001c0af4 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -60,10 +60,10 @@ def cache_dir_from_config(config): 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, ) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 1a2edf4f8a8..03769b8153f 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -32,7 +32,7 @@ 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 @@ def parse_hookspec_opts(self, module_or_class, name): 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 @@ def import_plugin(self, modname, consider_entry_points=False): 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 @@ def _preparse(self, args, addopts=True): from _pytest.warnings import _issue_warning_captured _issue_warning_captured( - PytestWarning( + PytestConfigWarning( "could not load initial conftests: {}".format(e.path) ), self.hook, diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index c2b277b8a17..3a5f3173553 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -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): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 135f5bff991..377357846bd 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -45,7 +45,8 @@ 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 @@ def collect(self): 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 @@ def collect(self): return [] elif hasnew(self.obj): self.warn( - PytestWarning( + PytestCollectionWarning( "cannot collect test class %r because it has a " "__new__ constructor" % self.obj.__name__ ) diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 70f41a2b26e..2777aabeaaf 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -9,28 +9,43 @@ class PytestWarning(UserWarning): """ -class PytestUnknownMarkWarning(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 PytestDeprecationWarning(PytestWarning, DeprecationWarning): +class PytestCacheWarning(PytestWarning): """ - Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. + Bases: :class:`PytestWarning`. - Warning class for features that will be removed in a future version. + Warning emitted by the cache plugin in various situations. """ -class RemovedInPytest4Warning(PytestDeprecationWarning): +class PytestConfigWarning(PytestWarning): """ - Bases: :class:`pytest.PytestDeprecationWarning`. + Bases: :class:`PytestWarning`. - Warning class for features scheduled to be removed in pytest 4.0. + 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. + """ + + +class PytestDeprecationWarning(PytestWarning, DeprecationWarning): + """ + Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. + + Warning class for features that will be removed in a future version. """ @@ -51,6 +66,33 @@ def simple(cls, apiname): ) +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. diff --git a/src/pytest.py b/src/pytest.py index 2be72ce7721..a6376843d24 100644 --- a/src/pytest.py +++ b/src/pytest.py @@ -35,8 +35,13 @@ 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 @@ -67,13 +72,18 @@ "Module", "Package", "param", + "PytestAssertRewriteWarning", + "PytestCacheWarning", + "PytestCollectionWarning", + "PytestConfigWarning", "PytestDeprecationWarning", "PytestExperimentalApiWarning", + "PytestUnhandledCoroutineWarning", + "PytestUnknownMarkWarning", "PytestWarning", "raises", "register_assert_rewrite", "RemovedInPytest4Warning", - "PytestUnknownMarkWarning", "Session", "set_trace", "skip", diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 929ae8d607f..a24289f576f 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -630,7 +630,7 @@ def test(): 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(