Skip to content

Commit 9f9f6ee

Browse files
remove most of markertransfer
keywords are still a big issue
1 parent 58fc918 commit 9f9f6ee

File tree

8 files changed

+52
-421
lines changed

8 files changed

+52
-421
lines changed

src/_pytest/config/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,11 @@ def parse_hookimpl_opts(self, plugin, name):
270270
opts = {}
271271

272272
if opts is not None:
273+
# TODO: DeprecationWarning, people should use hookimpl
274+
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
273275
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
274-
opts.setdefault(name, hasattr(method, name))
276+
277+
opts.setdefault(name, hasattr(method, name) or name in known_marks)
275278
return opts
276279

277280
def parse_hookspec_opts(self, module_or_class, name):

src/_pytest/fixtures.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,19 +1207,20 @@ def pytest_generate_tests(self, metafunc):
12071207
if faclist:
12081208
fixturedef = faclist[-1]
12091209
if fixturedef.params is not None:
1210-
parametrize_func = getattr(metafunc.function, "parametrize", None)
1211-
if parametrize_func is not None:
1212-
parametrize_func = parametrize_func.combined
1213-
func_params = getattr(parametrize_func, "args", [[None]])
1214-
func_kwargs = getattr(parametrize_func, "kwargs", {})
1215-
# skip directly parametrized arguments
1216-
if "argnames" in func_kwargs:
1217-
argnames = parametrize_func.kwargs["argnames"]
1210+
markers = list(metafunc.definition.iter_markers("parametrize"))
1211+
for parametrize_mark in markers:
1212+
if "argnames" in parametrize_mark.kwargs:
1213+
argnames = parametrize_mark.kwargs["argnames"]
1214+
else:
1215+
argnames = parametrize_mark.args[0]
1216+
1217+
if not isinstance(argnames, (tuple, list)):
1218+
argnames = [
1219+
x.strip() for x in argnames.split(",") if x.strip()
1220+
]
1221+
if argname in argnames:
1222+
break
12181223
else:
1219-
argnames = func_params[0]
1220-
if not isinstance(argnames, (tuple, list)):
1221-
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
1222-
if argname not in func_params and argname not in argnames:
12231224
metafunc.parametrize(
12241225
argname,
12251226
fixturedef.params,

src/_pytest/mark/__init__.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,10 @@
1111
from .structures import MARK_GEN
1212
from .structures import MarkDecorator
1313
from .structures import MarkGenerator
14-
from .structures import MarkInfo
1514
from .structures import ParameterSet
16-
from .structures import transfer_markers
1715
from _pytest.config import UsageError
1816

19-
__all__ = [
20-
"Mark",
21-
"MarkInfo",
22-
"MarkDecorator",
23-
"MarkGenerator",
24-
"transfer_markers",
25-
"get_empty_parameterset_mark",
26-
]
17+
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
2718

2819

2920
def param(*values, **kw):

src/_pytest/mark/structures.py

Lines changed: 8 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import inspect
22
import warnings
33
from collections import namedtuple
4-
from functools import reduce
54
from operator import attrgetter
65

76
import attr
87
import six
9-
from six.moves import map
108

119
from ..compat import ascii_escaped
1210
from ..compat import getfslineno
1311
from ..compat import MappingMixin
1412
from ..compat import NOTSET
15-
from ..deprecated import MARK_INFO_ATTRIBUTE
1613
from _pytest.outcomes import fail
1714

1815

@@ -233,11 +230,7 @@ def __call__(self, *args, **kwargs):
233230
func = args[0]
234231
is_class = inspect.isclass(func)
235232
if len(args) == 1 and (istestfunc(func) or is_class):
236-
if is_class:
237-
store_mark(func, self.mark)
238-
else:
239-
store_legacy_markinfo(func, self.mark)
240-
store_mark(func, self.mark)
233+
store_mark(func, self.mark)
241234
return func
242235
return self.with_args(*args, **kwargs)
243236

@@ -259,7 +252,13 @@ def normalize_mark_list(mark_list):
259252
:type mark_list: List[Union[Mark, Markdecorator]]
260253
:rtype: List[Mark]
261254
"""
262-
return [getattr(mark, "mark", mark) for mark in mark_list] # unpack MarkDecorator
255+
extracted = [
256+
getattr(mark, "mark", mark) for mark in mark_list
257+
] # unpack MarkDecorator
258+
for mark in extracted:
259+
if not isinstance(mark, Mark):
260+
raise TypeError("got {!r} instead of Mark".format(mark))
261+
return [x for x in extracted if isinstance(x, Mark)]
263262

264263

265264
def store_mark(obj, mark):
@@ -272,90 +271,6 @@ def store_mark(obj, mark):
272271
obj.pytestmark = get_unpacked_marks(obj) + [mark]
273272

274273

275-
def store_legacy_markinfo(func, mark):
276-
"""create the legacy MarkInfo objects and put them onto the function
277-
"""
278-
if not isinstance(mark, Mark):
279-
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
280-
holder = getattr(func, mark.name, None)
281-
if holder is None:
282-
holder = MarkInfo.for_mark(mark)
283-
setattr(func, mark.name, holder)
284-
elif isinstance(holder, MarkInfo):
285-
holder.add_mark(mark)
286-
287-
288-
def transfer_markers(funcobj, cls, mod):
289-
"""
290-
this function transfers class level markers and module level markers
291-
into function level markinfo objects
292-
293-
this is the main reason why marks are so broken
294-
the resolution will involve phasing out function level MarkInfo objects
295-
296-
"""
297-
for obj in (cls, mod):
298-
for mark in get_unpacked_marks(obj):
299-
if not _marked(funcobj, mark):
300-
store_legacy_markinfo(funcobj, mark)
301-
302-
303-
def _marked(func, mark):
304-
""" Returns True if :func: is already marked with :mark:, False otherwise.
305-
This can happen if marker is applied to class and the test file is
306-
invoked more than once.
307-
"""
308-
try:
309-
func_mark = getattr(func, getattr(mark, "combined", mark).name)
310-
except AttributeError:
311-
return False
312-
return any(mark == info.combined for info in func_mark)
313-
314-
315-
@attr.s(repr=False)
316-
class MarkInfo(object):
317-
""" Marking object created by :class:`MarkDecorator` instances. """
318-
319-
_marks = attr.ib(converter=list)
320-
321-
@_marks.validator
322-
def validate_marks(self, attribute, value):
323-
for item in value:
324-
if not isinstance(item, Mark):
325-
raise ValueError(
326-
"MarkInfo expects Mark instances, got {!r} ({!r})".format(
327-
item, type(item)
328-
)
329-
)
330-
331-
combined = attr.ib(
332-
repr=False,
333-
default=attr.Factory(
334-
lambda self: reduce(Mark.combined_with, self._marks), takes_self=True
335-
),
336-
)
337-
338-
name = alias("combined.name", warning=MARK_INFO_ATTRIBUTE)
339-
args = alias("combined.args", warning=MARK_INFO_ATTRIBUTE)
340-
kwargs = alias("combined.kwargs", warning=MARK_INFO_ATTRIBUTE)
341-
342-
@classmethod
343-
def for_mark(cls, mark):
344-
return cls([mark])
345-
346-
def __repr__(self):
347-
return "<MarkInfo {!r}>".format(self.combined)
348-
349-
def add_mark(self, mark):
350-
""" add a MarkInfo with the given args and kwargs. """
351-
self._marks.append(mark)
352-
self.combined = self.combined.combined_with(mark)
353-
354-
def __iter__(self):
355-
""" yield MarkInfo objects each relating to a marking-call. """
356-
return map(MarkInfo.for_mark, self._marks)
357-
358-
359274
class MarkGenerator(object):
360275
""" Factory for :class:`MarkDecorator` objects - exposed as
361276
a ``pytest.mark`` singleton instance. Example::

src/_pytest/nodes.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import _pytest._code
1212
from _pytest.compat import getfslineno
13-
from _pytest.mark.structures import MarkInfo
1413
from _pytest.mark.structures import NodeKeywords
1514
from _pytest.outcomes import fail
1615

@@ -211,20 +210,6 @@ def get_closest_marker(self, name, default=None):
211210
"""
212211
return next(self.iter_markers(name=name), default)
213212

214-
def get_marker(self, name):
215-
""" get a marker object from this node or None if
216-
the node doesn't have a marker with that name.
217-
218-
.. deprecated:: 3.6
219-
This function has been deprecated in favor of
220-
:meth:`Node.get_closest_marker <_pytest.nodes.Node.get_closest_marker>` and
221-
:meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`, see :ref:`update marker code`
222-
for more details.
223-
"""
224-
markers = list(self.iter_markers(name=name))
225-
if markers:
226-
return MarkInfo(markers)
227-
228213
def listextrakeywords(self):
229214
""" Return a set of all extra keywords in self and any parents."""
230215
extra_keywords = set()

src/_pytest/python.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
from _pytest.mark import MARK_GEN
4242
from _pytest.mark.structures import get_unpacked_marks
4343
from _pytest.mark.structures import normalize_mark_list
44-
from _pytest.mark.structures import transfer_markers
4544
from _pytest.outcomes import fail
4645
from _pytest.pathlib import parts
4746
from _pytest.warning_types import PytestWarning
@@ -125,10 +124,10 @@ def pytest_generate_tests(metafunc):
125124
# those alternative spellings are common - raise a specific error to alert
126125
# the user
127126
alt_spellings = ["parameterize", "parametrise", "parameterise"]
128-
for attr in alt_spellings:
129-
if hasattr(metafunc.function, attr):
127+
for mark_name in alt_spellings:
128+
if metafunc.definition.get_closest_marker(mark_name):
130129
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
131-
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
130+
fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
132131
for marker in metafunc.definition.iter_markers(name="parametrize"):
133132
metafunc.parametrize(*marker.args, **marker.kwargs)
134133

@@ -385,7 +384,6 @@ def _genfunctions(self, name, funcobj):
385384
module = self.getparent(Module).obj
386385
clscol = self.getparent(Class)
387386
cls = clscol and clscol.obj or None
388-
transfer_markers(funcobj, cls, module)
389387
fm = self.session._fixturemanager
390388

391389
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
@@ -1291,6 +1289,18 @@ def __init__(
12911289
if keywords:
12921290
self.keywords.update(keywords)
12931291

1292+
# todo: this is a hell of a hack
1293+
self.keywords.update(
1294+
dict.fromkeys(
1295+
[
1296+
mark.name
1297+
for mark in self.iter_markers()
1298+
if mark.name not in self.keywords
1299+
],
1300+
True,
1301+
)
1302+
)
1303+
12941304
if fixtureinfo is None:
12951305
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
12961306
self, self.obj, self.cls, funcargs=not self._isyieldedfunction()

src/_pytest/unittest.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
from _pytest.outcomes import xfail
1515
from _pytest.python import Class
1616
from _pytest.python import Function
17-
from _pytest.python import Module
18-
from _pytest.python import transfer_markers
1917

2018

2119
def pytest_pycollect_makeitem(collector, name, obj):
@@ -54,14 +52,12 @@ def collect(self):
5452
return
5553
self.session._fixturemanager.parsefactories(self, unittest=True)
5654
loader = TestLoader()
57-
module = self.getparent(Module).obj
5855
foundsomething = False
5956
for name in loader.getTestCaseNames(self.obj):
6057
x = getattr(self.obj, name)
6158
if not getattr(x, "__test__", True):
6259
continue
6360
funcobj = getimfunc(x)
64-
transfer_markers(funcobj, cls, module)
6561
yield TestCaseFunction(name, parent=self, callobj=funcobj)
6662
foundsomething = True
6763

0 commit comments

Comments
 (0)