diff --git a/changelog/5584.breaking.rst b/changelog/5584.breaking.rst new file mode 100644 index 000000000..990d04cb1 --- /dev/null +++ b/changelog/5584.breaking.rst @@ -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 `__ +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 `__. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 828a2af27..775dd556a 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1024,10 +1024,45 @@ When set (regardless of value), pytest will use color in terminal output. Exceptions ---------- -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`: diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 30ea52965..d1e27ecad 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -381,8 +381,6 @@ custom error message. Internal pytest warnings ------------------------ - - 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 @@ -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 features. -The following warning types are used by pytest and are part of the public API: - -.. 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: +The full list of warnings is listed in :ref:`the reference documentation `. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index b24fc5fd3..9521a7a17 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -46,7 +46,6 @@ from _pytest.compat import TYPE_CHECKING from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config.argparsing import Parser -from _pytest.deprecated import FILLFUNCARGS from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.deprecated import FUNCARGNAMES from _pytest.mark import ParameterSet @@ -361,7 +360,8 @@ def reorder_items_atscope( def fillfixtures(function: "Function") -> None: """ 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: request = function._request except AttributeError: diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index cf3da400a..d21c4d4d9 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -12,7 +12,6 @@ import py.path from pluggy import HookspecMarker from .deprecated import COLLECT_DIRECTORY_HOOK -from .deprecated import WARNING_CAPTURED_HOOK from _pytest.compat import 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( warning_message: "warnings.WarningMessage", when: "Literal['config', 'collect', 'runtest']", diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 7bbea54d2..5d71a7725 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,6 +1,5 @@ """ generic mechanism for marking and selecting python functions. """ import typing -import warnings from typing import AbstractSet from typing import List from typing import Optional @@ -23,8 +22,6 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config import UsageError 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 if TYPE_CHECKING: @@ -181,12 +178,14 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: if keywordexpr.startswith("-"): # 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:] selectuntil = False if keywordexpr[-1:] == ":": # 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 keywordexpr = keywordexpr[:-1] diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 494b92eff..6f3b88da8 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -78,7 +78,7 @@ class PytestUnhandledCoroutineWarning(PytestWarning): class PytestUnknownMarkWarning(PytestWarning): """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" diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 33b01b797..3a8f2d8b3 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -105,6 +105,8 @@ def catch_warnings_for_item( warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) + warnings.filterwarnings("error", category=pytest.PytestDeprecationWarning) + # filters should have this precedence: mark, cmdline options, ini # filters should be applied in the inverse order of precedence for arg in inifilters: diff --git a/src/pytest/collect.py b/src/pytest/collect.py index ec9c2d8b4..55b4b9b35 100644 --- a/src/pytest/collect.py +++ b/src/pytest/collect.py @@ -1,11 +1,9 @@ import sys -import warnings from types import ModuleType from typing import Any from typing import List import pytest -from _pytest.deprecated import PYTEST_COLLECT_MODULE COLLECT_FAKEMODULE_ATTRIBUTES = [ @@ -33,7 +31,8 @@ class FakeCollectModule(ModuleType): def __getattr__(self, name: str) -> Any: if name not in self.__all__: 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) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index e558c7f67..2a386e2c6 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -302,10 +302,10 @@ class TestGeneralUsage: pass class MyCollector(pytest.File): def collect(self): - return [MyItem(name="xyz", parent=self)] + return [MyItem.from_parent(name="xyz", parent=self)] def pytest_collect_file(path, parent): if path.basename.startswith("conftest"): - return MyCollector(path, parent) + return MyCollector.from_parent(fspath=path, parent=parent) """ ) result = testdir.runpytest(c.basename + "::" + "xyz") diff --git a/testing/conftest.py b/testing/conftest.py index f43018948..a667be42f 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -116,11 +116,11 @@ def dummy_yaml_custom_test(testdir): def pytest_collect_file(parent, path): 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): def collect(self): - yield YamlItem(self.fspath.basename, self) + yield YamlItem.from_parent(name=self.fspath.basename, parent=self) class YamlItem(pytest.Item): def runtest(self): diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 7cce092df..f4de3b5d9 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -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 # false positive due to dynamic attribute def test_pytest_collect_module_deprecated(attribute): @@ -117,7 +118,8 @@ def test_node_direct_ctor_warning() -> None: 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( pytest.PytestDeprecationWarning, match="The `_fillfuncargs` function is deprecated", @@ -125,6 +127,7 @@ def test__fillfuncargs_is_deprecated() -> None: pytest._fillfuncargs(mock.Mock()) +@pytest.mark.skip(reason="should be reintroduced in 6.1: #7361") def test_minus_k_dash_is_deprecated(testdir) -> None: threepass = testdir.makepyfile( test_threepass=""" @@ -137,6 +140,7 @@ def test_minus_k_dash_is_deprecated(testdir) -> None: 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: threepass = testdir.makepyfile( test_threepass=""" diff --git a/testing/example_scripts/fixtures/custom_item/conftest.py b/testing/example_scripts/fixtures/custom_item/conftest.py index 25299d726..161934b58 100644 --- a/testing/example_scripts/fixtures/custom_item/conftest.py +++ b/testing/example_scripts/fixtures/custom_item/conftest.py @@ -1,10 +1,15 @@ import pytest -class CustomItem(pytest.Item, pytest.File): +class CustomItem(pytest.Item): def runtest(self): pass +class CustomFile(pytest.File): + def collect(self): + yield CustomItem.from_parent(name="foo", parent=self) + + def pytest_collect_file(path, parent): - return CustomItem(path, parent) + return CustomFile.from_parent(fspath=path, parent=parent) diff --git a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index aa5d87831..a053a638a 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -3,11 +3,11 @@ import pytest class MyFile(pytest.File): def collect(self): - return [MyItem("hello", parent=self)] + return [MyItem.from_parent(name="hello", parent=self)] def pytest_collect_file(path, parent): - return MyFile(path, parent) + return MyFile.from_parent(fspath=path, parent=parent) class MyItem(pytest.Item): diff --git a/testing/python/collect.py b/testing/python/collect.py index 80c962d70..f64a14629 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -762,7 +762,7 @@ class TestConftestCustomization: pass def pytest_pycollect_makemodule(path, parent): if path.basename == "test_xyz.py": - return MyModule(path, parent) + return MyModule.from_parent(fspath=path, parent=parent) """ ) testdir.makepyfile("def test_some(): pass") @@ -836,7 +836,7 @@ class TestConftestCustomization: pass def pytest_pycollect_makeitem(collector, name, obj): if name == "some": - return MyFunction(name, collector) + return MyFunction.from_parent(name=name, parent=collector) """ ) testdir.makepyfile("def some(): pass") @@ -873,7 +873,7 @@ class TestConftestCustomization: def pytest_collect_file(path, parent): if path.ext == ".narf": - return Module(path, parent)""" + return Module.from_parent(fspath=path, parent=parent)""" ) testdir.makefile( ".narf", diff --git a/testing/test_collection.py b/testing/test_collection.py index 6bab509d0..3e01e296b 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -282,7 +282,7 @@ class TestPrunetraceback: """ import pytest def pytest_collect_file(path, parent): - return MyFile(path, parent) + return MyFile.from_parent(fspath=path, parent=parent) class MyError(Exception): pass class MyFile(pytest.File): @@ -401,7 +401,7 @@ class TestCustomConftests: pass def pytest_collect_file(path, parent): if path.ext == ".py": - return MyModule(path, parent) + return MyModule.from_parent(fspath=path, parent=parent) """ ) testdir.mkdir("sub") @@ -419,7 +419,7 @@ class TestCustomConftests: pass def pytest_collect_file(path, parent): if path.ext == ".py": - return MyModule1(path, parent) + return MyModule1.from_parent(fspath=path, parent=parent) """ ) conf1.move(sub1.join(conf1.basename)) @@ -430,7 +430,7 @@ class TestCustomConftests: pass def pytest_collect_file(path, parent): if path.ext == ".py": - return MyModule2(path, parent) + return MyModule2.from_parent(fspath=path, parent=parent) """ ) conf2.move(sub2.join(conf2.basename)) @@ -537,10 +537,10 @@ class TestSession: return # ok class SpecialFile(pytest.File): def collect(self): - return [SpecialItem(name="check", parent=self)] + return [SpecialItem.from_parent(name="check", parent=self)] def pytest_collect_file(path, parent): if path.basename == %r: - return SpecialFile(fspath=path, parent=parent) + return SpecialFile.from_parent(fspath=path, parent=parent) """ % p.basename ) @@ -761,18 +761,23 @@ def test_matchnodes_two_collections_same_file(testdir): class Plugin2(object): def pytest_collect_file(self, path, parent): if path.ext == ".abc": - return MyFile2(path, parent) + return MyFile2.from_parent(fspath=path, parent=parent) def pytest_collect_file(path, parent): 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): 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): def runtest(self): @@ -783,7 +788,7 @@ def test_matchnodes_two_collections_same_file(testdir): result = testdir.runpytest() assert result.ret == 0 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*"]) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index eb8475ca5..01eeccdcd 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -906,11 +906,8 @@ class TestNonPython: import pytest def pytest_collect_file(path, parent): if path.ext == ".xyz": - return MyItem(path, parent) + return MyItem.from_parent(name=path.basename, parent=parent) class MyItem(pytest.Item): - def __init__(self, path, parent): - super(MyItem, self).__init__(path.basename, parent) - self.fspath = path def runtest(self): raise ValueError(42) def repr_failure(self, excinfo): @@ -1336,14 +1333,14 @@ def test_fancy_items_regression(testdir, run_and_parse): class FunCollector(pytest.File): def collect(self): return [ - FunItem('a', self), - NoFunItem('a', self), - NoFunItem('b', self), + FunItem.from_parent(name='a', parent=self), + NoFunItem.from_parent(name='a', parent=self), + NoFunItem.from_parent(name='b', parent=self), ] def pytest_collect_file(path, parent): if path.check(ext='.py'): - return FunCollector(path, parent) + return FunCollector.from_parent(fspath=path, parent=parent) """ ) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 61de0b3e1..92182ff38 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1126,7 +1126,7 @@ def test_xfail_item(testdir): pytest.xfail("Expected Failure") def pytest_collect_file(path, parent): - return MyItem("foo", parent) + return MyItem.from_parent(name="foo", parent=parent) """ ) result = testdir.inline_run() @@ -1206,7 +1206,7 @@ def test_mark_xfail_item(testdir): assert False def pytest_collect_file(path, parent): - return MyItem("foo", parent) + return MyItem.from_parent(name="foo", parent=parent) """ ) result = testdir.inline_run() diff --git a/testing/test_warnings.py b/testing/test_warnings.py index e21ccf42a..c36681802 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -520,9 +520,6 @@ class TestDeprecationWarningsByDefault: @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): """This ensures that PytestDeprecationWarnings raised by pytest are turned into errors.