Merge pull request #1923 from RonnyPfannschmidt/mark-internal-value

use consistent inner repressentation for marks
This commit is contained in:
Floris Bruynooghe 2016-09-19 15:30:22 +01:00 committed by GitHub
commit 01db0f1cd1
5 changed files with 56 additions and 47 deletions

View File

@ -111,7 +111,7 @@ if sys.version_info[:2] == (2, 6):
if _PY3: if _PY3:
import codecs import codecs
imap = map
STRING_TYPES = bytes, str STRING_TYPES = bytes, str
def _escape_strings(val): def _escape_strings(val):
@ -145,6 +145,8 @@ if _PY3:
else: else:
STRING_TYPES = bytes, str, unicode STRING_TYPES = bytes, str, unicode
from itertools import imap # NOQA
def _escape_strings(val): def _escape_strings(val):
"""In py2 bytes and str are the same type, so return if it's a bytes """In py2 bytes and str are the same type, so return if it's a bytes
object, return it unchanged if it is a full ascii string, object, return it unchanged if it is a full ascii string,
@ -213,4 +215,4 @@ def _is_unittest_unexpected_success_a_failure():
Changed in version 3.4: Returns False if there were any Changed in version 3.4: Returns False if there were any
unexpectedSuccesses from tests marked with the expectedFailure() decorator. unexpectedSuccesses from tests marked with the expectedFailure() decorator.
""" """
return sys.version_info >= (3, 4) return sys.version_info >= (3, 4)

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 .compat 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):