From 370df813324d36cd8e1c74b028ee1e9b3ba9651d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 9 Oct 2022 10:43:33 -0300 Subject: [PATCH] Emit nose deprecation warnings for plain setup/teardown methods --- doc/en/deprecations.rst | 9 +++++---- src/_pytest/deprecated.py | 11 ++++++++++- src/_pytest/python.py | 21 ++++++++++++++++++++- testing/deprecated_test.py | 37 ++++++++++++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index fcbbc2491..62a31fdad 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -25,7 +25,7 @@ Support for tests written for nose Support for running tests written for `nose `__ is now deprecated. -`nose` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills +``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills over the code base (see :issue:`9886` for more details). setup/teardown @@ -70,11 +70,12 @@ Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`x ... +This is easy to do in an entire code base by doing a simple find/replace. + @with_setup ^^^^^^^^^^^ -Code using `@with_setup `_ will also need to be ported to a supported -pytest style: +Code using `@with_setup `_ such as this: .. code-block:: python @@ -93,7 +94,7 @@ pytest style: def test_foo(): ... -One way to do it is using a fixture: +Will also need to be ported to a supported pytest style. One way to do it is using a fixture: .. code-block:: python diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 38af4b24c..fc0d9b149 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -25,7 +25,16 @@ DEPRECATED_EXTERNAL_PLUGINS = { NOSE_SUPPORT = UnformattedWarning( PytestRemovedIn8Warning, "Support for nose tests is deprecated and will be removed in a future release.\n" - "{nodeid} is using nose method: `{method}` ({stage})", + "{nodeid} is using nose method: `{method}` ({stage})\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", +) + +NOSE_SUPPORT_METHOD = UnformattedWarning( + PytestRemovedIn8Warning, + "Support for nose tests is deprecated and will be removed in a future release.\n" + "{nodeid} is using nose-specific method: `{method}(self)`\n" + "To remove this warning, rename it to `{method}_method(self)`\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", ) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 91054f370..8cad83b8f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -59,6 +59,7 @@ from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.deprecated import INSTANCE_COLLECTOR +from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.fixtures import FuncFixtureInfo from _pytest.main import Session from _pytest.mark import MARK_GEN @@ -872,19 +873,23 @@ class Class(PyCollector): """Inject a hidden autouse, function scoped fixture into the collected class object that invokes setup_method/teardown_method if either or both are available. - Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with + Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ has_nose = self.config.pluginmanager.has_plugin("nose") setup_name = "setup_method" setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) + emit_nose_setup_warning = False if setup_method is None and has_nose: setup_name = "setup" + emit_nose_setup_warning = True setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) teardown_name = "teardown_method" teardown_method = getattr(self.obj, teardown_name, None) + emit_nose_teardown_warning = False if teardown_method is None and has_nose: teardown_name = "teardown" + emit_nose_teardown_warning = True teardown_method = getattr(self.obj, teardown_name, None) if setup_method is None and teardown_method is None: return @@ -900,10 +905,24 @@ class Class(PyCollector): if setup_method is not None: func = getattr(self, setup_name) _call_with_optional_argument(func, method) + if emit_nose_setup_warning: + warnings.warn( + NOSE_SUPPORT_METHOD.format( + nodeid=request.node.nodeid, method="setup" + ), + stacklevel=2, + ) yield if teardown_method is not None: func = getattr(self, teardown_name) _call_with_optional_argument(func, method) + if emit_nose_teardown_warning: + warnings.warn( + NOSE_SUPPORT_METHOD.format( + nodeid=request.node.nodeid, method="teardown" + ), + stacklevel=2, + ) self.obj.__pytest_setup_method = xunit_setup_method_fixture diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index dcc397f8a..795b52a3f 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -233,7 +233,8 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None: from _pytest.python import Instance # noqa: F401 -def test_nose_deprecated(pytester: Pytester) -> None: +@pytest.mark.filterwarnings("default") +def test_nose_deprecated_with_setup(pytester: Pytester) -> None: pytest.importorskip("nose") pytester.makepyfile( """ @@ -253,9 +254,39 @@ def test_nose_deprecated(pytester: Pytester) -> None: output = pytester.runpytest() message = [ "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", - "*test_nose_deprecated.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)", + "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)", "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", - "*test_nose_deprecated.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)", + "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)", + ] + output.stdout.fnmatch_lines(message) + output.assert_outcomes(passed=1) + + +@pytest.mark.filterwarnings("default") +def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None: + pytest.importorskip("nose") + pytester.makepyfile( + """ + class Test: + + def setup(self): + ... + + def teardown(self): + ... + + def test(self): + ... + """ + ) + output = pytester.runpytest() + message = [ + "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", + "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`", + "*To remove this warning, rename it to `setup_method(self)`", + "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", + "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `teardown(self)`", + "*To remove this warning, rename it to `teardown_method(self)`", ] output.stdout.fnmatch_lines(message) output.assert_outcomes(passed=1)