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:
import codecs
imap = map
STRING_TYPES = bytes, str
def _escape_strings(val):
@ -145,6 +145,8 @@ if _PY3:
else:
STRING_TYPES = bytes, str, unicode
from itertools import imap # NOQA
def _escape_strings(val):
"""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,
@ -213,4 +215,4 @@ def _is_unittest_unexpected_success_a_failure():
Changed in version 3.4: Returns False if there were any
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
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

View File

@ -1,5 +1,11 @@
""" generic mechanism for marking and selecting python functions. """
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):
@ -182,7 +188,7 @@ class MarkGenerator:
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:
@ -235,19 +241,20 @@ class MarkDecorator:
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):
assert isinstance(mark, Mark), repr(mark)
self.mark = mark
name = alias('mark.name')
args = alias('mark.args')
kwargs = alias('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>" % self.mark
def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info.
@ -270,17 +277,14 @@ class MarkDecorator:
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_mark(self.mark)
return func
kw = self.kwargs.copy()
kw.update(kwargs)
args = self.args + args
return self.__class__(self.name, args=args, kwargs=kw)
mark = Mark(self.name, args, kwargs)
return self.__class__(self.mark.combined_with(mark))
def extract_argvalue(maybe_marked_args):
@ -291,36 +295,41 @@ def extract_argvalue(maybe_marked_args):
newmarks = {}
argval = maybe_marked_args
while isinstance(argval, MarkDecorator):
newmark = MarkDecorator(argval.markname,
argval.args[:-1], argval.kwargs)
newmarks[newmark.markname] = newmark
newmark = MarkDecorator(Mark(
argval.markname, argval.args[:-1], argval.kwargs))
newmarks[newmark.name] = newmark
argval = argval.args[-1]
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. """
def __init__(self, name, args, kwargs):
#: name of attribute
self.name = name
#: positional argument list, empty if none specified
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs.copy()
self._arglist = [(args, kwargs.copy())]
def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.combined = mark
self._marks = [mark]
name = alias('combined.name')
args = alias('combined.args')
kwargs = alias('combined.kwargs')
def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self.name, self.args, self.kwargs
)
return "<MarkInfo {0!r}>".format(self.combined)
def add(self, args, kwargs):
def add_mark(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)
self.combined = self.combined.combined_with(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)
return imap(MarkInfo, self._marks)

View File

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

View File

@ -5,8 +5,8 @@ from _pytest.mark import MarkGenerator as Mark
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):