Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/3079.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove support for yield tests - they are fundamentally broken since collection and test execution were separated.
1 change: 1 addition & 0 deletions changelog/3616.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove the deprecated compat properties for node.Class/Function/Module - use pytest... now.
1 change: 0 additions & 1 deletion src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,6 @@ def safe_str(v):
COLLECT_FAKEMODULE_ATTRIBUTES = (
"Collector",
"Module",
"Generator",
"Function",
"Instance",
"Session",
Expand Down
17 changes: 1 addition & 16 deletions src/_pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,13 @@
"pass a list of arguments instead."
)

YIELD_TESTS = RemovedInPytest4Warning(
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
)
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"

CACHED_SETUP = RemovedInPytest4Warning(
"cached_setup is deprecated and will be removed in a future release. "
"Use standard fixture functions instead."
)

COMPAT_PROPERTY = UnformattedWarning(
RemovedInPytest4Warning,
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
)

CUSTOM_CLASS = UnformattedWarning(
RemovedInPytest4Warning,
'use of special named "{name}" objects in collectors of type "{type_name}" to '
"customize the created nodes is deprecated. "
"Use pytest_pycollect_makeitem(...) to create custom "
"collection nodes instead.",
)

FUNCARG_PREFIX = UnformattedWarning(
RemovedInPytest4Warning,
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
Expand Down
6 changes: 0 additions & 6 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,17 +1303,11 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
if holderobj in self._holderobjseen:
return

from _pytest.nodes import _CompatProperty

self._holderobjseen.add(holderobj)
autousenames = []
for name in dir(holderobj):
# The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getatt() ignores such exceptions.
maybe_property = safe_getattr(type(holderobj), name, None)
if isinstance(maybe_property, _CompatProperty):
# deprecated
continue
obj = safe_getattr(holderobj, name, None)
marker = getfixturemarker(obj)
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
Expand Down
35 changes: 0 additions & 35 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import os
import warnings

import attr
import py
import six

Expand Down Expand Up @@ -56,22 +55,6 @@ def ischildnode(baseid, nodeid):
return node_parts[: len(base_parts)] == base_parts


@attr.s
class _CompatProperty(object):
name = attr.ib()

def __get__(self, obj, owner):
if obj is None:
return self

from _pytest.deprecated import COMPAT_PROPERTY

warnings.warn(
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
)
return getattr(__import__("pytest"), self.name)


class Node(object):
""" base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes."""
Expand Down Expand Up @@ -119,24 +102,6 @@ def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)

Module = _CompatProperty("Module")
Class = _CompatProperty("Class")
Instance = _CompatProperty("Instance")
Function = _CompatProperty("Function")
File = _CompatProperty("File")
Item = _CompatProperty("Item")

def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty):
return getattr(__import__("pytest"), name)
else:
from _pytest.deprecated import CUSTOM_CLASS

cls = getattr(self, name)
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
return cls

def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None))

Expand Down
12 changes: 0 additions & 12 deletions src/_pytest/nose.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ def pytest_runtest_makereport(item, call):
@hookimpl(trylast=True)
def pytest_runtest_setup(item):
if is_potential_nosetest(item):
if isinstance(item.parent, python.Generator):
gen = item.parent
if not hasattr(gen, "_nosegensetup"):
call_optional(gen.obj, "setup")
if isinstance(gen.parent, python.Instance):
call_optional(gen.parent.obj, "setup")
gen._nosegensetup = True
if not call_optional(item.obj, "setup"):
# call module level setup if there is no object level one
call_optional(item.parent.obj, "setup")
Expand All @@ -53,11 +46,6 @@ def teardown_nose(item):
# del item.parent._nosegensetup


def pytest_make_collect_report(collector):
if isinstance(collector, python.Generator):
call_optional(collector.obj, "setup")


def is_potential_nosetest(item):
# extra check needed since we do not do nose style setup/teardown
# on direct unittest style classes
Expand Down
62 changes: 9 additions & 53 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from _pytest.compat import STRING_TYPES
from _pytest.config import hookimpl
from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import normalize_mark_list
from _pytest.mark.structures import transfer_markers
Expand Down Expand Up @@ -199,7 +200,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
# nothing was collected elsewhere, let's do it here
if safe_isclass(obj):
if collector.istestclass(obj, name):
Class = collector._getcustomclass("Class")
outcome.force_result(Class(name, parent=collector))
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it
Expand All @@ -219,7 +219,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
res = Generator(name, parent=collector)
res = Function(name, parent=collector)
reason = deprecated.YIELD_TESTS.format(name=name)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestWarning(reason))
else:
res = list(collector._genfunctions(name, obj))
outcome.force_result(res)
Expand Down Expand Up @@ -408,7 +411,6 @@ def _genfunctions(self, name, funcobj):
else:
self.ihook.pytest_generate_tests(metafunc=metafunc)

Function = self._getcustomclass("Function")
if not metafunc._calls:
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
else:
Expand Down Expand Up @@ -648,7 +650,7 @@ def collect(self):
)
)
return []
return [self._getcustomclass("Instance")(name="()", parent=self)]
return [Instance(name="()", parent=self)]

def setup(self):
setup_class = _get_xunit_func(self.obj, "setup_class")
Expand Down Expand Up @@ -739,51 +741,6 @@ def repr_failure(self, excinfo, outerr=None):
return self._repr_failure_py(excinfo, style=style)


class Generator(FunctionMixin, PyCollector):
def collect(self):

# test generators are seen as collectors but they also
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
from _pytest import deprecated

self.warn(deprecated.YIELD_TESTS)

self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
values = []
seen = {}
_Function = self._getcustomclass("Function")
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
if not callable(call):
raise TypeError("%r yielded non callable test %r" % (self.obj, call))
if name is None:
name = "[%d]" % i
else:
name = "['%s']" % name
if name in seen:
raise ValueError(
"%r generated tests with non-unique name %r" % (self, name)
)
seen[name] = True
values.append(_Function(name, self, args=args, callobj=call))
return values

def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
# explicit naming
if isinstance(obj[0], six.string_types):
name = obj[0]
obj = obj[1:]
else:
name = None
call, args = obj[0], obj[1:]
return name, call, args


def hasinit(obj):
init = getattr(obj, "__init__", None)
if init:
Expand Down Expand Up @@ -1326,20 +1283,19 @@ def _showfixtures_main(config, session):
tw.line(" %s: no docstring available" % (loc,), red=True)


def write_docstring(tw, doc):
INDENT = " "
def write_docstring(tw, doc, indent=" "):
doc = doc.rstrip()
if "\n" in doc:
firstline, rest = doc.split("\n", 1)
else:
firstline, rest = doc, ""

if firstline.strip():
tw.line(INDENT + firstline.strip())
tw.line(indent + firstline.strip())

if rest:
for line in dedent(rest).split("\n"):
tw.write(INDENT + line + "\n")
tw.write(indent + line + "\n")


class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
Expand Down
2 changes: 0 additions & 2 deletions src/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from _pytest.outcomes import xfail
from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import Generator
from _pytest.python import Instance
from _pytest.python import Module
from _pytest.python import Package
Expand Down Expand Up @@ -57,7 +56,6 @@
"fixture",
"freeze_includes",
"Function",
"Generator",
"hookimpl",
"hookspec",
"importorskip",
Expand Down
71 changes: 0 additions & 71 deletions testing/deprecated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,6 @@
pytestmark = pytest.mark.pytester_example_path("deprecated")


def test_yield_tests_deprecation(testdir):
testdir.makepyfile(
"""
def func1(arg, arg2):
assert arg == arg2
def test_gen():
yield "m1", func1, 15, 3*5
yield "m2", func1, 42, 6*7
def test_gen2():
for k in range(10):
yield func1, 1, 1
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_yield_tests_deprecation.py:3:*yield tests are deprecated*",
"*test_yield_tests_deprecation.py:6:*yield tests are deprecated*",
"*2 passed*",
]
)
assert result.stdout.str().count("yield tests are deprecated") == 2


def test_compat_properties_deprecation(testdir):
testdir.makepyfile(
"""
def test_foo(request):
print(request.node.Module)
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, "
"please use pytest.Module instead*",
"*1 passed, 1 warnings in*",
]
)


def test_cached_setup_deprecation(testdir):
testdir.makepyfile(
"""
Expand All @@ -72,36 +31,6 @@ def test_foo(fix):
)


def test_custom_class_deprecation(testdir):
testdir.makeconftest(
"""
import pytest

class MyModule(pytest.Module):

class Class(pytest.Class):
pass

def pytest_pycollect_makemodule(path, parent):
return MyModule(path, parent)
"""
)
testdir.makepyfile(
"""
class Test:
def test_foo(self):
pass
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
'*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*',
"*1 passed, 1 warnings in*",
]
)


def test_funcarg_prefix_deprecation(testdir):
testdir.makepyfile(
"""
Expand Down
Loading