use consistent inner repressentation for marks

This commit is contained in:
Ronny Pfannschmidt 2016-09-08 09:52:22 +02:00
parent 98ac1dd34b
commit 10094a3f09
4 changed files with 52 additions and 45 deletions

View File

@ -356,7 +356,7 @@ class Node(object):
""" """
from _pytest.mark import MarkDecorator from _pytest.mark import MarkDecorator
if isinstance(marker, py.builtin._basestring): if isinstance(marker, py.builtin._basestring):
marker = MarkDecorator(marker) marker = getattr(pytest.mark, marker)
elif not isinstance(marker, MarkDecorator): elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker") raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker self.keywords[marker.name] = marker

View File

@ -1,5 +1,11 @@
""" generic mechanism for marking and selecting python functions. """ """ generic mechanism for marking and selecting python functions. """
import inspect import inspect
from collections import namedtuple
from operator import attrgetter
from itertools import imap
def alias(name):
return property(attrgetter(name), doc='alias for ' + name)
class MarkerError(Exception): class MarkerError(Exception):
@ -182,7 +188,7 @@ class MarkGenerator:
raise AttributeError("Marker name must NOT start with underscore") raise AttributeError("Marker name must NOT start with underscore")
if hasattr(self, '_config'): if hasattr(self, '_config'):
self._check(name) self._check(name)
return MarkDecorator(name) return MarkDecorator(Mark(name, (), {}))
def _check(self, name): def _check(self, name):
try: try:
@ -235,19 +241,20 @@ class MarkDecorator:
additional keyword or positional arguments. additional keyword or positional arguments.
""" """
def __init__(self, name, args=None, kwargs=None): def __init__(self, mark):
self.name = name assert isinstance(mark, Mark), repr(mark)
self.args = args or () self.mark = mark
self.kwargs = kwargs or {}
name = alias('mark.name')
args = alias('mark.args')
kwargs = alias('mark.kwargs')
@property @property
def markname(self): def markname(self):
return self.name # for backward-compat (2.4.1 had this attr) return self.name # for backward-compat (2.4.1 had this attr)
def __repr__(self): def __repr__(self):
d = self.__dict__.copy() return "<MarkDecorator %r>" % self.mark
name = d.pop('name')
return "<MarkDecorator %r %r>" % (name, d)
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info. """ if passed a single callable argument: decorate it with mark info.
@ -270,17 +277,14 @@ class MarkDecorator:
else: else:
holder = getattr(func, self.name, None) holder = getattr(func, self.name, None)
if holder is None: if holder is None:
holder = MarkInfo( holder = MarkInfo(self.mark)
self.name, self.args, self.kwargs
)
setattr(func, self.name, holder) setattr(func, self.name, holder)
else: else:
holder.add(self.args, self.kwargs) holder.add_mark(self.mark)
return func return func
kw = self.kwargs.copy()
kw.update(kwargs) mark = Mark(self.name, args, kwargs)
args = self.args + args return self.__class__(self.mark.combined_with(mark))
return self.__class__(self.name, args=args, kwargs=kw)
def extract_argvalue(maybe_marked_args): def extract_argvalue(maybe_marked_args):
@ -291,36 +295,41 @@ def extract_argvalue(maybe_marked_args):
newmarks = {} newmarks = {}
argval = maybe_marked_args argval = maybe_marked_args
while isinstance(argval, MarkDecorator): while isinstance(argval, MarkDecorator):
newmark = MarkDecorator(argval.markname, newmark = MarkDecorator(Mark(
argval.args[:-1], argval.kwargs) argval.markname, argval.args[:-1], argval.kwargs))
newmarks[newmark.markname] = newmark newmarks[newmark.name] = newmark
argval = argval.args[-1] argval = argval.args[-1]
return argval, newmarks return argval, newmarks
class MarkInfo: class Mark(namedtuple('Mark', 'name, args, kwargs')):
def combined_with(self, other):
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
dict(self.kwargs, **other.kwargs))
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """ """ Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, name, args, kwargs): def __init__(self, mark):
#: name of attribute assert isinstance(mark, Mark), repr(mark)
self.name = name self.combined = mark
#: positional argument list, empty if none specified self._marks = [mark]
self.args = args
#: keyword argument dictionary, empty if nothing specified name = alias('combined.name')
self.kwargs = kwargs.copy() args = alias('combined.args')
self._arglist = [(args, kwargs.copy())] kwargs = alias('combined.kwargs')
def __repr__(self): def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % ( return "<MarkInfo {0!r}>".format(self.combined)
self.name, self.args, self.kwargs
)
def add(self, args, kwargs): def add_mark(self, mark):
""" add a MarkInfo with the given args and kwargs. """ """ add a MarkInfo with the given args and kwargs. """
self._arglist.append((args, kwargs)) self._marks.append(mark)
self.args += args self.combined = self.combined.combined_with(mark)
self.kwargs.update(kwargs)
def __iter__(self): def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """ """ yield MarkInfo objects each relating to a marking-call. """
for args, kwargs in self._arglist: return imap(MarkInfo, self._marks)
yield MarkInfo(self.name, args, kwargs)

View File

@ -121,11 +121,9 @@ class MarkEvaluator:
# "holder" might be a MarkInfo or a MarkDecorator; only # "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an # MarkInfo keeps track of all parameters it received in an
# _arglist attribute # _arglist attribute
if hasattr(self.holder, '_arglist'): marks = getattr(self.holder, '_marks', None) \
arglist = self.holder._arglist or [self.holder.mark]
else: for _, args, kwargs in marks:
arglist = [(self.holder.args, self.holder.kwargs)]
for args, kwargs in arglist:
if 'condition' in kwargs: if 'condition' in kwargs:
args = (kwargs['condition'],) args = (kwargs['condition'],)
for expr in args: for expr in args:

View File

@ -5,8 +5,8 @@ from _pytest.mark import MarkGenerator as Mark
class TestMark: class TestMark:
def test_markinfo_repr(self): def test_markinfo_repr(self):
from _pytest.mark import MarkInfo from _pytest.mark import MarkInfo, Mark
m = MarkInfo("hello", (1,2), {}) m = MarkInfo(Mark("hello", (1,2), {}))
repr(m) repr(m)
def test_pytest_exists_in_namespace_all(self): def test_pytest_exists_in_namespace_all(self):