151 lines
5.2 KiB
Python
151 lines
5.2 KiB
Python
""" generic mechanism for marking and selecting python functions. """
|
|
import py
|
|
|
|
def pytest_namespace():
|
|
return {'mark': MarkGenerator()}
|
|
|
|
def pytest_addoption(parser):
|
|
group = parser.getgroup("general")
|
|
group._addoption('-k',
|
|
action="store", dest="keyword", default='',
|
|
help="only run test items matching the given "
|
|
"space separated keywords. precede a keyword with '-' to negate. "
|
|
"Terminate the expression with ':' to treat a match as a signal "
|
|
"to run all subsequent tests. ")
|
|
|
|
def pytest_collection_modifyitems(items, config):
|
|
keywordexpr = config.option.keyword
|
|
if not keywordexpr:
|
|
return
|
|
selectuntil = False
|
|
if keywordexpr[-1] == ":":
|
|
selectuntil = True
|
|
keywordexpr = keywordexpr[:-1]
|
|
|
|
remaining = []
|
|
deselected = []
|
|
for colitem in items:
|
|
if keywordexpr and skipbykeyword(colitem, keywordexpr):
|
|
deselected.append(colitem)
|
|
else:
|
|
remaining.append(colitem)
|
|
if selectuntil:
|
|
keywordexpr = None
|
|
|
|
if deselected:
|
|
config.hook.pytest_deselected(items=deselected)
|
|
items[:] = remaining
|
|
|
|
def skipbykeyword(colitem, keywordexpr):
|
|
""" return True if they given keyword expression means to
|
|
skip this collector/item.
|
|
"""
|
|
if not keywordexpr:
|
|
return
|
|
chain = colitem.listchain()
|
|
for key in filter(None, keywordexpr.split()):
|
|
eor = key[:1] == '-'
|
|
if eor:
|
|
key = key[1:]
|
|
if not (eor ^ matchonekeyword(key, chain)):
|
|
return True
|
|
|
|
def matchonekeyword(key, chain):
|
|
elems = key.split(".")
|
|
# XXX O(n^2), anyone cares?
|
|
chain = [item.keywords for item in chain if item.keywords]
|
|
for start, _ in enumerate(chain):
|
|
if start + len(elems) > len(chain):
|
|
return False
|
|
for num, elem in enumerate(elems):
|
|
for keyword in chain[num + start]:
|
|
ok = False
|
|
if elem in keyword:
|
|
ok = True
|
|
break
|
|
if not ok:
|
|
break
|
|
if num == len(elems) - 1 and ok:
|
|
return True
|
|
return False
|
|
|
|
class MarkGenerator:
|
|
""" non-underscore attributes of this object can be used as decorators for
|
|
marking test functions. Example: @py.test.mark.slowtest in front of a
|
|
function will set the 'slowtest' marker object on it. """
|
|
def __getattr__(self, name):
|
|
if name[0] == "_":
|
|
raise AttributeError(name)
|
|
return MarkDecorator(name)
|
|
|
|
class MarkDecorator:
|
|
""" decorator for setting function attributes. """
|
|
def __init__(self, name):
|
|
self.markname = name
|
|
self.kwargs = {}
|
|
self.args = []
|
|
|
|
def __repr__(self):
|
|
d = self.__dict__.copy()
|
|
name = d.pop('markname')
|
|
return "<MarkDecorator %r %r>" %(name, d)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
""" if passed a single callable argument: decorate it with mark info.
|
|
otherwise add *args/**kwargs in-place to mark information. """
|
|
if args:
|
|
func = args[0]
|
|
if len(args) == 1 and hasattr(func, '__call__') or \
|
|
hasattr(func, '__bases__'):
|
|
if hasattr(func, '__bases__'):
|
|
if hasattr(func, 'pytestmark'):
|
|
l = func.pytestmark
|
|
if not isinstance(l, list):
|
|
func.pytestmark = [l, self]
|
|
else:
|
|
l.append(self)
|
|
else:
|
|
func.pytestmark = [self]
|
|
else:
|
|
holder = getattr(func, self.markname, None)
|
|
if holder is None:
|
|
holder = MarkInfo(self.markname, self.args, self.kwargs)
|
|
setattr(func, self.markname, holder)
|
|
else:
|
|
holder.kwargs.update(self.kwargs)
|
|
holder.args.extend(self.args)
|
|
return func
|
|
else:
|
|
self.args.extend(args)
|
|
self.kwargs.update(kwargs)
|
|
return self
|
|
|
|
class MarkInfo:
|
|
def __init__(self, name, args, kwargs):
|
|
self._name = name
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
def __repr__(self):
|
|
return "<MarkInfo %r args=%r kwargs=%r>" % (
|
|
self._name, self.args, self.kwargs)
|
|
|
|
def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
|
item = __multicall__.execute()
|
|
if isinstance(item, py.test.collect.Function):
|
|
cls = collector.getparent(py.test.collect.Class)
|
|
mod = collector.getparent(py.test.collect.Module)
|
|
func = item.obj
|
|
func = getattr(func, '__func__', func) # py3
|
|
func = getattr(func, 'im_func', func) # py2
|
|
for parent in [x for x in (mod, cls) if x]:
|
|
marker = getattr(parent.obj, 'pytestmark', None)
|
|
if marker is not None:
|
|
if not isinstance(marker, list):
|
|
marker = [marker]
|
|
for mark in marker:
|
|
if isinstance(mark, MarkDecorator):
|
|
mark(func)
|
|
item.keywords.update(py.builtin._getfuncdict(func) or {})
|
|
return item
|