Change pytest deprecation warnings into errors for 6.0 release (#7362)

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
This commit is contained in:
Bruno Oliveira 2020-07-22 21:36:51 -03:00 committed by GitHub
parent a9799f0b35
commit 7ec6401ffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 122 additions and 84 deletions

View File

@ -0,0 +1,23 @@
**PytestDeprecationWarning are now errors by default.**
Following our plan to remove deprecated features with as little disruption as
possible, all warnings of type ``PytestDeprecationWarning`` now generate errors
instead of warning messages.
**The affected features will be effectively removed in pytest 6.1**, so please consult the
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__
section in the docs for directions on how to update existing code.
In the pytest ``6.0.X`` series, it is possible to change the errors back into warnings as a
stopgap measure by adding this to your ``pytest.ini`` file:
.. code-block:: ini
[pytest]
filterwarnings =
ignore::pytest.PytestDeprecationWarning
But this will stop working when pytest ``6.1`` is released.
**If you have concerns** about the removal of a specific feature, please add a
comment to `#5584 <https://github.com/pytest-dev/pytest/issues/5584>`__.

View File

@ -1024,10 +1024,45 @@ When set (regardless of value), pytest will use color in terminal output.
Exceptions Exceptions
---------- ----------
UsageError
~~~~~~~~~~
.. autoclass:: _pytest.config.UsageError() .. autoclass:: _pytest.config.UsageError()
:show-inheritance:
.. _`warnings ref`:
Warnings
--------
Custom warnings generated in some situations such as improper usage or deprecated features.
.. autoclass:: pytest.PytestWarning
:show-inheritance:
.. autoclass:: pytest.PytestAssertRewriteWarning
:show-inheritance:
.. autoclass:: pytest.PytestCacheWarning
:show-inheritance:
.. autoclass:: pytest.PytestCollectionWarning
:show-inheritance:
.. autoclass:: pytest.PytestConfigWarning
:show-inheritance:
.. autoclass:: pytest.PytestDeprecationWarning
:show-inheritance:
.. autoclass:: pytest.PytestExperimentalApiWarning
:show-inheritance:
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
:show-inheritance:
.. autoclass:: pytest.PytestUnknownMarkWarning
:show-inheritance:
Consult the :ref:`internal-warnings` section in the documentation for more information.
.. _`ini options ref`: .. _`ini options ref`:

View File

@ -381,8 +381,6 @@ custom error message.
Internal pytest warnings Internal pytest warnings
------------------------ ------------------------
pytest may generate its own warnings in some situations, such as improper usage or deprecated features. pytest may generate its own warnings in some situations, such as improper usage or deprecated features.
For example, pytest will emit a warning if it encounters a class that matches :confval:`python_classes` but also For example, pytest will emit a warning if it encounters a class that matches :confval:`python_classes` but also
@ -415,31 +413,4 @@ These warnings might be filtered using the same builtin mechanisms used to filte
Please read our :ref:`backwards-compatibility` to learn how we proceed about deprecating and eventually removing Please read our :ref:`backwards-compatibility` to learn how we proceed about deprecating and eventually removing
features. features.
The following warning types are used by pytest and are part of the public API: The full list of warnings is listed in :ref:`the reference documentation <warnings ref>`.
.. autoclass:: pytest.PytestWarning
:show-inheritance:
.. autoclass:: pytest.PytestAssertRewriteWarning
:show-inheritance:
.. autoclass:: pytest.PytestCacheWarning
:show-inheritance:
.. autoclass:: pytest.PytestCollectionWarning
:show-inheritance:
.. autoclass:: pytest.PytestConfigWarning
:show-inheritance:
.. autoclass:: pytest.PytestDeprecationWarning
:show-inheritance:
.. autoclass:: pytest.PytestExperimentalApiWarning
:show-inheritance:
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
:show-inheritance:
.. autoclass:: pytest.PytestUnknownMarkWarning
:show-inheritance:

View File

@ -46,7 +46,6 @@ from _pytest.compat import TYPE_CHECKING
from _pytest.config import _PluggyPlugin from _pytest.config import _PluggyPlugin
from _pytest.config import Config from _pytest.config import Config
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.deprecated import FILLFUNCARGS
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
from _pytest.deprecated import FUNCARGNAMES from _pytest.deprecated import FUNCARGNAMES
from _pytest.mark import ParameterSet from _pytest.mark import ParameterSet
@ -361,7 +360,8 @@ def reorder_items_atscope(
def fillfixtures(function: "Function") -> None: def fillfixtures(function: "Function") -> None:
""" fill missing funcargs for a test function. """ """ fill missing funcargs for a test function. """
warnings.warn(FILLFUNCARGS, stacklevel=2) # Uncomment this after 6.0 release (#7361)
# warnings.warn(FILLFUNCARGS, stacklevel=2)
try: try:
request = function._request request = function._request
except AttributeError: except AttributeError:

View File

@ -12,7 +12,6 @@ import py.path
from pluggy import HookspecMarker from pluggy import HookspecMarker
from .deprecated import COLLECT_DIRECTORY_HOOK from .deprecated import COLLECT_DIRECTORY_HOOK
from .deprecated import WARNING_CAPTURED_HOOK
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
@ -737,7 +736,9 @@ def pytest_terminal_summary(
""" """
@hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK) # Uncomment this after 6.0 release (#7361)
# @hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK)
@hookspec(historic=True)
def pytest_warning_captured( def pytest_warning_captured(
warning_message: "warnings.WarningMessage", warning_message: "warnings.WarningMessage",
when: "Literal['config', 'collect', 'runtest']", when: "Literal['config', 'collect', 'runtest']",

View File

@ -1,6 +1,5 @@
""" generic mechanism for marking and selecting python functions. """ """ generic mechanism for marking and selecting python functions. """
import typing import typing
import warnings
from typing import AbstractSet from typing import AbstractSet
from typing import List from typing import List
from typing import Optional from typing import Optional
@ -23,8 +22,6 @@ from _pytest.config import ExitCode
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config import UsageError from _pytest.config import UsageError
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.deprecated import MINUS_K_COLON
from _pytest.deprecated import MINUS_K_DASH
from _pytest.store import StoreKey from _pytest.store import StoreKey
if TYPE_CHECKING: if TYPE_CHECKING:
@ -181,12 +178,14 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
if keywordexpr.startswith("-"): if keywordexpr.startswith("-"):
# To be removed in pytest 7.0.0. # To be removed in pytest 7.0.0.
warnings.warn(MINUS_K_DASH, stacklevel=2) # Uncomment this after 6.0 release (#7361)
# warnings.warn(MINUS_K_DASH, stacklevel=2)
keywordexpr = "not " + keywordexpr[1:] keywordexpr = "not " + keywordexpr[1:]
selectuntil = False selectuntil = False
if keywordexpr[-1:] == ":": if keywordexpr[-1:] == ":":
# To be removed in pytest 7.0.0. # To be removed in pytest 7.0.0.
warnings.warn(MINUS_K_COLON, stacklevel=2) # Uncomment this after 6.0 release (#7361)
# warnings.warn(MINUS_K_COLON, stacklevel=2)
selectuntil = True selectuntil = True
keywordexpr = keywordexpr[:-1] keywordexpr = keywordexpr[:-1]

View File

@ -78,7 +78,7 @@ class PytestUnhandledCoroutineWarning(PytestWarning):
class PytestUnknownMarkWarning(PytestWarning): class PytestUnknownMarkWarning(PytestWarning):
"""Warning emitted on use of unknown markers. """Warning emitted on use of unknown markers.
See https://docs.pytest.org/en/stable/mark.html for details. See :ref:`mark` for details.
""" """
__module__ = "pytest" __module__ = "pytest"

View File

@ -105,6 +105,8 @@ def catch_warnings_for_item(
warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=DeprecationWarning)
warnings.filterwarnings("always", category=PendingDeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning)
warnings.filterwarnings("error", category=pytest.PytestDeprecationWarning)
# filters should have this precedence: mark, cmdline options, ini # filters should have this precedence: mark, cmdline options, ini
# filters should be applied in the inverse order of precedence # filters should be applied in the inverse order of precedence
for arg in inifilters: for arg in inifilters:

View File

@ -1,11 +1,9 @@
import sys import sys
import warnings
from types import ModuleType from types import ModuleType
from typing import Any from typing import Any
from typing import List from typing import List
import pytest import pytest
from _pytest.deprecated import PYTEST_COLLECT_MODULE
COLLECT_FAKEMODULE_ATTRIBUTES = [ COLLECT_FAKEMODULE_ATTRIBUTES = [
@ -33,7 +31,8 @@ class FakeCollectModule(ModuleType):
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
if name not in self.__all__: if name not in self.__all__:
raise AttributeError(name) raise AttributeError(name)
warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2) # Uncomment this after 6.0 release (#7361)
# warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2)
return getattr(pytest, name) return getattr(pytest, name)

View File

@ -302,10 +302,10 @@ class TestGeneralUsage:
pass pass
class MyCollector(pytest.File): class MyCollector(pytest.File):
def collect(self): def collect(self):
return [MyItem(name="xyz", parent=self)] return [MyItem.from_parent(name="xyz", parent=self)]
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.basename.startswith("conftest"): if path.basename.startswith("conftest"):
return MyCollector(path, parent) return MyCollector.from_parent(fspath=path, parent=parent)
""" """
) )
result = testdir.runpytest(c.basename + "::" + "xyz") result = testdir.runpytest(c.basename + "::" + "xyz")

View File

@ -116,11 +116,11 @@ def dummy_yaml_custom_test(testdir):
def pytest_collect_file(parent, path): def pytest_collect_file(parent, path):
if path.ext == ".yaml" and path.basename.startswith("test"): if path.ext == ".yaml" and path.basename.startswith("test"):
return YamlFile(path, parent) return YamlFile.from_parent(fspath=path, parent=parent)
class YamlFile(pytest.File): class YamlFile(pytest.File):
def collect(self): def collect(self):
yield YamlItem(self.fspath.basename, self) yield YamlItem.from_parent(name=self.fspath.basename, parent=self)
class YamlItem(pytest.Item): class YamlItem(pytest.Item):
def runtest(self): def runtest(self):

View File

@ -28,6 +28,7 @@ def test_resultlog_is_deprecated(testdir):
) )
@pytest.mark.skip(reason="should be reintroduced in 6.1: #7361")
@pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore @pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore
# false positive due to dynamic attribute # false positive due to dynamic attribute
def test_pytest_collect_module_deprecated(attribute): def test_pytest_collect_module_deprecated(attribute):
@ -117,7 +118,8 @@ def test_node_direct_ctor_warning() -> None:
assert w[0].filename == __file__ assert w[0].filename == __file__
def test__fillfuncargs_is_deprecated() -> None: @pytest.mark.skip(reason="should be reintroduced in 6.1: #7361")
def test_fillfuncargs_is_deprecated() -> None:
with pytest.warns( with pytest.warns(
pytest.PytestDeprecationWarning, pytest.PytestDeprecationWarning,
match="The `_fillfuncargs` function is deprecated", match="The `_fillfuncargs` function is deprecated",
@ -125,6 +127,7 @@ def test__fillfuncargs_is_deprecated() -> None:
pytest._fillfuncargs(mock.Mock()) pytest._fillfuncargs(mock.Mock())
@pytest.mark.skip(reason="should be reintroduced in 6.1: #7361")
def test_minus_k_dash_is_deprecated(testdir) -> None: def test_minus_k_dash_is_deprecated(testdir) -> None:
threepass = testdir.makepyfile( threepass = testdir.makepyfile(
test_threepass=""" test_threepass="""
@ -137,6 +140,7 @@ def test_minus_k_dash_is_deprecated(testdir) -> None:
result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"]) result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"])
@pytest.mark.skip(reason="should be reintroduced in 6.1: #7361")
def test_minus_k_colon_is_deprecated(testdir) -> None: def test_minus_k_colon_is_deprecated(testdir) -> None:
threepass = testdir.makepyfile( threepass = testdir.makepyfile(
test_threepass=""" test_threepass="""

View File

@ -1,10 +1,15 @@
import pytest import pytest
class CustomItem(pytest.Item, pytest.File): class CustomItem(pytest.Item):
def runtest(self): def runtest(self):
pass pass
class CustomFile(pytest.File):
def collect(self):
yield CustomItem.from_parent(name="foo", parent=self)
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
return CustomItem(path, parent) return CustomFile.from_parent(fspath=path, parent=parent)

View File

@ -3,11 +3,11 @@ import pytest
class MyFile(pytest.File): class MyFile(pytest.File):
def collect(self): def collect(self):
return [MyItem("hello", parent=self)] return [MyItem.from_parent(name="hello", parent=self)]
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
return MyFile(path, parent) return MyFile.from_parent(fspath=path, parent=parent)
class MyItem(pytest.Item): class MyItem(pytest.Item):

View File

@ -762,7 +762,7 @@ class TestConftestCustomization:
pass pass
def pytest_pycollect_makemodule(path, parent): def pytest_pycollect_makemodule(path, parent):
if path.basename == "test_xyz.py": if path.basename == "test_xyz.py":
return MyModule(path, parent) return MyModule.from_parent(fspath=path, parent=parent)
""" """
) )
testdir.makepyfile("def test_some(): pass") testdir.makepyfile("def test_some(): pass")
@ -836,7 +836,7 @@ class TestConftestCustomization:
pass pass
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
if name == "some": if name == "some":
return MyFunction(name, collector) return MyFunction.from_parent(name=name, parent=collector)
""" """
) )
testdir.makepyfile("def some(): pass") testdir.makepyfile("def some(): pass")
@ -873,7 +873,7 @@ class TestConftestCustomization:
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.ext == ".narf": if path.ext == ".narf":
return Module(path, parent)""" return Module.from_parent(fspath=path, parent=parent)"""
) )
testdir.makefile( testdir.makefile(
".narf", ".narf",

View File

@ -282,7 +282,7 @@ class TestPrunetraceback:
""" """
import pytest import pytest
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
return MyFile(path, parent) return MyFile.from_parent(fspath=path, parent=parent)
class MyError(Exception): class MyError(Exception):
pass pass
class MyFile(pytest.File): class MyFile(pytest.File):
@ -401,7 +401,7 @@ class TestCustomConftests:
pass pass
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.ext == ".py": if path.ext == ".py":
return MyModule(path, parent) return MyModule.from_parent(fspath=path, parent=parent)
""" """
) )
testdir.mkdir("sub") testdir.mkdir("sub")
@ -419,7 +419,7 @@ class TestCustomConftests:
pass pass
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.ext == ".py": if path.ext == ".py":
return MyModule1(path, parent) return MyModule1.from_parent(fspath=path, parent=parent)
""" """
) )
conf1.move(sub1.join(conf1.basename)) conf1.move(sub1.join(conf1.basename))
@ -430,7 +430,7 @@ class TestCustomConftests:
pass pass
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.ext == ".py": if path.ext == ".py":
return MyModule2(path, parent) return MyModule2.from_parent(fspath=path, parent=parent)
""" """
) )
conf2.move(sub2.join(conf2.basename)) conf2.move(sub2.join(conf2.basename))
@ -537,10 +537,10 @@ class TestSession:
return # ok return # ok
class SpecialFile(pytest.File): class SpecialFile(pytest.File):
def collect(self): def collect(self):
return [SpecialItem(name="check", parent=self)] return [SpecialItem.from_parent(name="check", parent=self)]
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.basename == %r: if path.basename == %r:
return SpecialFile(fspath=path, parent=parent) return SpecialFile.from_parent(fspath=path, parent=parent)
""" """
% p.basename % p.basename
) )
@ -761,18 +761,23 @@ def test_matchnodes_two_collections_same_file(testdir):
class Plugin2(object): class Plugin2(object):
def pytest_collect_file(self, path, parent): def pytest_collect_file(self, path, parent):
if path.ext == ".abc": if path.ext == ".abc":
return MyFile2(path, parent) return MyFile2.from_parent(fspath=path, parent=parent)
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.ext == ".abc": if path.ext == ".abc":
return MyFile1(path, parent) return MyFile1.from_parent(fspath=path, parent=parent)
class MyFile1(pytest.File):
def collect(self):
yield Item1.from_parent(name="item1", parent=self)
class MyFile1(pytest.Item, pytest.File):
def runtest(self):
pass
class MyFile2(pytest.File): class MyFile2(pytest.File):
def collect(self): def collect(self):
return [Item2("hello", parent=self)] yield Item2.from_parent(name="item2", parent=self)
class Item1(pytest.Item):
def runtest(self):
pass
class Item2(pytest.Item): class Item2(pytest.Item):
def runtest(self): def runtest(self):
@ -783,7 +788,7 @@ def test_matchnodes_two_collections_same_file(testdir):
result = testdir.runpytest() result = testdir.runpytest()
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines(["*2 passed*"]) result.stdout.fnmatch_lines(["*2 passed*"])
res = testdir.runpytest("%s::hello" % p.basename) res = testdir.runpytest("%s::item2" % p.basename)
res.stdout.fnmatch_lines(["*1 passed*"]) res.stdout.fnmatch_lines(["*1 passed*"])

View File

@ -906,11 +906,8 @@ class TestNonPython:
import pytest import pytest
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.ext == ".xyz": if path.ext == ".xyz":
return MyItem(path, parent) return MyItem.from_parent(name=path.basename, parent=parent)
class MyItem(pytest.Item): class MyItem(pytest.Item):
def __init__(self, path, parent):
super(MyItem, self).__init__(path.basename, parent)
self.fspath = path
def runtest(self): def runtest(self):
raise ValueError(42) raise ValueError(42)
def repr_failure(self, excinfo): def repr_failure(self, excinfo):
@ -1336,14 +1333,14 @@ def test_fancy_items_regression(testdir, run_and_parse):
class FunCollector(pytest.File): class FunCollector(pytest.File):
def collect(self): def collect(self):
return [ return [
FunItem('a', self), FunItem.from_parent(name='a', parent=self),
NoFunItem('a', self), NoFunItem.from_parent(name='a', parent=self),
NoFunItem('b', self), NoFunItem.from_parent(name='b', parent=self),
] ]
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.check(ext='.py'): if path.check(ext='.py'):
return FunCollector(path, parent) return FunCollector.from_parent(fspath=path, parent=parent)
""" """
) )

View File

@ -1126,7 +1126,7 @@ def test_xfail_item(testdir):
pytest.xfail("Expected Failure") pytest.xfail("Expected Failure")
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
return MyItem("foo", parent) return MyItem.from_parent(name="foo", parent=parent)
""" """
) )
result = testdir.inline_run() result = testdir.inline_run()
@ -1206,7 +1206,7 @@ def test_mark_xfail_item(testdir):
assert False assert False
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
return MyItem("foo", parent) return MyItem.from_parent(name="foo", parent=parent)
""" """
) )
result = testdir.inline_run() result = testdir.inline_run()

View File

@ -520,9 +520,6 @@ class TestDeprecationWarningsByDefault:
@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"])
@pytest.mark.skip(
reason="This test should be enabled again before pytest 6.0 is released"
)
def test_deprecation_warning_as_error(testdir, change_default): def test_deprecation_warning_as_error(testdir, change_default):
"""This ensures that PytestDeprecationWarnings raised by pytest are turned into errors. """This ensures that PytestDeprecationWarnings raised by pytest are turned into errors.