diff --git a/_pytest/mark.py b/_pytest/mark.py deleted file mode 100644 index 1a763540240..00000000000 --- a/_pytest/mark.py +++ /dev/null @@ -1,311 +0,0 @@ -""" generic mechanism for marking and selecting python functions. """ -import inspect - - -class MarkerError(Exception): - - """Error in use of a pytest marker/attribute.""" - - -def pytest_namespace(): - return {'mark': MarkGenerator()} - - -def pytest_addoption(parser): - group = parser.getgroup("general") - group._addoption( - '-k', - action="store", dest="keyword", default='', metavar="EXPRESSION", - help="only run tests which match the given substring expression. " - "An expression is a python evaluatable expression " - "where all names are substring-matched against test names " - "and their parent classes. Example: -k 'test_method or test " - "other' matches all test functions and classes whose name " - "contains 'test_method' or 'test_other'. " - "Additionally keywords are matched to classes and functions " - "containing extra names in their 'extra_keyword_matches' set, " - "as well as functions which have names assigned directly to them." - ) - - 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): - import _pytest.config - if config.option.markers: - config._do_configure() - tw = _pytest.config.create_terminal_writer(config) - 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._ensure_unconfigure() - 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 - # pytest used to allow "-" for negating - # but today we just allow "-" at the beginning, use "not" instead - # we probably remove "-" alltogether soon - if keywordexpr.startswith("-"): - keywordexpr = "not " + keywordexpr[1:] - selectuntil = False - if keywordexpr[-1:] == ":": - selectuntil = True - keywordexpr = keywordexpr[:-1] - - remaining = [] - deselected = [] - for colitem in items: - if keywordexpr and not matchkeyword(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 MarkMapping: - """Provides a local mapping for markers where item access - resolves to True if the marker is present. """ - def __init__(self, keywords): - mymarks = set() - for key, value in keywords.items(): - if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator): - mymarks.add(key) - self._mymarks = mymarks - - def __getitem__(self, name): - return name in self._mymarks - - -class KeywordMapping: - """Provides a local mapping for keywords. - Given a list of names, map any substring of one of these names to True. - """ - def __init__(self, names): - self._names = names - - def __getitem__(self, subname): - for name in self._names: - if subname in name: - return True - return False - - -def matchmark(colitem, markexpr): - """Tries to match on any marker names, attached to the given colitem.""" - return eval(markexpr, {}, MarkMapping(colitem.keywords)) - - -def matchkeyword(colitem, keywordexpr): - """Tries to match given keyword expression to given collector item. - - Will match on the name of colitem, including the names of its parents. - Only matches names of items which are either a :class:`Class` or a - :class:`Function`. - Additionally, matches on names in the 'extra_keyword_matches' set of - any item, as well as names directly assigned to test functions. - """ - mapped_names = set() - - # Add the names of the current item and any parent items - import pytest - for item in colitem.listchain(): - if not isinstance(item, pytest.Instance): - mapped_names.add(item.name) - - # Add the names added as extra keywords to current or parent items - for name in colitem.listextrakeywords(): - mapped_names.add(name) - - # Add the names attached to the current function through direct assignment - if hasattr(colitem, 'function'): - for name in colitem.function.__dict__: - mapped_names.add(name) - - mapping = KeywordMapping(mapped_names) - if " " not in keywordexpr: - # special case to allow for simple "-k pass" and "-k 1.3" - return mapping[keywordexpr] - elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]: - return not mapping[keywordexpr[4:]] - return eval(keywordexpr, {}, mapping) - - -def pytest_configure(config): - import pytest - if config.option.strict: - pytest.mark._config = config - - -class MarkGenerator: - """ Factory for :class:`MarkDecorator` objects - exposed as - a ``pytest.mark`` singleton instance. Example:: - - import pytest - @pytest.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("Marker name must NOT start with underscore") - 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,)) - -def istestfunc(func): - return hasattr(func, "__call__") and \ - getattr(func, "__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 `. - MarkDecorator instances are often created like this:: - - mark1 = pytest.mark.NAME # simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator - - and can then be applied as decorators to test functions:: - - @mark2 - def test_function(): - pass - - When a MarkDecorator instance is called it does the following: - 1. If called with a single class as its only positional argument and no - additional keyword arguments, it attaches itself to the class so it - gets applied automatically to all test cases found in that class. - 2. If called with a single function as its only positional argument and - no additional keyword arguments, it attaches a MarkInfo object to the - function, containing all the arguments already stored internally in - the MarkDecorator. - 3. When called in any other case, it performs a 'fake construction' call, - i.e. it returns a new MarkDecorator instance with the original - MarkDecorator's content updated with the arguments passed to this - call. - - Note: The rules above prevent MarkDecorator objects from storing only a - single function or class reference as their positional argument with no - additional keyword or positional arguments. - - """ - def __init__(self, name, args=None, kwargs=None): - self.name = name - self.args = args or () - self.kwargs = kwargs or {} - - @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 "" % (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 and not kwargs: - func = args[0] - is_class = inspect.isclass(func) - if len(args) == 1 and (istestfunc(func) or is_class): - if is_class: - if hasattr(func, 'pytestmark'): - mark_list = func.pytestmark - if not isinstance(mark_list, list): - mark_list = [mark_list] - # always work on a copy to avoid updating pytestmark - # from a superclass by accident - mark_list = mark_list + [self] - func.pytestmark = mark_list - else: - func.pytestmark = [self] - else: - holder = getattr(func, self.name, None) - if holder is None: - holder = MarkInfo( - self.name, self.args, self.kwargs - ) - setattr(func, self.name, 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.name, 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.copy() - self._arglist = [(args, kwargs.copy())] - - def __repr__(self): - return "" % ( - 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) diff --git a/_pytest/mark/__init__.py b/_pytest/mark/__init__.py new file mode 100644 index 00000000000..08ddd1677c1 --- /dev/null +++ b/_pytest/mark/__init__.py @@ -0,0 +1,175 @@ +""" generic mechanism for marking and selecting python functions. """ +from .model import MarkInfo, MarkDecorator, MarkGenerator + + +class MarkerError(Exception): + """Error in use of a pytest marker/attribute.""" + + +def pytest_namespace(): + return {'mark': MarkGenerator()} + + +def pytest_addoption(parser): + # yapf: disable + group = parser.getgroup("general") + group._addoption( + '-k', + action="store", dest="keyword", default='', metavar="EXPRESSION", + help="only run tests which match the given substring expression. " + "An expression is a python evaluatable expression " + "where all names are substring-matched against test names " + "and their parent classes. Example: -k 'test_method or test " + "other' matches all test functions and classes whose name " + "contains 'test_method' or 'test_other'. " + "Additionally keywords are matched to classes and functions " + "containing extra names in their 'extra_keyword_matches' set, " + "as well as functions which have names assigned directly to them." + ) + + 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') + # yapf: enable + +def pytest_cmdline_main(config): + import _pytest.config + if config.option.markers: + config._do_configure() + tw = _pytest.config.create_terminal_writer(config) + 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._ensure_unconfigure() + 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 + # pytest used to allow "-" for negating + # but today we just allow "-" at the beginning, use "not" instead + # we probably remove "-" alltogether soon + if keywordexpr.startswith("-"): + keywordexpr = "not " + keywordexpr[1:] + selectuntil = False + if keywordexpr[-1:] == ":": + selectuntil = True + keywordexpr = keywordexpr[:-1] + + remaining = [] + deselected = [] + for colitem in items: + if keywordexpr and not matchkeyword(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 MarkMapping: + """Provides a local mapping for markers where item access + resolves to True if the marker is present. """ + + def __init__(self, keywords): + mymarks = set() + for key, value in keywords.items(): + if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator): + mymarks.add(key) + self._mymarks = mymarks + + def __getitem__(self, name): + return name in self._mymarks + + +class KeywordMapping: + """Provides a local mapping for keywords. + Given a list of names, map any substring of one of these names to True. + """ + + def __init__(self, names): + self._names = names + + def __getitem__(self, subname): + for name in self._names: + if subname in name: + return True + return False + + +def matchmark(colitem, markexpr): + """Tries to match on any marker names, attached to the given colitem.""" + return eval(markexpr, {}, MarkMapping(colitem.keywords)) + + +def matchkeyword(colitem, keywordexpr): + """Tries to match given keyword expression to given collector item. + + Will match on the name of colitem, including the names of its parents. + Only matches names of items which are either a :class:`Class` or a + :class:`Function`. + Additionally, matches on names in the 'extra_keyword_matches' set of + any item, as well as names directly assigned to test functions. + """ + mapped_names = set() + + # Add the names of the current item and any parent items + import pytest + for item in colitem.listchain(): + if not isinstance(item, pytest.Instance): + mapped_names.add(item.name) + + # Add the names added as extra keywords to current or parent items + for name in colitem.listextrakeywords(): + mapped_names.add(name) + + # Add the names attached to the current function through direct assignment + if hasattr(colitem, 'function'): + for name in colitem.function.__dict__: + mapped_names.add(name) + + mapping = KeywordMapping(mapped_names) + if " " not in keywordexpr: + # special case to allow for simple "-k pass" and "-k 1.3" + return mapping[keywordexpr] + elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]: + return not mapping[keywordexpr[4:]] + return eval(keywordexpr, {}, mapping) + + +def pytest_configure(config): + import pytest + if config.option.strict: + pytest.mark._config = config + + +def _parsed_markers(config): + for line in config.getini("markers"): + beginning = line.split(":", 1) + yield beginning[0].split("(", 1)[0] diff --git a/_pytest/mark/evalexpr.py b/_pytest/mark/evalexpr.py new file mode 100644 index 00000000000..d00195e78f0 --- /dev/null +++ b/_pytest/mark/evalexpr.py @@ -0,0 +1,107 @@ +import sys +import traceback +import pytest +import os +import py + + +def cached_eval(config, expr, d): + if not hasattr(config, '_evalcache'): + config._evalcache = {} + try: + return config._evalcache[expr] + except KeyError: + import _pytest._code + exprcode = _pytest._code.compile(expr, mode="eval") + config._evalcache[expr] = x = eval(exprcode, d) + return x + + +class MarkEvaluator: + def __init__(self, item, name): + self.item = item + self.name = name + + @property + def holder(self): + return self.item.keywords.get(self.name) + + def __bool__(self): + return bool(self.holder) + + __nonzero__ = __bool__ + + def wasvalid(self): + return not hasattr(self, 'exc') + + def invalidraise(self, exc): + raises = self.get('raises') + if not raises: + return + return not isinstance(exc, raises) + + def istrue(self): + try: + return self._istrue() + except Exception: + self.exc = sys.exc_info() + if isinstance(self.exc[1], SyntaxError): + msg = [" " * (self.exc[1].offset + 4) + "^", ] + msg.append("SyntaxError: invalid syntax") + else: + msg = traceback.format_exception_only(*self.exc[:2]) + pytest.fail("Error evaluating %r expression\n" + " %s\n" + "%s" % (self.name, self.expr, "\n".join(msg)), + pytrace=False) + + def _getglobals(self): + d = {'os': os, 'sys': sys, 'config': self.item.config} + d.update(self.item.obj.__globals__) + return d + + def _istrue(self): + if hasattr(self, 'result'): + return self.result + if self.holder: + d = self._getglobals() + if self.holder.args: + self.result = False + # "holder" might be a MarkInfo or a MarkDecorator; only + # MarkInfo keeps track of all parameters it received + try: + marklist = list(self.holder) + except: + marklist = [self.holder.mark] + for mark in marklist: + for expr in mark.args: + self.expr = expr + if isinstance(expr, py.builtin._basestring): + result = cached_eval(self.item.config, expr, d) + else: + if "reason" not in mark.kwargs: + # XXX better be checked at collection time + msg = "you need to specify reason=STRING " \ + "when using booleans as conditions." + pytest.fail(msg) + result = bool(expr) + if result: + self.result = True + self.reason = mark.kwargs.get('reason', None) + self.expr = expr + return self.result + else: + self.result = True + return getattr(self, 'result', False) + + def get(self, attr, default=None): + return self.holder.kwargs.get(attr, default) + + def getexplanation(self): + expl = getattr(self, 'reason', None) or self.get('reason', None) + if not expl: + if not hasattr(self, 'expr'): + return "" + else: + return "condition: " + str(self.expr) + return expl diff --git a/_pytest/mark/model.py b/_pytest/mark/model.py new file mode 100644 index 00000000000..da53d9889b7 --- /dev/null +++ b/_pytest/mark/model.py @@ -0,0 +1,172 @@ +import inspect +from operator import attrgetter + + +def alias(name): + return property(fget=attrgetter(name), doc='alias for self.' + name) + + +def istestfunc(func): + return hasattr(func, "__call__") and \ + getattr(func, "__name__", "") != "" + + +def apply_mark(mark, obj): + # unwrap MarkDecorator + mark = getattr(mark, 'mark', mark) + if not isinstance(mark, Mark): + raise TypeError('%r is not a marker' % (mark, )) + is_class = inspect.isclass(obj) + if is_class: + if hasattr(obj, 'pytestmark'): + mark_list = obj.pytestmark + if not isinstance(mark_list, list): + mark_list = [mark_list] + # always work on a copy to avoid updating pytestmark + # from a superclass by accident + mark_list = mark_list + [mark] + obj.pytestmark = mark_list + else: + obj.pytestmark = [mark] + else: + holder = getattr(obj, mark.name, None) + if holder is None: + holder = MarkInfo(mark) + setattr(obj, mark.name, holder) + else: + holder.add(mark) + + +class MarkDecorator(object): + """ 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 `. + MarkDecorator instances are often created like this:: + + mark1 = pytest.mark.NAME # simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator + + and can then be applied as decorators to test functions:: + + @mark2 + def test_function(): + pass + + When a MarkDecorator instance is called it does the following: + 1. If called with a single class as its only positional argument and no + additional keyword arguments, it attaches itself to the class so it + gets applied automatically to all test cases found in that class. + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches a MarkInfo object to the + function, containing all the arguments already stored internally in + the MarkDecorator. + 3. When called in any other case, it performs a 'fake construction' call, + i.e. it returns a new MarkDecorator instance with the original + MarkDecorator's content updated with the arguments passed to this + call. + + Note: The rules above prevent MarkDecorator objects from storing only a + single function or class reference as their positional argument with no + additional keyword or positional arguments. + + """ + + def __init__(self, name, args=None, kwargs=None): + self.mark = Mark(name, args or (), kwargs or {}) + + name = markname = alias('mark.name') + args = alias('mark.args') + kwargs = alias('mark.kwargs') + + def __repr__(self): + + return repr(self.mark).replace('Mark', 'MarkDecorator') + + 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 and not kwargs: + func = args[0] + is_class = inspect.isclass(func) + if len(args) == 1 and (istestfunc(func) or is_class): + apply_mark(mark=self.mark, obj=func) + return func + + kw = self.kwargs.copy() + kw.update(kwargs) + args = self.args + args + return self.__class__(self.name, args=args, kwargs=kw) + + +class MarkInfo(object): + """ Marking object created by :class:`MarkDecorator` instances. """ + name = markname = alias('mark.name') + args = alias('mark.args') + kwargs = alias('mark.kwargs') + + def __init__(self, mark): + self.mark = mark + #: name of attribute + self._mark_list = [mark] + + def __repr__(self): + return "" % (self.name, self.args, + self.kwargs) + + def add(self, mark): + """ add a MarkInfo with the given args and kwargs. """ + self._mark_list.append(mark) + self.mark += mark + + def __iter__(self): + """ yield MarkInfo objects each relating to a marking-call. """ + return iter(self._mark_list) + + +class Mark(object): + def __init__(self, name, args, kwargs): + self.name = name + self.args = args + self.kwargs = kwargs + + def __add__(self, other): + assert isinstance(other, Mark) + assert other.name == self.name + return Mark(self.name, self.args + other.args, + dict(self.kwargs, **other.kwargs)) + + def __repr__(self): + return "" % (self.name, self.args, + self.kwargs) + + +class MarkGenerator(object): + """ Factory for :class:`MarkDecorator` objects - exposed as + a ``pytest.mark`` singleton instance. Example:: + + import py + @pytest.mark.slowtest + def test_function(): + pass + + will set a 'slowtest' :class:`MarkInfo` object + on the ``test_function`` object. """ + + def __init__(self): + self.__known_markers = set() + + def __getattr__(self, name): + if name[0] == "_": + raise AttributeError("Marker name must NOT start with underscore") + if hasattr(self, '_config'): + self._check(name) + return MarkDecorator(name) + + def _check(self, name): + if name in self.__known_markers: + return + from . import _parsed_markers + self.__known_markers.update(_parsed_markers(self._config)) + + if name not in self.__known_markers: + raise AttributeError("%r not a registered marker" % (name, )) diff --git a/_pytest/mark/utils.py b/_pytest/mark/utils.py new file mode 100644 index 00000000000..209546ca3f5 --- /dev/null +++ b/_pytest/mark/utils.py @@ -0,0 +1,34 @@ +from .model import apply_mark, MarkDecorator + + +def _marked(func, mark): + """ Returns True if :func: is already marked with :mark:, False otherwise. + This can happen if marker is applied to class and the test file is + invoked more than once. + """ + try: + func_mark = getattr(func, mark.name) + except AttributeError: + return False + return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs + + +def get_marks(obj): + try: + maybe_mark = obj.pytestmark + except AttributeError: + return [] + else: + if isinstance(maybe_mark, list): + return maybe_mark + elif isinstance(maybe_mark, MarkDecorator): + return [maybe_mark.mark] + else: + raise TypeError('%r is not a mark' % (maybe_mark,)) + + +def transfer_markers(funcobj, cls, mod): + for holder in (cls, mod): + for mark in get_marks(holder): + if not _marked(funcobj, mark): + apply_mark(mark=mark, obj=funcobj) diff --git a/_pytest/python.py b/_pytest/python.py index a18f13b43cf..496031d202f 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -13,7 +13,7 @@ import pytest from _pytest._code.code import TerminalRepr from _pytest.mark import MarkDecorator, MarkerError - +from _pytest.mark.utils import transfer_markers try: import enum except ImportError: # pragma: no cover @@ -26,7 +26,6 @@ cutdir2 = py.path.local(_pytest.__file__).dirpath() cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) - NoneType = type(None) NOTSET = object() isfunction = inspect.isfunction @@ -40,15 +39,18 @@ _PY3 = sys.version_info > (3, 0) _PY2 = not _PY3 - if hasattr(inspect, 'signature'): + def _format_args(func): return str(inspect.signature(func)) else: + def _format_args(func): return inspect.formatargspec(*inspect.getargspec(func)) -if sys.version_info[:2] == (2, 6): + +if sys.version_info[:2] == (2, 6): + def isclass(object): """ Return true if the object is a class. Overrides inspect.isclass for python 2.6 because it will return True for objects which always return @@ -57,6 +59,7 @@ def isclass(object): """ return isinstance(object, (type, types.ClassType)) + def _has_positional_arg(func): return func.__code__.co_argcount @@ -85,6 +88,7 @@ def get_real_func(obj): obj = obj.func return obj + def getfslineno(obj): # xxx let decorators etc specify a sane ordering obj = get_real_func(obj) @@ -94,6 +98,7 @@ def getfslineno(obj): assert isinstance(fslineno[1], int), obj return fslineno + def getimfunc(func): try: return func.__func__ @@ -103,6 +108,7 @@ def getimfunc(func): except AttributeError: return func + def safe_getattr(object, name, default): """ Like getattr but return default upon any Exception. @@ -128,7 +134,7 @@ def __init__(self, scope, params, def __call__(self, function): if isclass(function): raise ValueError( - "class fixtures not supported (may be in the future)") + "class fixtures not supported (may be in the future)") function._pytestfixturefunction = self return function @@ -175,6 +181,7 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None): params = list(params) return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) + def yield_fixture(scope="function", params=None, autouse=False, ids=None): """ (return a) decorator to mark a yield-fixture factory function (EXPERIMENTAL). @@ -186,44 +193,68 @@ def yield_fixture(scope="function", params=None, autouse=False, ids=None): """ if callable(scope) and params is None and autouse == False: # direct decoration - return FixtureFunctionMarker( - "function", params, autouse, yieldctx=True)(scope) + return FixtureFunctionMarker("function", + params, + autouse, + yieldctx=True)(scope) else: - return FixtureFunctionMarker(scope, params, autouse, - yieldctx=True, ids=ids) + return FixtureFunctionMarker(scope, + params, + autouse, + yieldctx=True, + ids=ids) + defaultfuncargprefixmarker = fixture() + def pyobj_property(name): def get(self): node = self.getparent(getattr(pytest, name)) if node is not None: return node.obj + doc = "python %s object this node was collected from (can be None)." % ( - name.lower(),) + name.lower(), ) return property(get, None, None, doc) def pytest_addoption(parser): group = parser.getgroup("general") - group.addoption('--fixtures', '--funcargs', - action="store_true", dest="showfixtures", default=False, - help="show available fixtures, sorted by plugin appearance") - parser.addini("usefixtures", type="args", default=[], - help="list of default fixtures to be used with this project") - parser.addini("python_files", type="args", + group.addoption( + '--fixtures', + '--funcargs', + action="store_true", + dest="showfixtures", + default=False, + help="show available fixtures, sorted by plugin appearance") + parser.addini("usefixtures", + type="args", + default=[], + help="list of default fixtures to be used with this project") + parser.addini( + "python_files", + type="args", default=['test_*.py', '*_test.py'], help="glob-style file patterns for Python test module discovery") - parser.addini("python_classes", type="args", default=["Test",], + parser.addini( + "python_classes", + type="args", + default=["Test", ], help="prefixes or glob names for Python test class discovery") - parser.addini("python_functions", type="args", default=["test",], - help="prefixes or glob names for Python test function and " - "method discovery") - - group.addoption("--import-mode", default="prepend", - choices=["prepend", "append"], dest="importmode", + parser.addini("python_functions", + type="args", + default=["test", ], + help="prefixes or glob names for Python test function and " + "method discovery") + + group.addoption( + "--import-mode", + default="prepend", + choices=["prepend", "append"], + dest="importmode", help="prepend/append to sys.path when importing test modules, " - "default is to prepend.") + "default is to prepend.") def pytest_cmdline_main(config): @@ -247,8 +278,10 @@ def pytest_generate_tests(metafunc): for marker in markers: metafunc.parametrize(*marker.args, **marker.kwargs) + def pytest_configure(config): - config.addinivalue_line("markers", + config.addinivalue_line( + "markers", "parametrize(argnames, argvalues): call a test function multiple " "times passing in different arguments in turn. argvalues generally " "needs to be a list of values if argnames specifies only one name " @@ -256,16 +289,17 @@ def pytest_configure(config): "Example: @parametrize('arg1', [1,2]) would lead to two calls of the " "decorated test function, one with arg1=1 and another with arg1=2." "see http://pytest.org/latest/parametrize.html for more info and " - "examples." - ) - config.addinivalue_line("markers", + "examples.") + config.addinivalue_line( + "markers", "usefixtures(fixturename1, fixturename2, ...): mark tests as needing " - "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures " - ) + "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures ") + def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) + @pytest.hookimpl(trylast=True) def pytest_namespace(): raises.Exception = pytest.fail.Exception @@ -275,11 +309,16 @@ def pytest_namespace(): 'raises': raises, 'approx': approx, 'collect': { - 'Module': Module, 'Class': Class, 'Instance': Instance, - 'Function': Function, 'Generator': Generator, - '_fillfuncargs': fillfixtures} + 'Module': Module, + 'Class': Class, + 'Instance': Instance, + 'Function': Function, + 'Generator': Generator, + '_fillfuncargs': fillfixtures + } } + @fixture(scope="session") def pytestconfig(request): """ the pytest config object with access to command line opts.""" @@ -299,6 +338,7 @@ def pytest_pyfunc_call(pyfuncitem): testfunction(**testargs) return True + def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": @@ -307,13 +347,15 @@ def pytest_collect_file(path, parent): if path.fnmatch(pat): break else: - return + return ihook = parent.session.gethookproxy(path) return ihook.pytest_pycollect_makemodule(path=path, parent=parent) + def pytest_pycollect_makemodule(path, parent): return Module(path, parent) + @pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield @@ -332,9 +374,10 @@ def pytest_pycollect_makeitem(collector, name, obj): # or a funtools.wrapped. # We musn't if it's been wrapped with mock.patch (python 2 only) if not (isfunction(obj) or isfunction(get_real_func(obj))): - collector.warn(code="C2", message= - "cannot collect %r because it is not a function." - % name, ) + collector.warn( + code="C2", + message="cannot collect %r because it is not a function." % + name, ) elif getattr(obj, "__test__", True): if is_generator(obj): res = Generator(name, parent=collector) @@ -342,18 +385,22 @@ def pytest_pycollect_makeitem(collector, name, obj): res = list(collector._genfunctions(name, obj)) outcome.force_result(res) + def is_generator(func): try: - return _pytest._code.getrawcode(func).co_flags & 32 # generator function - except AttributeError: # builtin functions have no bytecode + return _pytest._code.getrawcode( + func).co_flags & 32 # generator function + except AttributeError: # builtin functions have no bytecode # assume them to not be generators return False + class PyobjContext(object): module = pyobj_property("Module") cls = pyobj_property("Class") instance = pyobj_property("Instance") + class PyobjMixin(PyobjContext): def obj(): def fget(self): @@ -362,9 +409,12 @@ def fget(self): except AttributeError: self._obj = obj = self._getobj() return obj + def fset(self, value): self._obj = value + return property(fget, fset, None, "underlying python object") + obj = obj() def _getobj(self): @@ -410,8 +460,8 @@ def reportinfo(self): assert isinstance(lineno, int) return fspath, lineno, modpath -class PyCollector(PyobjMixin, pytest.Collector): +class PyCollector(PyobjMixin, pytest.Collector): def funcnamefilter(self, name): return self._matches_prefix_or_glob_option('python_functions', name) @@ -428,10 +478,9 @@ def classnamefilter(self, name): return self._matches_prefix_or_glob_option('python_classes', name) def istestfunction(self, obj, name): - return ( - (self.funcnamefilter(name) or self.isnosetest(obj)) and - safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None - ) + return ((self.funcnamefilter(name) or self.isnosetest(obj)) and + safe_getattr(obj, "__call__", False) and + getfixturemarker(obj) is None) def istestclass(self, obj, name): return self.classnamefilter(name) or self.isnosetest(obj) @@ -479,8 +528,9 @@ def collect(self): def makeitem(self, name, obj): #assert self.ihook.fspath == self.fspath, self - return self.ihook.pytest_pycollect_makeitem( - collector=self, name=name, obj=obj) + return self.ihook.pytest_pycollect_makeitem(collector=self, + name=name, + obj=obj) def _genfunctions(self, name, funcobj): module = self.getparent(Module).obj @@ -489,16 +539,20 @@ def _genfunctions(self, name, funcobj): transfer_markers(funcobj, cls, module) fm = self.session._fixturemanager fixtureinfo = fm.getfixtureinfo(self, funcobj, cls) - metafunc = Metafunc(funcobj, fixtureinfo, self.config, - cls=cls, module=module) + metafunc = Metafunc(funcobj, + fixtureinfo, + self.config, + cls=cls, + module=module) methods = [] if hasattr(module, "pytest_generate_tests"): methods.append(module.pytest_generate_tests) if hasattr(cls, "pytest_generate_tests"): methods.append(cls().pytest_generate_tests) if methods: - self.ihook.pytest_generate_tests.call_extra(methods, - dict(metafunc=metafunc)) + self.ihook.pytest_generate_tests.call_extra( + methods, + dict(metafunc=metafunc)) else: self.ihook.pytest_generate_tests(metafunc=metafunc) @@ -510,11 +564,14 @@ def _genfunctions(self, name, funcobj): add_funcarg_pseudo_fixture_def(self, metafunc, fm) for callspec in metafunc._calls: - subname = "%s[%s]" %(name, callspec.id) - yield Function(name=subname, parent=self, - callspec=callspec, callobj=funcobj, + subname = "%s[%s]" % (name, callspec.id) + yield Function(name=subname, + parent=self, + callspec=callspec, + callobj=funcobj, fixtureinfo=fixtureinfo, - keywords={callspec.id:True}) + keywords={callspec.id: True}) + def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): # this function will transform all collected calls to a functions @@ -524,7 +581,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): # XXX we can probably avoid this algorithm if we modify CallSpec2 # to directly care for creating the fixturedefs within its methods. if not metafunc._calls[0].funcargs: - return # this function call does not have direct parametrization + return # this function call does not have direct parametrization # collect funcargs of all callspecs into a list of values arg2params = {} arg2scope = {} @@ -560,10 +617,9 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): if node and argname in node._name2pseudofixturedef: arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]] else: - fixturedef = FixtureDef(fixturemanager, '', argname, - get_direct_param_fixture_func, - arg2scope[argname], - valuelist, False, False) + fixturedef = FixtureDef( + fixturemanager, '', argname, get_direct_param_fixture_func, + arg2scope[argname], valuelist, False, False) arg2fixturedefs[argname] = [fixturedef] if node is not None: node._name2pseudofixturedef[argname] = fixturedef @@ -572,6 +628,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): def get_direct_param_fixture_func(request): return request.param + class FuncFixtureInfo: def __init__(self, argnames, names_closure, name2fixturedefs): self.argnames = argnames @@ -579,36 +636,9 @@ def __init__(self, argnames, names_closure, name2fixturedefs): self.name2fixturedefs = name2fixturedefs -def _marked(func, mark): - """ Returns True if :func: is already marked with :mark:, False otherwise. - This can happen if marker is applied to class and the test file is - invoked more than once. - """ - try: - func_mark = getattr(func, mark.name) - except AttributeError: - return False - return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs - - -def transfer_markers(funcobj, cls, mod): - # XXX this should rather be code in the mark plugin or the mark - # plugin should merge with the python plugin. - for holder in (cls, mod): - try: - pytestmark = holder.pytestmark - except AttributeError: - continue - if isinstance(pytestmark, list): - for mark in pytestmark: - if not _marked(funcobj, mark): - mark(funcobj) - else: - if not _marked(funcobj, pytestmark): - pytestmark(funcobj) - class Module(pytest.File, PyCollector): """ Collector for test classes and functions. """ + def _getobj(self): return self._memoizedcall('_obj', self._importtestmodule) @@ -622,8 +652,8 @@ def _importtestmodule(self): try: mod = self.fspath.pyimport(ensuresyspath=importmode) except SyntaxError: - raise self.CollectError( - _pytest._code.ExceptionInfo().getrepr(style="short")) + raise self.CollectError(_pytest._code.ExceptionInfo().getrepr( + style="short")) except self.fspath.ImportMismatchError: e = sys.exc_info()[1] raise self.CollectError( @@ -633,9 +663,7 @@ def _importtestmodule(self): "which is not the same as the test file we want to collect:\n" " %s\n" "HINT: remove __pycache__ / .pyc files and/or use a " - "unique basename for your test file modules" - % e.args - ) + "unique basename for your test file modules" % e.args) except ImportError: exc_class, exc, _ = sys.exc_info() raise self.CollectError( @@ -676,10 +704,11 @@ def setup(self): class Class(PyCollector): """ Collector for test methods. """ + def collect(self): if hasinit(self.obj): self.warn("C1", "cannot collect test class %r because it has a " - "__init__ constructor" % self.obj.__name__) + "__init__ constructor" % self.obj.__name__) return [] return [self._getcustomclass("Instance")(name="()", parent=self)] @@ -696,6 +725,7 @@ def setup(self): fin_class = getattr(fin_class, '__func__', fin_class) self.addfinalizer(lambda: fin_class(self.obj)) + class Instance(PyCollector): def _getobj(self): obj = self.parent.obj() @@ -709,6 +739,7 @@ def newinstance(self): self.obj = self._getobj() return self.obj + class FunctionMixin(PyobjMixin): """ mixin for the code common to Function and Generator. """ @@ -762,7 +793,7 @@ def _repr_failure_py(self, excinfo, style="long"): if not excinfo.value.pytrace: return py._builtin._totext(excinfo.value) return super(FunctionMixin, self)._repr_failure_py(excinfo, - style=style) + style=style) def repr_failure(self, excinfo, outerr=None): assert outerr is None, "XXX outerr usage is deprecated" @@ -785,20 +816,22 @@ def collect(self): for i, x in enumerate(self.obj()): name, call, args = self.getcallargs(x) if not callable(call): - raise TypeError("%r yielded non callable test %r" %(self.obj, call,)) + raise TypeError("%r yielded non callable test %r" % (self.obj, + call, )) if name is None: name = "[%d]" % i else: name = "['%s']" % name if name in seen: - raise ValueError("%r generated tests with non-unique name %r" %(self, name)) + raise ValueError("%r generated tests with non-unique name %r" % + (self, name)) seen[name] = True l.append(self.Function(name, self, args=args, callobj=call)) return l def getcallargs(self, obj): if not isinstance(obj, (tuple, list)): - obj = (obj,) + obj = (obj, ) # explict naming if isinstance(obj[0], py.builtin._basestring): name = obj[0] @@ -816,7 +849,6 @@ def hasinit(obj): return True - def fillfixtures(function): """ fill missing funcargs for a test function. """ try: @@ -841,6 +873,7 @@ def fillfixtures(function): _notexists = object() + class CallSpec2(object): def __init__(self, metafunc): self.metafunc = metafunc @@ -869,7 +902,7 @@ def copy(self, metafunc): def _checkargnotcontained(self, arg): if arg in self.params or arg in self.funcargs: - raise ValueError("duplicate %r" %(arg,)) + raise ValueError("duplicate %r" % (arg, )) def getparam(self, name): try: @@ -885,7 +918,7 @@ def id(self): def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum, param_index): - for arg,val in zip(argnames, valset): + for arg, val in zip(argnames, valset): self._checkargnotcontained(arg) valtype_for_arg = valtypes[arg] getattr(self, valtype_for_arg)[arg] = val @@ -913,11 +946,13 @@ class FuncargnamesCompatAttr: """ helper class so that Metafunc, Function and FixtureRequest don't need to each define the "funcargnames" compatibility attribute. """ + @property def funcargnames(self): """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" return self.fixturenames + class Metafunc(FuncargnamesCompatAttr): """ Metafunc objects are passed to the ``pytest_generate_tests`` hook. @@ -940,6 +975,7 @@ class Metafunc(FuncargnamesCompatAttr): .. deprecated:: 2.3 Use ``fixturenames`` instead. """ + def __init__(self, function, fixtureinfo, config, cls=None, module=None): self.config = config self.module = module @@ -950,8 +986,12 @@ def __init__(self, function, fixtureinfo, config, cls=None, module=None): self._calls = [] self._ids = py.builtin.set() - def parametrize(self, argnames, argvalues, indirect=False, ids=None, - scope=None): + def parametrize(self, + argnames, + argvalues, + indirect=False, + ids=None, + scope=None): """ Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed during the collection phase. If you need to setup expensive resources @@ -997,8 +1037,8 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, unwrapped_argvalues = [] for i, argval in enumerate(argvalues): while isinstance(argval, MarkDecorator): - newmark = MarkDecorator(argval.markname, - argval.args[:-1], argval.kwargs) + newmark = MarkDecorator(argval.markname, argval.args[:-1], + argval.kwargs) newmarks = newkeywords.setdefault(i, {}) newmarks[newmark.markname] = newmark argval = argval.args[-1] @@ -1008,9 +1048,9 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] if len(argnames) == 1: - argvalues = [(val,) for val in argvalues] + argvalues = [(val, ) for val in argvalues] if not argvalues: - argvalues = [(_notexists,) * len(argnames)] + argvalues = [(_notexists, ) * len(argnames)] if scope is None: scope = "function" @@ -1018,7 +1058,8 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, valtypes = {} for arg in argnames: if arg not in self.fixturenames: - raise ValueError("%r uses no fixture %r" %(self.function, arg)) + raise ValueError("%r uses no fixture %r" % + (self.function, arg)) if indirect is True: valtypes = dict.fromkeys(argnames, "params") @@ -1028,25 +1069,26 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, valtypes = dict.fromkeys(argnames, "funcargs") for arg in indirect: if arg not in argnames: - raise ValueError("indirect given to %r: fixture %r doesn't exist" %( - self.function, arg)) + raise ValueError( + "indirect given to %r: fixture %r doesn't exist" % ( + self.function, arg)) valtypes[arg] = "params" idfn = None if callable(ids): idfn = ids ids = None if ids and len(ids) != len(argvalues): - raise ValueError('%d tests specified with %d ids' %( - len(argvalues), len(ids))) + raise ValueError('%d tests specified with %d ids' % + (len(argvalues), len(ids))) ids = idmaker(argnames, argvalues, idfn, ids) newcalls = [] for callspec in self._calls or [CallSpec2(self)]: for param_index, valset in enumerate(argvalues): assert len(valset) == len(argnames) newcallspec = callspec.copy(self) - newcallspec.setmulti(valtypes, argnames, valset, ids[param_index], - newkeywords.get(param_index, {}), scopenum, - param_index) + newcallspec.setmulti( + valtypes, argnames, valset, ids[param_index], + newkeywords.get(param_index, {}), scopenum, param_index) newcalls.append(newcallspec) self._calls = newcalls @@ -1157,7 +1199,7 @@ def _idval(val, argname, idx, idfn): return str(val) elif isclass(val) and hasattr(val, '__name__'): return val.__name__ - return str(argname)+str(idx) + return str(argname) + str(idx) def _idvalset(idx, valset, argnames, idfn, ids): if ids is None or ids[idx] is None: @@ -1180,10 +1222,12 @@ def idmaker(argnames, argvalues, idfn=None, ids=None): counters[testid] += 1 return ids + def showfixtures(config): from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) + def _showfixtures_main(config, session): import _pytest.config session.perform_collect() @@ -1200,10 +1244,9 @@ def _showfixtures_main(config, session): continue fixturedef = fixturedefs[-1] loc = getlocation(fixturedef.func, curdir) - available.append((len(fixturedef.baseid), - fixturedef.func.__module__, - curdir.bestrelpath(loc), - fixturedef.argname, fixturedef)) + available.append((len(fixturedef.baseid), fixturedef.func.__module__, + curdir.bestrelpath( + loc), fixturedef.argname, fixturedef)) available.sort() currentmodule = None @@ -1211,12 +1254,12 @@ def _showfixtures_main(config, session): if currentmodule != module: if not module.startswith("_pytest."): tw.line() - tw.sep("-", "fixtures defined from %s" %(module,)) + tw.sep("-", "fixtures defined from %s" % (module, )) currentmodule = module if verbose <= 0 and argname[0] == "_": continue if verbose > 0: - funcargspec = "%s -- %s" %(argname, bestrel,) + funcargspec = "%s -- %s" % (argname, bestrel, ) else: funcargspec = argname tw.line(funcargspec, green=True) @@ -1226,8 +1269,8 @@ def _showfixtures_main(config, session): for line in doc.strip().split("\n"): tw.line(" " + line.strip()) else: - tw.line(" %s: no docstring available" %(loc,), - red=True) + tw.line(" %s: no docstring available" % (loc, ), red=True) + def getlocation(function, curdir): import inspect @@ -1235,10 +1278,11 @@ def getlocation(function, curdir): lineno = py.builtin._getcode(function).co_firstlineno if fn.relto(curdir): fn = fn.relto(curdir) - return "%s:%d" %(fn, lineno+1) + return "%s:%d" % (fn, lineno + 1) # builtin pytest.raises helper + def raises(expected_exception, *args, **kwargs): """ Assert that a code block/function call raises ``expected_exception`` @@ -1351,6 +1395,7 @@ def raises(expected_exception, *args, **kwargs): return _pytest._code.ExceptionInfo() pytest.fail("DID NOT RAISE {0}".format(expected_exception)) + class RaisesContext(object): def __init__(self, expected_exception): self.expected_exception = expected_exception @@ -1627,15 +1672,26 @@ def tolerance(self): # the basic pytest Function item # + class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr): """ a Function Item is responsible for setting up and executing a Python test function. """ _genid = None - def __init__(self, name, parent, args=None, config=None, - callspec=None, callobj=NOTSET, keywords=None, session=None, + + def __init__(self, + name, + parent, + args=None, + config=None, + callspec=None, + callobj=NOTSET, + keywords=None, + session=None, fixtureinfo=None): - super(Function, self).__init__(name, parent, config=config, + super(Function, self).__init__(name, + parent, + config=config, session=session) self._args = args if callobj is not NOTSET: @@ -1650,7 +1706,9 @@ def __init__(self, name, parent, args=None, config=None, if fixtureinfo is None: fixtureinfo = self.session._fixturemanager.getfixtureinfo( - self.parent, self.obj, self.cls, + self.parent, + self.obj, + self.cls, funcargs=not self._isyieldedfunction()) self._fixtureinfo = fixtureinfo self.fixturenames = fixtureinfo.names_closure @@ -1677,7 +1735,7 @@ def function(self): def _getobj(self): name = self.name - i = name.find("[") # parametrization + i = name.find("[") # parametrization if i != -1: name = name[:i] return getattr(self.parent.obj, name) @@ -1702,27 +1760,31 @@ def setup(self): pass else: fs, lineno = self._getfslineno() - pytest.skip("got empty parameter set, function %s at %s:%d" %( - self.function.__name__, fs, lineno)) + pytest.skip("got empty parameter set, function %s at %s:%d" % + (self.function.__name__, fs, lineno)) super(Function, self).setup() fillfixtures(self) scope2props = dict(session=()) scope2props["module"] = ("fspath", "module") -scope2props["class"] = scope2props["module"] + ("cls",) +scope2props["class"] = scope2props["module"] + ("cls", ) scope2props["instance"] = scope2props["class"] + ("instance", ) scope2props["function"] = scope2props["instance"] + ("function", "keywords") + def scopeproperty(name=None, doc=None): def decoratescope(func): scopename = name or func.__name__ + def provide(self): if func.__name__ in scope2props[self.scope]: return func(self) - raise AttributeError("%s not available in %s-scoped context" % ( - scopename, self.scope)) + raise AttributeError("%s not available in %s-scoped context" % + (scopename, self.scope)) + return property(provide, None, None, func.__doc__) + return decoratescope @@ -1740,7 +1802,7 @@ def __init__(self, pyfuncitem): self.fixturename = None #: Scope string, one of "function", "cls", "module", "session" self.scope = "function" - self._funcargs = {} + self._funcargs = {} self._fixturedefs = {} fixtureinfo = pyfuncitem._fixtureinfo self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() @@ -1753,7 +1815,6 @@ def node(self): """ underlying collection node (depends on current request scope)""" return self._getscopeitem(self.scope) - def _getnextfixturedef(self, argname): fixturedefs = self._arg2fixturedefs.get(argname, None) if fixturedefs is None: @@ -1761,7 +1822,7 @@ def _getnextfixturedef(self, argname): # getfuncargvalue(argname) usage which was naturally # not known at parsing/collection time fixturedefs = self._fixturemanager.getfixturedefs( - argname, self._pyfuncitem.parent.nodeid) + argname, self._pyfuncitem.parent.nodeid) self._arg2fixturedefs[argname] = fixturedefs # fixturedefs list is immutable so we maintain a decreasing index index = self._arg2index.get(argname, 0) - 1 @@ -1775,7 +1836,6 @@ def config(self): """ the pytest config object associated with this request. """ return self._pyfuncitem.config - @scopeproperty() def function(self): """ test function object if the request has a per-function scope. """ @@ -1828,8 +1888,8 @@ def addfinalizer(self, finalizer): def _addfinalizer(self, finalizer, scope): colitem = self._getscopeitem(scope) - self._pyfuncitem.session._setupstate.addfinalizer( - finalizer=finalizer, colitem=colitem) + self._pyfuncitem.session._setupstate.addfinalizer(finalizer=finalizer, + colitem=colitem) def applymarker(self, marker): """ Apply a marker to a single test function invocation. @@ -1855,7 +1915,11 @@ def _fillfixtures(self): if argname not in item.funcargs: item.funcargs[argname] = self.getfuncargvalue(argname) - def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): + def cached_setup(self, + setup, + teardown=None, + scope="module", + extrakey=None): """ (deprecated) Return a testing resource managed by ``setup`` & ``teardown`` calls. ``scope`` and ``extrakey`` determine when the ``teardown`` function will be called so that subsequent calls to @@ -1871,7 +1935,7 @@ def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): :arg extrakey: added to internal caching key of (funcargname, scope). """ if not hasattr(self.config, '_setupcache'): - self.config._setupcache = {} # XXX weakref? + self.config._setupcache = {} # XXX weakref? cachekey = (self.fixturename, self._getscopeitem(scope), extrakey) cache = self.config._setupcache try: @@ -1881,9 +1945,11 @@ def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): val = setup() cache[cachekey] = val if teardown is not None: + def finalizer(): del cache[cachekey] teardown(val) + self._addfinalizer(finalizer, scope=scope) return val @@ -1906,9 +1972,11 @@ def _get_active_fixturedef(self, argname): fixturedef = self._getnextfixturedef(argname) except FixtureLookupError: if argname == "request": + class PseudoFixtureDef: cached_result = (self, [0], None) scope = "function" + return PseudoFixtureDef raise # remove indent to prevent the python3 exception @@ -1972,10 +2040,11 @@ def _check_scope(self, argname, invoking_scope, requested_scope): if scopemismatch(invoking_scope, requested_scope): # try to report something helpful lines = self._factorytraceback() - pytest.fail("ScopeMismatch: You tried to access the %r scoped " + pytest.fail( + "ScopeMismatch: You tried to access the %r scoped " "fixture %r with a %r scoped request object, " - "involved factories\n%s" %( - (requested_scope, argname, invoking_scope, "\n".join(lines))), + "involved factories\n%s" % + ((requested_scope, argname, invoking_scope, "\n".join(lines))), pytrace=False) def _factorytraceback(self): @@ -1985,8 +2054,8 @@ def _factorytraceback(self): fs, lineno = getfslineno(factory) p = self._pyfuncitem.session.fspath.bestrelpath(fs) args = _format_args(factory) - lines.append("%s:%d: def %s%s" %( - p, lineno, factory.__name__, args)) + lines.append("%s:%d: def %s%s" % (p, lineno, factory.__name__, + args)) return lines def _getscopeitem(self, scope): @@ -2001,12 +2070,13 @@ def _getscopeitem(self, scope): return node def __repr__(self): - return "" %(self.node) + return "" % (self.node) class SubRequest(FixtureRequest): """ a sub request for handling getting a fixture from a test function/fixture. """ + def __init__(self, request, scope, param, param_index, fixturedef): self._parent_request = request self.fixturename = fixturedef.argname @@ -2017,7 +2087,7 @@ def __init__(self, request, scope, param, param_index, fixturedef): self._fixturedef = fixturedef self.addfinalizer = fixturedef.addfinalizer self._pyfuncitem = request._pyfuncitem - self._funcargs = request._funcargs + self._funcargs = request._funcargs self._fixturedefs = request._fixturedefs self._arg2fixturedefs = request._arg2fixturedefs self._arg2index = request._arg2index @@ -2033,14 +2103,18 @@ class ScopeMismatchError(Exception): which has a lower scope (e.g. a Session one calls a function one) """ + scopes = "session module class function".split() scopenum_function = scopes.index("function") + + def scopemismatch(currentscope, newscope): return scopes.index(newscope) > scopes.index(currentscope) class FixtureLookupError(LookupError): """ could not return a requested Fixture (missing or invalid). """ + def __init__(self, argname, request, msg=None): self.argname = argname self.request = request @@ -2063,9 +2137,9 @@ def formatrepr(self): lines, _ = inspect.getsourcelines(get_real_func(function)) except (IOError, IndexError): error_msg = "file %s, line %s: source code not available" - addline(error_msg % (fspath, lineno+1)) + addline(error_msg % (fspath, lineno + 1)) else: - addline("file %s, line %s" % (fspath, lineno+1)) + addline("file %s, line %s" % (fspath, lineno + 1)) for i, line in enumerate(lines): line = line.rstrip() addline(" " + line) @@ -2080,11 +2154,13 @@ def formatrepr(self): faclist = list(fm._matchfactories(fixturedef, parentid)) if faclist: available.append(name) - msg = "fixture %r not found" % (self.argname,) - msg += "\n available fixtures: %s" %(", ".join(available),) + msg = "fixture %r not found" % (self.argname, ) + msg += "\n available fixtures: %s" % (", ".join(available), ) msg += "\n use 'py.test --fixtures [testpath]' for help on them." - return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) + return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, + self.argname) + class FixtureLookupErrorRepr(TerminalRepr): def __init__(self, filename, firstlineno, tblines, errorstring, argname): @@ -2101,7 +2177,8 @@ def toterminal(self, tw): for line in self.errorstring.split("\n"): tw.line(" " + line.strip(), red=True) tw.line() - tw.line("%s:%d" % (self.filename, self.firstlineno+1)) + tw.line("%s:%d" % (self.filename, self.firstlineno + 1)) + class FixtureManager: """ @@ -2145,10 +2222,10 @@ def __init__(self, session): self._arg2fixturedefs = {} self._holderobjseen = set() self._arg2finish = {} - self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] + self._nodeid_and_autousenames = [("", + self.config.getini("usefixtures"))] session.config.pluginmanager.register(self, "funcmanage") - def getfixtureinfo(self, node, func, cls, funcargs=True): if funcargs and not hasattr(node, "nofuncargs"): if cls is not None: @@ -2190,13 +2267,12 @@ def _getautousenames(self, nodeid): if nodeid.startswith(baseid): if baseid: i = len(baseid) - nextchar = nodeid[i:i+1] + nextchar = nodeid[i:i + 1] if nextchar and nextchar not in ":/": continue autousenames.extend(basenames) # make sure autousenames are sorted by scope, scopenum 0 is session - autousenames.sort( - key=lambda x: self._arg2fixturedefs[x][-1].scopenum) + autousenames.sort(key=lambda x: self._arg2fixturedefs[x][-1].scopenum) return autousenames def getfixtureclosure(self, fixturenames, parentnode): @@ -2209,10 +2285,12 @@ def getfixtureclosure(self, fixturenames, parentnode): parentid = parentnode.nodeid fixturenames_closure = self._getautousenames(parentid) + def merge(otherlist): for arg in otherlist: if arg not in fixturenames_closure: fixturenames_closure.append(arg) + merge(fixturenames) arg2fixturedefs = {} lastlen = -1 @@ -2233,17 +2311,22 @@ def pytest_generate_tests(self, metafunc): if faclist: fixturedef = faclist[-1] if fixturedef.params is not None: - func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]]) + func_params = getattr( + getattr(metafunc.function, 'parametrize', + None), 'args', [[None]]) # skip directly parametrized arguments argnames = func_params[0] if not isinstance(argnames, (tuple, list)): - argnames = [x.strip() for x in argnames.split(",") if x.strip()] + argnames = [x.strip() + for x in argnames.split(",") if x.strip()] if argname not in func_params and argname not in argnames: - metafunc.parametrize(argname, fixturedef.params, - indirect=True, scope=fixturedef.scope, + metafunc.parametrize(argname, + fixturedef.params, + indirect=True, + scope=fixturedef.scope, ids=fixturedef.ids) else: - continue # will raise FixtureLookupError at setup time + continue # will raise FixtureLookupError at setup time def pytest_collection_modifyitems(self, items): # separate parametrized setups @@ -2279,10 +2362,15 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): if marker.name: name = marker.name assert not name.startswith(self._argprefix) - fixturedef = FixtureDef(self, nodeid, name, obj, - marker.scope, marker.params, + fixturedef = FixtureDef(self, + nodeid, + name, + obj, + marker.scope, + marker.params, yieldctx=marker.yieldctx, - unittest=unittest, ids=marker.ids) + unittest=unittest, + ids=marker.ids) faclist = self._arg2fixturedefs.setdefault(name, []) if fixturedef.has_location: faclist.append(fixturedef) @@ -2314,43 +2402,59 @@ def _matchfactories(self, fixturedefs, nodeid): def fail_fixturefunc(fixturefunc, msg): fs, lineno = getfslineno(fixturefunc) - location = "%s:%s" % (fs, lineno+1) + location = "%s:%s" % (fs, lineno + 1) source = _pytest._code.Source(fixturefunc) pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False) + def call_fixture_func(fixturefunc, request, kwargs, yieldctx): if yieldctx: if not is_generator(fixturefunc): - fail_fixturefunc(fixturefunc, + fail_fixturefunc( + fixturefunc, msg="yield_fixture requires yield statement in function") iter = fixturefunc(**kwargs) next = getattr(iter, "__next__", None) if next is None: next = getattr(iter, "next") res = next() + def teardown(): try: next() except StopIteration: pass else: - fail_fixturefunc(fixturefunc, + fail_fixturefunc( + fixturefunc, "yield_fixture function has more than one 'yield'") + request.addfinalizer(teardown) else: if is_generator(fixturefunc): - fail_fixturefunc(fixturefunc, + fail_fixturefunc( + fixturefunc, msg="pytest.fixture functions cannot use ``yield``. " - "Instead write and return an inner function/generator " - "and let the consumer call and iterate over it.") + "Instead write and return an inner function/generator " + "and let the consumer call and iterate over it.") res = fixturefunc(**kwargs) return res + class FixtureDef: """ A container for a factory definition. """ - def __init__(self, fixturemanager, baseid, argname, func, scope, params, - yieldctx, unittest=False, ids=None): + + def __init__(self, + fixturemanager, + baseid, + argname, + func, + scope, + params, + yieldctx, + unittest=False, + ids=None): self._fixturemanager = fixturemanager self.baseid = baseid or '' self.has_location = baseid is not None @@ -2434,6 +2538,7 @@ def __repr__(self): return ("" % (self.argname, self.scope, self.baseid)) + def num_mock_patch_args(function): """ return number of arguments used up by mock arguments (if any) """ patchings = getattr(function, "patchings", None) @@ -2441,8 +2546,9 @@ def num_mock_patch_args(function): return 0 mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None)) if mock is not None: - return len([p for p in patchings - if not p.attribute_name and p.new is mock.DEFAULT]) + return len([p + for p in patchings + if not p.attribute_name and p.new is mock.DEFAULT]) return len(patchings) @@ -2478,6 +2584,7 @@ def getfuncargnames(function, startindex=None): # down to the lower scopes such as to minimize number of "high scope" # setups and teardowns + def reorder_items(items): argkeys_cache = {} for scopenum in range(0, scopenum_function): @@ -2488,6 +2595,7 @@ def reorder_items(items): d[item] = keys return reorder_items_atscope(items, set(), argkeys_cache, 0) + def reorder_items_atscope(items, ignore, argkeys_cache, scopenum): if scopenum >= scopenum_function or len(items) < 3: return items @@ -2495,8 +2603,8 @@ def reorder_items_atscope(items, ignore, argkeys_cache, scopenum): while 1: items_before, items_same, items_other, newignore = \ slice_items(items, ignore, argkeys_cache[scopenum]) - items_before = reorder_items_atscope( - items_before, ignore, argkeys_cache,scopenum+1) + items_before = reorder_items_atscope(items_before, ignore, + argkeys_cache, scopenum + 1) if items_same is None: # nothing to reorder in this scope assert items_other is None @@ -2536,6 +2644,7 @@ def slice_items(items, ignore, scoped_argkeys_cache): return (items_before, items_same, items_other, newignore) return items, None, None, None + def get_parametrized_fixture_keys(item, scopenum): """ return list of keys for all parametrized arguments which match the specified scope. """ @@ -2551,7 +2660,7 @@ def get_parametrized_fixture_keys(item, scopenum): for argname, param_index in cs.indices.items(): if cs._arg2scopenum[argname] != scopenum: continue - if scopenum == 0: # session + if scopenum == 0: # session key = (argname, param_index) elif scopenum == 1: # module key = (argname, param_index, item.fspath) @@ -2565,6 +2674,7 @@ def xunitsetup(obj, name): if getfixturemarker(meth) is None: return meth + def getfixturemarker(obj): """ return fixturemarker or None if it doesn't exist or raised exceptions.""" @@ -2577,11 +2687,10 @@ def getfixturemarker(obj): # we don't expect them to be fixture functions return None -scopename2class = { - 'class': Class, - 'module': Module, - 'function': pytest.Item, -} + +scopename2class = {'class': Class, 'module': Module, 'function': pytest.Item, } + + def get_scope_node(node, scope): cls = scopename2class.get(scope) if cls is None: diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 69157f485a0..8d91653b644 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -1,12 +1,7 @@ """ support for skip/xfail functions and markers. """ -import os -import sys -import traceback - -import py import pytest from _pytest.mark import MarkInfo, MarkDecorator - +from _pytest.mark.evalexpr import MarkEvaluator def pytest_addoption(parser): group = parser.getgroup("general") @@ -63,100 +58,6 @@ def xfail(reason=""): xfail.Exception = XFailed -class MarkEvaluator: - def __init__(self, item, name): - self.item = item - self.name = name - - @property - def holder(self): - return self.item.keywords.get(self.name) - - def __bool__(self): - return bool(self.holder) - __nonzero__ = __bool__ - - def wasvalid(self): - return not hasattr(self, 'exc') - - def invalidraise(self, exc): - raises = self.get('raises') - if not raises: - return - return not isinstance(exc, raises) - - def istrue(self): - try: - return self._istrue() - except Exception: - self.exc = sys.exc_info() - if isinstance(self.exc[1], SyntaxError): - msg = [" " * (self.exc[1].offset + 4) + "^",] - msg.append("SyntaxError: invalid syntax") - else: - msg = traceback.format_exception_only(*self.exc[:2]) - pytest.fail("Error evaluating %r expression\n" - " %s\n" - "%s" - %(self.name, self.expr, "\n".join(msg)), - pytrace=False) - - def _getglobals(self): - d = {'os': os, 'sys': sys, 'config': self.item.config} - func = self.item.obj - try: - d.update(func.__globals__) - except AttributeError: - d.update(func.func_globals) - return d - - def _istrue(self): - if hasattr(self, 'result'): - return self.result - if self.holder: - d = self._getglobals() - if self.holder.args: - self.result = False - # "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: - for expr in args: - self.expr = expr - if isinstance(expr, py.builtin._basestring): - result = cached_eval(self.item.config, expr, d) - else: - if "reason" not in kwargs: - # XXX better be checked at collection time - msg = "you need to specify reason=STRING " \ - "when using booleans as conditions." - pytest.fail(msg) - result = bool(expr) - if result: - self.result = True - self.reason = kwargs.get('reason', None) - self.expr = expr - return self.result - else: - self.result = True - return getattr(self, 'result', False) - - def get(self, attr, default=None): - return self.holder.kwargs.get(attr, default) - - def getexplanation(self): - expl = getattr(self, 'reason', None) or self.get('reason', None) - if not expl: - if not hasattr(self, 'expr'): - return "" - else: - return "condition: " + str(self.expr) - return expl - @pytest.hookimpl(tryfirst=True) def pytest_runtest_setup(item): @@ -312,17 +213,6 @@ def show_xpassed(terminalreporter, lines): reason = rep.wasxfail lines.append("XPASS %s %s" %(pos, reason)) -def cached_eval(config, expr, d): - if not hasattr(config, '_evalcache'): - config._evalcache = {} - try: - return config._evalcache[expr] - except KeyError: - import _pytest._code - exprcode = _pytest._code.compile(expr, mode="eval") - config._evalcache[expr] = x = eval(exprcode, d) - return x - def folded_skips(skipped): d = {} diff --git a/setup.py b/setup.py index 6660f21604d..1f39b111b4b 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,13 @@ def main(): # the following should be enabled for release install_requires=install_requires, extras_require=extras_require, - packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'], + packages=[ + '_pytest', + '_pytest.assertion', + '_pytest._code', + '_pytest.vendored_packages', + '_pytest.mark', + ], py_modules=['pytest'], zip_safe=False, ) diff --git a/testing/test_mark.py b/testing/test_mark.py index aa1be6f7c66..5e47e9ff3fd 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -3,10 +3,12 @@ import py, pytest 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.model import Mark + m = MarkInfo(Mark("hello", (1, 2), {})) repr(m) def test_pytest_exists_in_namespace_all(self): @@ -23,15 +25,19 @@ def test_pytest_mark_name_starts_with_underscore(self): def test_pytest_mark_bare(self): mark = Mark() + def f(): pass + mark.hello(f) assert f.hello def test_pytest_mark_keywords(self): mark = Mark() + def f(): pass + mark.world(x=3, y=4)(f) assert f.world assert f.world.kwargs['x'] == 3 @@ -39,8 +45,10 @@ def f(): def test_apply_multiple_and_merge(self): mark = Mark() + def f(): pass + mark.world mark.world(x=3)(f) assert f.world.kwargs['x'] == 3 @@ -53,33 +61,43 @@ def f(): def test_pytest_mark_positional(self): mark = Mark() + def f(): pass + mark.world("hello")(f) assert f.world.args[0] == "hello" mark.world("world")(f) def test_pytest_mark_positional_func_and_keyword(self): mark = Mark() + def f(): raise Exception + m = mark.world(f, omega="hello") + def g(): pass + assert m(g) == g assert g.world.args[0] is f assert g.world.kwargs["omega"] == "hello" def test_pytest_mark_reuse(self): mark = Mark() + def f(): pass + w = mark.some w("hello", reason="123")(f) assert f.some.args[0] == "hello" assert f.some.kwargs['reason'] == "123" + def g(): pass + w("world", reason2="456")(g) assert g.some.args[0] == "world" assert 'reason' not in g.some.kwargs @@ -120,6 +138,7 @@ def test_markers(pytestconfig): rec = testdir.inline_run() rec.assertoutcome(passed=1) + def test_markers_option(testdir): testdir.makeini(""" [pytest] @@ -133,6 +152,7 @@ def test_markers_option(testdir): "*a1some*another marker", ]) + def test_markers_option_with_plugin_in_current_dir(testdir): testdir.makeconftest('pytest_plugins = "flip_flop"') testdir.makepyfile(flip_flop="""\ @@ -166,6 +186,7 @@ def test_hello(): reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + def test_strict_prohibits_unregistered_markers(testdir): testdir.makepyfile(""" import pytest @@ -175,15 +196,15 @@ def test_hello(): """) result = testdir.runpytest("--strict") assert result.ret != 0 - result.stdout.fnmatch_lines([ - "*unregisteredmark*not*registered*", - ]) + result.stdout.fnmatch_lines(["*unregisteredmark*not*registered*", ]) + @pytest.mark.parametrize("spec", [ - ("xyz", ("test_one",)), - ("xyz and xyz2", ()), - ("xyz2", ("test_two",)), - ("xyz or xyz2", ("test_one", "test_two"),) + ("xyz", + ("test_one", )), ("xyz and xyz2", + ()), ("xyz2", + ("test_two", )), ("xyz or xyz2", + ("test_one", "test_two"), ) ]) def test_mark_option(spec, testdir): testdir.makepyfile(""" @@ -202,9 +223,10 @@ def test_two(): assert len(passed) == len(passed_result) assert list(passed) == list(passed_result) + @pytest.mark.parametrize("spec", [ - ("interface", ("test_interface",)), - ("not interface", ("test_nointer",)), + ("interface", ("test_interface", )), + ("not interface", ("test_nointer", )), ]) def test_mark_option_custom(spec, testdir): testdir.makeconftest(""" @@ -227,11 +249,12 @@ def test_nointer(): assert len(passed) == len(passed_result) assert list(passed) == list(passed_result) + @pytest.mark.parametrize("spec", [ - ("interface", ("test_interface",)), - ("not interface", ("test_nointer", "test_pass")), - ("pass", ("test_pass",)), - ("not pass", ("test_interface", "test_nointer")), + ("interface", ("test_interface", )), + ("not interface", ("test_nointer", "test_pass")), + ("pass", ("test_pass", )), + ("not pass", ("test_interface", "test_nointer")), ]) def test_keyword_option_custom(spec, testdir): testdir.makepyfile(""" @@ -251,9 +274,10 @@ def test_pass(): @pytest.mark.parametrize("spec", [ - ("None", ("test_func[None]",)), - ("1.3", ("test_func[1.3]",)), - ("2-3", ("test_func[2-3]",)) + ("None", + ("test_func[None]", )), ("1.3", + ("test_func[1.3]", )), ("2-3", + ("test_func[2-3]", )) ]) def test_keyword_option_parametrize(spec, testdir): testdir.makepyfile(""" @@ -285,8 +309,8 @@ def test_func(arg): rec.assertoutcome(passed=3) -class TestFunctional: +class TestFunctional: def test_mark_per_function(self, testdir): p = testdir.makepyfile(""" import pytest @@ -378,7 +402,7 @@ def test_func(self): # test the new __iter__ interface l = list(marker) assert len(l) == 3 - assert l[0].args == ("pos0",) + assert l[0].args == ("pos0", ) assert l[1].args == () assert l[2].args == ("pos1", ) @@ -398,7 +422,7 @@ def test_d(self): """) items, rec = testdir.inline_genitems(p) for item in items: - print (item, item.keywords) + print(item, item.keywords) assert 'a' in item.keywords def test_mark_decorator_subclass_does_not_propagate_to_base(self, testdir): @@ -416,7 +440,7 @@ class Test2(Base): def test_bar(self): pass """) items, rec = testdir.inline_genitems(p) - self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',)) + self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a', )) def test_mark_decorator_baseclasses_merged(self, testdir): p = testdir.makepyfile(""" @@ -437,7 +461,8 @@ class Test2(Base2): def test_bar(self): pass """) items, rec = testdir.inline_genitems(p) - self.assert_markers(items, test_foo=('a', 'b', 'c'), + self.assert_markers(items, + test_foo=('a', 'b', 'c'), test_bar=('a', 'b', 'd')) def test_mark_with_wrong_marker(self, testdir): @@ -466,9 +491,7 @@ def test_func(arg): pass """) result = testdir.runpytest() - result.stdout.fnmatch_lines([ - "keyword: *hello*" - ]) + result.stdout.fnmatch_lines(["keyword: *hello*"]) def test_merging_markers_two_functions(self, testdir): p = testdir.makepyfile(""" @@ -484,8 +507,8 @@ def test_func(): marker = keywords['hello'] l = list(marker) assert len(l) == 2 - assert l[0].args == ("pos0",) - assert l[1].args == ("pos1",) + assert l[0].args == ("pos0", ) + assert l[1].args == ("pos1", ) def test_no_marker_match_on_unmarked_names(self, testdir): p = testdir.makepyfile(""" @@ -559,7 +582,8 @@ def assert_markers(self, items, **expected): items = dict((x.name, x) for x in items) for name, expected_markers in expected.items(): markers = items[name].keywords._markers - marker_names = set([name for (name, v) in markers.items() + marker_names = set([name + for (name, v) in markers.items() if isinstance(v, MarkInfo)]) assert marker_names == set(expected_markers) @@ -573,6 +597,7 @@ class TestClass(object): def test_method_one(self): assert 42 == 43 """) + def check(keyword, name): reprec = testdir.inline_run("-s", "-k", keyword, file_test) passed, skipped, failed = reprec.listoutcomes() @@ -586,7 +611,8 @@ def check(keyword, name): @pytest.mark.parametrize("keyword", [ 'xxx', 'xxx and test_2', 'TestClass', 'xxx and not test_1', - 'TestClass and test_2', 'xxx and TestClass and test_2']) + 'TestClass and test_2', 'xxx and TestClass and test_2' + ]) def test_select_extra_keywords(self, testdir, keyword): p = testdir.makepyfile(test_select=""" def test_1(): @@ -659,6 +685,7 @@ def test_no_magic_values(self, testdir): p = testdir.makepyfile(""" def test_one(): assert 1 """) + def assert_test_is_not_selected(keyword): reprec = testdir.inline_run("-k", keyword, p) passed, skipped, failed = reprec.countoutcomes() @@ -669,4 +696,3 @@ def assert_test_is_not_selected(keyword): assert_test_is_not_selected("__") assert_test_is_not_selected("()") - diff --git a/tox.ini b/tox.ini index ac604b88e0d..d3b13eb09ed 100644 --- a/tox.ini +++ b/tox.ini @@ -157,7 +157,7 @@ rsyncdirs=tox.ini pytest.py _pytest testing python_files=test_*.py *_test.py testing/*/*.py python_classes=Test Acceptance python_functions=test -norecursedirs = .tox ja .hg cx_freeze_source +norecursedirs = .tox ja .hg cx_freeze_source .env [flake8]