Skip to content
Closed
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
2 changes: 1 addition & 1 deletion _pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def add_marker(self, marker):
"""
from _pytest.mark import MarkDecorator
if isinstance(marker, py.builtin._basestring):
marker = MarkDecorator(marker)
marker = getattr(pytest.mark, marker)
elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker
Expand Down
82 changes: 57 additions & 25 deletions _pytest/mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def __getattr__(self, name):
raise AttributeError("Marker name must NOT start with underscore")
if hasattr(self, '_config'):
self._check(name)
return MarkDecorator(name)
return MarkDecorator(Mark(name, (), {}))

def _check(self, name):
try:
Expand All @@ -202,7 +202,7 @@ def istestfunc(func):
return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>"

class MarkDecorator:
class MarkDecorator(object):
""" A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
Expand Down Expand Up @@ -235,19 +235,29 @@ def test_function():
additional keyword or positional arguments.

"""
def __init__(self, name, args=None, kwargs=None):
self.name = name
self.args = args or ()
self.kwargs = kwargs or {}
def __init__(self, mark):

self._mark = mark

@property
def name(self):
return self._mark.name

@property
def args(self):
return self._mark.args

@property
def kwargs(self):
return self._mark.kwargs

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

def __repr__(self):
d = self.__dict__.copy()
name = d.pop('name')
return "<MarkDecorator %r %r>" % (name, d)
return "<MarkDecorator %r %r>" % (
self.name, { 'args': self.args, 'kwargs': self.kwargs})

def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info.
Expand All @@ -270,20 +280,16 @@ def __call__(self, *args, **kwargs):
else:
holder = getattr(func, self.name, None)
if holder is None:
holder = MarkInfo(
self.name, self.args, self.kwargs
)
holder = MarkInfo(self._mark)
setattr(func, self.name, holder)
else:
holder.add(self.args, self.kwargs)
holder.add(self._mark)
return func
kw = self.kwargs.copy()
kw.update(kwargs)
args = self.args + args
return self.__class__(self.name, args=args, kwargs=kw)
return self.__class__(self._mark.new_with(args, kwargs))


class MarkInfo:

class Mark(object):
""" Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, name, args, kwargs):
#: name of attribute
Expand All @@ -292,20 +298,46 @@ def __init__(self, name, args, kwargs):
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs.copy()
self._arglist = [(args, kwargs.copy())]

def new_with(self, args, kwargs):
kw = self.kwargs.copy()
kw.update(kwargs)
return Mark(self.name, self.args + args, kw)


class MarkInfo:
""" Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, mark):
self._marks = [mark]

@property
def name(self):
return self._marks[0].name

@property
def args(self):
ret = ()
for mark in self._marks:
ret += mark.args
return ret

@property
def kwargs(self):
ret = {}
for mark in self._marks:
ret.update(mark.kwargs)
return ret

def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self.name, self.args, self.kwargs
)

def add(self, args, kwargs):
def add(self, mark):
""" add a MarkInfo with the given args and kwargs. """
self._arglist.append((args, kwargs))
self.args += args
self.kwargs.update(kwargs)
self._marks.append(mark)

def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
for args, kwargs in self._arglist:
yield MarkInfo(self.name, args, kwargs)
for mark in self._marks:
yield MarkInfo(mark)
4 changes: 2 additions & 2 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

class TestMark:
def test_markinfo_repr(self):
from _pytest.mark import MarkInfo
m = MarkInfo("hello", (1,2), {})
from _pytest.mark import MarkInfo, Mark
m = MarkInfo(Mark("hello", (1,2), {}))
repr(m)

def test_pytest_exists_in_namespace_all(self):
Expand Down