so never released. Rather extend keywords to also exist on nodes. Assigning to node.keywords will make the value appear on all subchildren's keywords.
219 lines
7.3 KiB
Python
219 lines
7.3 KiB
Python
""" generic mechanism for marking and selecting python functions. """
|
|
import pytest, py
|
|
|
|
def pytest_namespace():
|
|
return {'mark': MarkGenerator()}
|
|
|
|
def pytest_addoption(parser):
|
|
group = parser.getgroup("general")
|
|
group._addoption('-k',
|
|
action="store", dest="keyword", default='', metavar="KEYWORDEXPR",
|
|
help="only run tests which match given keyword expression. "
|
|
"An expression consists of space-separated terms. "
|
|
"Each term must match. Precede a term with '-' to negate. "
|
|
"Terminate expression with ':' to make the first match match "
|
|
"all subsequent tests (usually file-order). ")
|
|
|
|
group._addoption("-m",
|
|
action="store", dest="markexpr", default="", metavar="MARKEXPR",
|
|
help="only run tests matching given mark expression. "
|
|
"example: -m 'mark1 and not mark2'."
|
|
)
|
|
|
|
group.addoption("--markers", action="store_true", help=
|
|
"show markers (builtin, plugin and per-project ones).")
|
|
|
|
parser.addini("markers", "markers for test functions", 'linelist')
|
|
|
|
def pytest_cmdline_main(config):
|
|
if config.option.markers:
|
|
config.pluginmanager.do_configure(config)
|
|
tw = py.io.TerminalWriter()
|
|
for line in config.getini("markers"):
|
|
name, rest = line.split(":", 1)
|
|
tw.write("@pytest.mark.%s:" % name, bold=True)
|
|
tw.line(rest)
|
|
tw.line()
|
|
config.pluginmanager.do_unconfigure(config)
|
|
return 0
|
|
pytest_cmdline_main.tryfirst = True
|
|
|
|
def pytest_collection_modifyitems(items, config):
|
|
keywordexpr = config.option.keyword
|
|
matchexpr = config.option.markexpr
|
|
if not keywordexpr and not matchexpr:
|
|
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:
|
|
if selectuntil:
|
|
keywordexpr = None
|
|
if matchexpr:
|
|
if not matchmark(colitem, matchexpr):
|
|
deselected.append(colitem)
|
|
continue
|
|
remaining.append(colitem)
|
|
|
|
if deselected:
|
|
config.hook.pytest_deselected(items=deselected)
|
|
items[:] = remaining
|
|
|
|
class BoolDict:
|
|
def __init__(self, mydict):
|
|
self._mydict = mydict
|
|
def __getitem__(self, name):
|
|
return name in self._mydict
|
|
|
|
def matchmark(colitem, matchexpr):
|
|
return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__))
|
|
|
|
def pytest_configure(config):
|
|
if config.option.strict:
|
|
pytest.mark._config = config
|
|
|
|
def skipbykeyword(colitem, keywordexpr):
|
|
""" return True if they given keyword expression means to
|
|
skip this collector/item.
|
|
"""
|
|
if not keywordexpr:
|
|
return
|
|
|
|
itemkeywords = colitem.keywords
|
|
for key in filter(None, keywordexpr.split()):
|
|
eor = key[:1] == '-'
|
|
if eor:
|
|
key = key[1:]
|
|
if not (eor ^ matchonekeyword(key, itemkeywords)):
|
|
return True
|
|
|
|
def matchonekeyword(key, itemkeywords):
|
|
for elem in key.split("."):
|
|
for kw in itemkeywords:
|
|
if elem in kw:
|
|
break
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
class MarkGenerator:
|
|
""" Factory for :class:`MarkDecorator` objects - exposed as
|
|
a ``py.test.mark`` singleton instance. Example::
|
|
|
|
import py
|
|
@py.test.mark.slowtest
|
|
def test_function():
|
|
pass
|
|
|
|
will set a 'slowtest' :class:`MarkInfo` object
|
|
on the ``test_function`` object. """
|
|
|
|
def __getattr__(self, name):
|
|
if name[0] == "_":
|
|
raise AttributeError(name)
|
|
if hasattr(self, '_config'):
|
|
self._check(name)
|
|
return MarkDecorator(name)
|
|
|
|
def _check(self, name):
|
|
try:
|
|
if name in self._markers:
|
|
return
|
|
except AttributeError:
|
|
pass
|
|
self._markers = l = set()
|
|
for line in self._config.getini("markers"):
|
|
beginning = line.split(":", 1)
|
|
x = beginning[0].split("(", 1)[0]
|
|
l.add(x)
|
|
if name not in self._markers:
|
|
raise AttributeError("%r not a registered marker" % (name,))
|
|
|
|
class MarkDecorator:
|
|
""" 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>`.
|
|
MarkDecorator instances are often created like this::
|
|
|
|
mark1 = py.test.mark.NAME # simple MarkDecorator
|
|
mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator
|
|
|
|
and can then be applied as decorators to test functions::
|
|
|
|
@mark2
|
|
def test_function():
|
|
pass
|
|
"""
|
|
def __init__(self, name, args=None, kwargs=None):
|
|
self.markname = name
|
|
self.args = args or ()
|
|
self.kwargs = kwargs or {}
|
|
|
|
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.add(self.args, self.kwargs)
|
|
return func
|
|
kw = self.kwargs.copy()
|
|
kw.update(kwargs)
|
|
args = self.args + args
|
|
return self.__class__(self.markname, args=args, kwargs=kw)
|
|
|
|
class MarkInfo:
|
|
""" 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
|
|
self._arglist = [(args, kwargs.copy())]
|
|
|
|
def __repr__(self):
|
|
return "<MarkInfo %r args=%r kwargs=%r>" % (
|
|
self.name, self.args, self.kwargs)
|
|
|
|
def add(self, args, kwargs):
|
|
""" add a MarkInfo with the given args and kwargs. """
|
|
self._arglist.append((args, kwargs))
|
|
self.args += args
|
|
self.kwargs.update(kwargs)
|
|
|
|
def __iter__(self):
|
|
""" yield MarkInfo objects each relating to a marking-call. """
|
|
for args, kwargs in self._arglist:
|
|
yield MarkInfo(self.name, args, kwargs)
|
|
|