Skip to content

Commit 01db0f1

Browse files
authored
Merge pull request #1923 from RonnyPfannschmidt/mark-internal-value
use consistent inner repressentation for marks
2 parents dc16fe2 + 4552424 commit 01db0f1

File tree

5 files changed

+56
-47
lines changed

5 files changed

+56
-47
lines changed

_pytest/compat.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def isclass(object):
111111

112112
if _PY3:
113113
import codecs
114-
114+
imap = map
115115
STRING_TYPES = bytes, str
116116

117117
def _escape_strings(val):
@@ -145,6 +145,8 @@ def _escape_strings(val):
145145
else:
146146
STRING_TYPES = bytes, str, unicode
147147

148+
from itertools import imap # NOQA
149+
148150
def _escape_strings(val):
149151
"""In py2 bytes and str are the same type, so return if it's a bytes
150152
object, return it unchanged if it is a full ascii string,
@@ -213,4 +215,4 @@ def _is_unittest_unexpected_success_a_failure():
213215
Changed in version 3.4: Returns False if there were any
214216
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
215217
"""
216-
return sys.version_info >= (3, 4)
218+
return sys.version_info >= (3, 4)

_pytest/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ def add_marker(self, marker):
356356
"""
357357
from _pytest.mark import MarkDecorator
358358
if isinstance(marker, py.builtin._basestring):
359-
marker = MarkDecorator(marker)
359+
marker = getattr(pytest.mark, marker)
360360
elif not isinstance(marker, MarkDecorator):
361361
raise ValueError("is not a string or pytest.mark.* Marker")
362362
self.keywords[marker.name] = marker

_pytest/mark.py

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
""" generic mechanism for marking and selecting python functions. """
22
import inspect
3+
from collections import namedtuple
4+
from operator import attrgetter
5+
from .compat import imap
6+
7+
def alias(name):
8+
return property(attrgetter(name), doc='alias for ' + name)
39

410

511
class MarkerError(Exception):
@@ -182,7 +188,7 @@ def __getattr__(self, name):
182188
raise AttributeError("Marker name must NOT start with underscore")
183189
if hasattr(self, '_config'):
184190
self._check(name)
185-
return MarkDecorator(name)
191+
return MarkDecorator(Mark(name, (), {}))
186192

187193
def _check(self, name):
188194
try:
@@ -235,19 +241,20 @@ def test_function():
235241
additional keyword or positional arguments.
236242
237243
"""
238-
def __init__(self, name, args=None, kwargs=None):
239-
self.name = name
240-
self.args = args or ()
241-
self.kwargs = kwargs or {}
244+
def __init__(self, mark):
245+
assert isinstance(mark, Mark), repr(mark)
246+
self.mark = mark
247+
248+
name = alias('mark.name')
249+
args = alias('mark.args')
250+
kwargs = alias('mark.kwargs')
242251

243252
@property
244253
def markname(self):
245254
return self.name # for backward-compat (2.4.1 had this attr)
246255

247256
def __repr__(self):
248-
d = self.__dict__.copy()
249-
name = d.pop('name')
250-
return "<MarkDecorator %r %r>" % (name, d)
257+
return "<MarkDecorator %r>" % self.mark
251258

252259
def __call__(self, *args, **kwargs):
253260
""" if passed a single callable argument: decorate it with mark info.
@@ -270,17 +277,14 @@ def __call__(self, *args, **kwargs):
270277
else:
271278
holder = getattr(func, self.name, None)
272279
if holder is None:
273-
holder = MarkInfo(
274-
self.name, self.args, self.kwargs
275-
)
280+
holder = MarkInfo(self.mark)
276281
setattr(func, self.name, holder)
277282
else:
278-
holder.add(self.args, self.kwargs)
283+
holder.add_mark(self.mark)
279284
return func
280-
kw = self.kwargs.copy()
281-
kw.update(kwargs)
282-
args = self.args + args
283-
return self.__class__(self.name, args=args, kwargs=kw)
285+
286+
mark = Mark(self.name, args, kwargs)
287+
return self.__class__(self.mark.combined_with(mark))
284288

285289

286290
def extract_argvalue(maybe_marked_args):
@@ -291,36 +295,41 @@ def extract_argvalue(maybe_marked_args):
291295
newmarks = {}
292296
argval = maybe_marked_args
293297
while isinstance(argval, MarkDecorator):
294-
newmark = MarkDecorator(argval.markname,
295-
argval.args[:-1], argval.kwargs)
296-
newmarks[newmark.markname] = newmark
298+
newmark = MarkDecorator(Mark(
299+
argval.markname, argval.args[:-1], argval.kwargs))
300+
newmarks[newmark.name] = newmark
297301
argval = argval.args[-1]
298302
return argval, newmarks
299303

300304

301-
class MarkInfo:
305+
class Mark(namedtuple('Mark', 'name, args, kwargs')):
306+
307+
def combined_with(self, other):
308+
assert self.name == other.name
309+
return Mark(
310+
self.name, self.args + other.args,
311+
dict(self.kwargs, **other.kwargs))
312+
313+
314+
class MarkInfo(object):
302315
""" Marking object created by :class:`MarkDecorator` instances. """
303-
def __init__(self, name, args, kwargs):
304-
#: name of attribute
305-
self.name = name
306-
#: positional argument list, empty if none specified
307-
self.args = args
308-
#: keyword argument dictionary, empty if nothing specified
309-
self.kwargs = kwargs.copy()
310-
self._arglist = [(args, kwargs.copy())]
316+
def __init__(self, mark):
317+
assert isinstance(mark, Mark), repr(mark)
318+
self.combined = mark
319+
self._marks = [mark]
320+
321+
name = alias('combined.name')
322+
args = alias('combined.args')
323+
kwargs = alias('combined.kwargs')
311324

312325
def __repr__(self):
313-
return "<MarkInfo %r args=%r kwargs=%r>" % (
314-
self.name, self.args, self.kwargs
315-
)
326+
return "<MarkInfo {0!r}>".format(self.combined)
316327

317-
def add(self, args, kwargs):
328+
def add_mark(self, mark):
318329
""" add a MarkInfo with the given args and kwargs. """
319-
self._arglist.append((args, kwargs))
320-
self.args += args
321-
self.kwargs.update(kwargs)
330+
self._marks.append(mark)
331+
self.combined = self.combined.combined_with(mark)
322332

323333
def __iter__(self):
324334
""" yield MarkInfo objects each relating to a marking-call. """
325-
for args, kwargs in self._arglist:
326-
yield MarkInfo(self.name, args, kwargs)
335+
return imap(MarkInfo, self._marks)

_pytest/skipping.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,9 @@ def _istrue(self):
121121
# "holder" might be a MarkInfo or a MarkDecorator; only
122122
# MarkInfo keeps track of all parameters it received in an
123123
# _arglist attribute
124-
if hasattr(self.holder, '_arglist'):
125-
arglist = self.holder._arglist
126-
else:
127-
arglist = [(self.holder.args, self.holder.kwargs)]
128-
for args, kwargs in arglist:
124+
marks = getattr(self.holder, '_marks', None) \
125+
or [self.holder.mark]
126+
for _, args, kwargs in marks:
129127
if 'condition' in kwargs:
130128
args = (kwargs['condition'],)
131129
for expr in args:

testing/test_mark.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
class TestMark:
77
def test_markinfo_repr(self):
8-
from _pytest.mark import MarkInfo
9-
m = MarkInfo("hello", (1,2), {})
8+
from _pytest.mark import MarkInfo, Mark
9+
m = MarkInfo(Mark("hello", (1,2), {}))
1010
repr(m)
1111

1212
def test_pytest_exists_in_namespace_all(self):

0 commit comments

Comments
 (0)