From 4db3878a515a929a15dc7ca6087b97be82e77240 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 13 Jun 2020 09:33:10 -0300 Subject: [PATCH 1/6] Turn pytest deprecation warnings into errors Closes #5584 --- src/_pytest/warnings.py | 2 ++ testing/acceptance_test.py | 4 +-- testing/conftest.py | 4 +-- .../fixtures/custom_item/conftest.py | 9 ++++-- .../conftest.py | 4 +-- testing/python/collect.py | 6 ++-- testing/test_collection.py | 31 +++++++++++-------- testing/test_junitxml.py | 13 +++----- testing/test_skipping.py | 4 +-- testing/test_warnings.py | 3 -- 10 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 33b01b79707..3a8f2d8b33f 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/testing/acceptance_test.py b/testing/acceptance_test.py index e558c7f6781..2a386e2c6e4 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -302,10 +302,10 @@ def runtest(self): 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 f430189489e..a667be42fcb 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/example_scripts/fixtures/custom_item/conftest.py b/testing/example_scripts/fixtures/custom_item/conftest.py index 25299d72690..161934b58f7 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 aa5d878313c..a053a638a9f 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 @@ 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 e98a21f1cc5..d4a84ffea47 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -758,7 +758,7 @@ class MyModule(pytest.Module): 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") @@ -832,7 +832,7 @@ class MyFunction(pytest.Function): 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") @@ -869,7 +869,7 @@ def find_module(self, name, path=None): 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 d7a9b0439aa..232417d0b70 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -282,7 +282,7 @@ def test_custom_repr_failure(self, testdir): """ 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 MyModule(pytest.Module): 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 MyModule1(pytest.Module): 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 MyModule2(pytest.Module): 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 @@ def runtest(self): 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 pytest_configure(config): 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 runtest(self): 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 5e5826b236a..9799adbbad3 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -906,11 +906,8 @@ def test_summing_simple(self, testdir, run_and_parse, xunit_family): 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 runtest(self): 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 8fceb37aa71..2ec7114f18e 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1098,7 +1098,7 @@ def runtest(self): 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() @@ -1178,7 +1178,7 @@ def runtest(self): 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 e21ccf42ae8..c3668180216 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -520,9 +520,6 @@ def test_hidden_by_system(self, testdir, monkeypatch): @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. From 12d0e3522eb1cb50e59708caffdea3a19ad8b824 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 13 Jun 2020 09:38:31 -0300 Subject: [PATCH 2/6] Suppress MINUS_K_COLON and MINUS_K_DASH warnings until 6.1 They were introduced near 6.0 release where we turn deprecation warnings into errors, so we need to turn those warnings off until 6.1. Related to #7361 --- src/_pytest/mark/__init__.py | 9 ++++----- testing/deprecated_test.py | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 7bbea54d297..5d71a772526 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 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/testing/deprecated_test.py b/testing/deprecated_test.py index 7cce092df6c..9002c216635 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -125,6 +125,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 +138,7 @@ def test_three(): assert 1 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=""" From 1185a2b5c50532ef2314bb11d7e7c0d3e5acc4e4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 13 Jun 2020 09:42:34 -0300 Subject: [PATCH 3/6] Add CHANGELOG entry for #5584 --- changelog/5584.breaking.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 changelog/5584.breaking.rst diff --git a/changelog/5584.breaking.rst b/changelog/5584.breaking.rst new file mode 100644 index 00000000000..68b9f4c8c09 --- /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 stop +gap 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 `__. From d134e76f848f9d87e320a773bc3ee61d36ed100c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 11 Jul 2020 11:42:19 -0300 Subject: [PATCH 4/6] Also pospone deprecation of FILLFUNCARGS, PYTEST_COLLECT_MODULE and WARNING_CAPTURED_HOOK --- src/_pytest/fixtures.py | 4 ++-- src/_pytest/hookspec.py | 5 +++-- src/pytest/collect.py | 5 ++--- testing/deprecated_test.py | 4 +++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index cedcd462559..dc109e275df 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -46,7 +46,6 @@ 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 8c88b66cb17..c4bf89350dd 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -12,7 +12,6 @@ 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: @@ -738,7 +737,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/collect.py b/src/pytest/collect.py index ec9c2d8b4e1..55b4b9b359c 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 @@ def __dir__(self) -> List[str]: 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/deprecated_test.py b/testing/deprecated_test.py index 9002c216635..f4de3b5d9c5 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -28,6 +28,7 @@ def test(): ) +@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 @@ class MockConfig: 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", From 90d543394262abe31ee38840d60b5027dc99ae51 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 11 Jul 2020 11:58:56 -0300 Subject: [PATCH 5/6] Move warnings listing to reference docs --- doc/en/reference.rst | 41 +++++++++++++++++++++++++++++++++--- doc/en/warnings.rst | 31 +-------------------------- src/_pytest/warning_types.py | 2 +- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/doc/en/reference.rst b/doc/en/reference.rst index d81ba9bc7ea..b753514f046 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1021,10 +1021,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 30ea529650c..d1e27ecad21 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/warning_types.py b/src/_pytest/warning_types.py index 494b92efff6..6f3b88da8b8 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" From fe269ce8c0f770242f5eaa25659be2230def82b4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jul 2020 19:14:53 -0300 Subject: [PATCH 6/6] Update changelog/5584.breaking.rst Co-authored-by: Hugo van Kemenade --- changelog/5584.breaking.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/5584.breaking.rst b/changelog/5584.breaking.rst index 68b9f4c8c09..990d04cb15b 100644 --- a/changelog/5584.breaking.rst +++ b/changelog/5584.breaking.rst @@ -8,8 +8,8 @@ instead of warning messages. `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 stop -gap measure by adding this to your ``pytest.ini`` file: +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