-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
nose
has long been in maintenance only mode, and we support it via our nose.py
plugin.
The nose.py
plugin however is not trivial to maintain, and it dips its toes in a few places through the code:
Lines 382 to 389 in d949b3f
def isnosetest(self, obj: object) -> bool: | |
"""Look for the __test__ attribute, which is applied by the | |
@nose.tools.istest decorator. | |
""" | |
# We explicitly check for "is True" here to not mistakenly treat | |
# classes with a custom __getattr__ returning something truthy (like a | |
# function) as test classes. | |
return safe_getattr(obj, "__test__", False) is True |
Line 534 in d949b3f
has_nose = self.config.pluginmanager.has_plugin("nose") |
Line 869 in d949b3f
has_nose = self.config.pluginmanager.has_plugin("nose") |
pytest/src/_pytest/unittest.py
Lines 353 to 355 in d949b3f
# This is actually only needed for nose, which reuses unittest.SkipTest for | |
# its own nose.SkipTest. For unittest TestCases, SkipTest is already | |
# handled internally, and doesn't reach here. |
Also it can cause confusion such as in #9549.
Perhaps it is time to deprecate nose
, and eventually removing it? If there is a large test suite which still requires this, they can just pin pytest until the last version that still supports it.
It could become a third party plugin. but unless someone volunteers to do it, I don't think it should be pytest's core responsibility to maintain such a plugin.
How to deprecate it?
nose.py
actually just handles calling the nose-specific setup
and teardown
methods:
Lines 9 to 27 in d949b3f
@hookimpl(trylast=True) | |
def pytest_runtest_setup(item: Item) -> None: | |
if not isinstance(item, Function): | |
return | |
# Don't do nose style setup/teardown on direct unittest style classes. | |
if isinstance(item, TestCaseFunction): | |
return | |
# Capture the narrowed type of item for the teardown closure, | |
# see https://github.com/python/mypy/issues/2608 | |
func = item | |
call_optional(func.obj, "setup") | |
func.addfinalizer(lambda: call_optional(func.obj, "teardown")) | |
# NOTE: Module- and class-level fixtures are handled in python.py | |
# with `pluginmanager.has_plugin("nose")` checks. | |
# It would have been nicer to implement them outside of core, but | |
# it's not straightforward. |
We could issue a deprecation warning when one of those functions are called, which AFAIK is basically what our nose support entails.