Skip to content

Commit 7eb28f9

Browse files
remove yield tests and compat properties
1 parent 1d86247 commit 7eb28f9

18 files changed

+29
-691
lines changed

changelog/3079.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove support for yield tests - they are fundamentally broken since collection and test execution were separated.

changelog/3616.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove the deprecated compat properties for node.Class/Function/Module - use pytest... now.

src/_pytest/compat.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ def safe_str(v):
389389
COLLECT_FAKEMODULE_ATTRIBUTES = (
390390
"Collector",
391391
"Module",
392-
"Generator",
393392
"Function",
394393
"Instance",
395394
"Session",

src/_pytest/deprecated.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,13 @@
2222
"pass a list of arguments instead."
2323
)
2424

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

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

34-
COMPAT_PROPERTY = UnformattedWarning(
35-
RemovedInPytest4Warning,
36-
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
37-
)
38-
39-
CUSTOM_CLASS = UnformattedWarning(
40-
RemovedInPytest4Warning,
41-
'use of special named "{name}" objects in collectors of type "{type_name}" to '
42-
"customize the created nodes is deprecated. "
43-
"Use pytest_pycollect_makeitem(...) to create custom "
44-
"collection nodes instead.",
45-
)
46-
4732
FUNCARG_PREFIX = UnformattedWarning(
4833
RemovedInPytest4Warning,
4934
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '

src/_pytest/fixtures.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,17 +1303,11 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
13031303
if holderobj in self._holderobjseen:
13041304
return
13051305

1306-
from _pytest.nodes import _CompatProperty
1307-
13081306
self._holderobjseen.add(holderobj)
13091307
autousenames = []
13101308
for name in dir(holderobj):
13111309
# The attribute can be an arbitrary descriptor, so the attribute
13121310
# access below can raise. safe_getatt() ignores such exceptions.
1313-
maybe_property = safe_getattr(type(holderobj), name, None)
1314-
if isinstance(maybe_property, _CompatProperty):
1315-
# deprecated
1316-
continue
13171311
obj = safe_getattr(holderobj, name, None)
13181312
marker = getfixturemarker(obj)
13191313
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)

src/_pytest/nodes.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
import warnings
77

8-
import attr
98
import py
109
import six
1110

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

5857

59-
@attr.s
60-
class _CompatProperty(object):
61-
name = attr.ib()
62-
63-
def __get__(self, obj, owner):
64-
if obj is None:
65-
return self
66-
67-
from _pytest.deprecated import COMPAT_PROPERTY
68-
69-
warnings.warn(
70-
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
71-
)
72-
return getattr(__import__("pytest"), self.name)
73-
74-
7558
class Node(object):
7659
""" base class for Collector and Item the test collection tree.
7760
Collector subclasses have children, Items are terminal nodes."""
@@ -119,24 +102,6 @@ def ihook(self):
119102
""" fspath sensitive hook proxy used to call pytest hooks"""
120103
return self.session.gethookproxy(self.fspath)
121104

122-
Module = _CompatProperty("Module")
123-
Class = _CompatProperty("Class")
124-
Instance = _CompatProperty("Instance")
125-
Function = _CompatProperty("Function")
126-
File = _CompatProperty("File")
127-
Item = _CompatProperty("Item")
128-
129-
def _getcustomclass(self, name):
130-
maybe_compatprop = getattr(type(self), name)
131-
if isinstance(maybe_compatprop, _CompatProperty):
132-
return getattr(__import__("pytest"), name)
133-
else:
134-
from _pytest.deprecated import CUSTOM_CLASS
135-
136-
cls = getattr(self, name)
137-
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
138-
return cls
139-
140105
def __repr__(self):
141106
return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None))
142107

src/_pytest/nose.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ def pytest_runtest_makereport(item, call):
3030
@hookimpl(trylast=True)
3131
def pytest_runtest_setup(item):
3232
if is_potential_nosetest(item):
33-
if isinstance(item.parent, python.Generator):
34-
gen = item.parent
35-
if not hasattr(gen, "_nosegensetup"):
36-
call_optional(gen.obj, "setup")
37-
if isinstance(gen.parent, python.Instance):
38-
call_optional(gen.parent.obj, "setup")
39-
gen._nosegensetup = True
4033
if not call_optional(item.obj, "setup"):
4134
# call module level setup if there is no object level one
4235
call_optional(item.parent.obj, "setup")
@@ -53,11 +46,6 @@ def teardown_nose(item):
5346
# del item.parent._nosegensetup
5447

5548

56-
def pytest_make_collect_report(collector):
57-
if isinstance(collector, python.Generator):
58-
call_optional(collector.obj, "setup")
59-
60-
6149
def is_potential_nosetest(item):
6250
# extra check needed since we do not do nose style setup/teardown
6351
# on direct unittest style classes

src/_pytest/python.py

Lines changed: 9 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from _pytest.compat import STRING_TYPES
3939
from _pytest.config import hookimpl
4040
from _pytest.main import FSHookProxy
41+
from _pytest.mark import MARK_GEN
4142
from _pytest.mark.structures import get_unpacked_marks
4243
from _pytest.mark.structures import normalize_mark_list
4344
from _pytest.mark.structures import transfer_markers
@@ -199,7 +200,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
199200
# nothing was collected elsewhere, let's do it here
200201
if safe_isclass(obj):
201202
if collector.istestclass(obj, name):
202-
Class = collector._getcustomclass("Class")
203203
outcome.force_result(Class(name, parent=collector))
204204
elif collector.istestfunction(obj, name):
205205
# mock seems to store unbound methods (issue473), normalize it
@@ -219,7 +219,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
219219
)
220220
elif getattr(obj, "__test__", True):
221221
if is_generator(obj):
222-
res = Generator(name, parent=collector)
222+
res = Function(name, parent=collector)
223+
reason = deprecated.YIELD_TESTS.format(name=name)
224+
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
225+
res.warn(PytestWarning(reason))
223226
else:
224227
res = list(collector._genfunctions(name, obj))
225228
outcome.force_result(res)
@@ -408,7 +411,6 @@ def _genfunctions(self, name, funcobj):
408411
else:
409412
self.ihook.pytest_generate_tests(metafunc=metafunc)
410413

411-
Function = self._getcustomclass("Function")
412414
if not metafunc._calls:
413415
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
414416
else:
@@ -648,7 +650,7 @@ def collect(self):
648650
)
649651
)
650652
return []
651-
return [self._getcustomclass("Instance")(name="()", parent=self)]
653+
return [Instance(name="()", parent=self)]
652654

653655
def setup(self):
654656
setup_class = _get_xunit_func(self.obj, "setup_class")
@@ -739,51 +741,6 @@ def repr_failure(self, excinfo, outerr=None):
739741
return self._repr_failure_py(excinfo, style=style)
740742

741743

742-
class Generator(FunctionMixin, PyCollector):
743-
def collect(self):
744-
745-
# test generators are seen as collectors but they also
746-
# invoke setup/teardown on popular request
747-
# (induced by the common "test_*" naming shared with normal tests)
748-
from _pytest import deprecated
749-
750-
self.warn(deprecated.YIELD_TESTS)
751-
752-
self.session._setupstate.prepare(self)
753-
# see FunctionMixin.setup and test_setupstate_is_preserved_134
754-
self._preservedparent = self.parent.obj
755-
values = []
756-
seen = {}
757-
_Function = self._getcustomclass("Function")
758-
for i, x in enumerate(self.obj()):
759-
name, call, args = self.getcallargs(x)
760-
if not callable(call):
761-
raise TypeError("%r yielded non callable test %r" % (self.obj, call))
762-
if name is None:
763-
name = "[%d]" % i
764-
else:
765-
name = "['%s']" % name
766-
if name in seen:
767-
raise ValueError(
768-
"%r generated tests with non-unique name %r" % (self, name)
769-
)
770-
seen[name] = True
771-
values.append(_Function(name, self, args=args, callobj=call))
772-
return values
773-
774-
def getcallargs(self, obj):
775-
if not isinstance(obj, (tuple, list)):
776-
obj = (obj,)
777-
# explicit naming
778-
if isinstance(obj[0], six.string_types):
779-
name = obj[0]
780-
obj = obj[1:]
781-
else:
782-
name = None
783-
call, args = obj[0], obj[1:]
784-
return name, call, args
785-
786-
787744
def hasinit(obj):
788745
init = getattr(obj, "__init__", None)
789746
if init:
@@ -1326,20 +1283,19 @@ def _showfixtures_main(config, session):
13261283
tw.line(" %s: no docstring available" % (loc,), red=True)
13271284

13281285

1329-
def write_docstring(tw, doc):
1330-
INDENT = " "
1286+
def write_docstring(tw, doc, indent=" "):
13311287
doc = doc.rstrip()
13321288
if "\n" in doc:
13331289
firstline, rest = doc.split("\n", 1)
13341290
else:
13351291
firstline, rest = doc, ""
13361292

13371293
if firstline.strip():
1338-
tw.line(INDENT + firstline.strip())
1294+
tw.line(indent + firstline.strip())
13391295

13401296
if rest:
13411297
for line in dedent(rest).split("\n"):
1342-
tw.write(INDENT + line + "\n")
1298+
tw.write(indent + line + "\n")
13431299

13441300

13451301
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):

src/pytest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from _pytest.outcomes import xfail
2929
from _pytest.python import Class
3030
from _pytest.python import Function
31-
from _pytest.python import Generator
3231
from _pytest.python import Instance
3332
from _pytest.python import Module
3433
from _pytest.python import Package
@@ -57,7 +56,6 @@
5756
"fixture",
5857
"freeze_includes",
5958
"Function",
60-
"Generator",
6159
"hookimpl",
6260
"hookspec",
6361
"importorskip",

testing/deprecated_test.py

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,6 @@
1010
pytestmark = pytest.mark.pytester_example_path("deprecated")
1111

1212

13-
def test_yield_tests_deprecation(testdir):
14-
testdir.makepyfile(
15-
"""
16-
def func1(arg, arg2):
17-
assert arg == arg2
18-
def test_gen():
19-
yield "m1", func1, 15, 3*5
20-
yield "m2", func1, 42, 6*7
21-
def test_gen2():
22-
for k in range(10):
23-
yield func1, 1, 1
24-
"""
25-
)
26-
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
27-
result.stdout.fnmatch_lines(
28-
[
29-
"*test_yield_tests_deprecation.py:3:*yield tests are deprecated*",
30-
"*test_yield_tests_deprecation.py:6:*yield tests are deprecated*",
31-
"*2 passed*",
32-
]
33-
)
34-
assert result.stdout.str().count("yield tests are deprecated") == 2
35-
36-
37-
def test_compat_properties_deprecation(testdir):
38-
testdir.makepyfile(
39-
"""
40-
def test_foo(request):
41-
print(request.node.Module)
42-
"""
43-
)
44-
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
45-
result.stdout.fnmatch_lines(
46-
[
47-
"*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, "
48-
"please use pytest.Module instead*",
49-
"*1 passed, 1 warnings in*",
50-
]
51-
)
52-
53-
5413
def test_cached_setup_deprecation(testdir):
5514
testdir.makepyfile(
5615
"""
@@ -72,36 +31,6 @@ def test_foo(fix):
7231
)
7332

7433

75-
def test_custom_class_deprecation(testdir):
76-
testdir.makeconftest(
77-
"""
78-
import pytest
79-
80-
class MyModule(pytest.Module):
81-
82-
class Class(pytest.Class):
83-
pass
84-
85-
def pytest_pycollect_makemodule(path, parent):
86-
return MyModule(path, parent)
87-
"""
88-
)
89-
testdir.makepyfile(
90-
"""
91-
class Test:
92-
def test_foo(self):
93-
pass
94-
"""
95-
)
96-
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
97-
result.stdout.fnmatch_lines(
98-
[
99-
'*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*',
100-
"*1 passed, 1 warnings in*",
101-
]
102-
)
103-
104-
10534
def test_funcarg_prefix_deprecation(testdir):
10635
testdir.makepyfile(
10736
"""

0 commit comments

Comments
 (0)