merge with upstream

This commit is contained in:
Anatoly Bubenkov
2013-07-06 10:06:12 +02:00
60 changed files with 1623 additions and 477 deletions

View File

@@ -1,2 +1,2 @@
#
__version__ = '2.3.5.dev8'
__version__ = '2.4.0.dev5'

View File

@@ -177,6 +177,10 @@ def _write_pyc(co, source_path, pyc):
# This happens when we get a EEXIST in find_module creating the
# __pycache__ directory and __pycache__ is by some non-dir node.
return False
elif err == errno.EACCES:
# The directory is read-only; this can happen for example when
# running the tests in a package installed as root
return False
raise
try:
fp.write(imp.get_magic())
@@ -215,11 +219,17 @@ def _rewrite_test(state, fn):
if (not source.startswith(BOM_UTF8) and
(not cookie_re.match(source[0:end1]) or
not cookie_re.match(source[end1:end2]))):
if hasattr(state, "_indecode"):
return None # encodings imported us again, we don't rewrite
state._indecode = True
try:
source.decode("ascii")
except UnicodeDecodeError:
# Let it fail in real import.
return None
try:
source.decode("ascii")
except UnicodeDecodeError:
# Let it fail in real import.
return None
finally:
del state._indecode
# On Python versions which are not 2.7 and less than or equal to 3.1, the
# parser expects *nix newlines.
if REWRITE_NEWLINES:

View File

@@ -10,6 +10,7 @@ BuiltinAssertionError = py.builtin.builtins.AssertionError
# DebugInterpreter.
_reprcompare = None
def format_explanation(explanation):
"""This formats an explanation
@@ -85,7 +86,7 @@ except NameError:
def assertrepr_compare(config, op, left, right):
"""Return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
summary = '%s %s %s' % (left_repr, op, right_repr)
@@ -93,7 +94,7 @@ def assertrepr_compare(config, op, left, right):
issequence = lambda x: isinstance(x, (list, tuple))
istext = lambda x: isinstance(x, basestring)
isdict = lambda x: isinstance(x, dict)
isset = lambda x: isinstance(x, set)
isset = lambda x: isinstance(x, (set, frozenset))
verbose = config.getoption('verbose')
explanation = None
@@ -114,9 +115,9 @@ def assertrepr_compare(config, op, left, right):
raise
except:
excinfo = py.code.ExceptionInfo()
explanation = ['(pytest_assertion plugin: representation of '
'details failed. Probably an object has a faulty __repr__.)',
str(excinfo)]
explanation = [
'(pytest_assertion plugin: representation of details failed. '
'Probably an object has a faulty __repr__.)', str(excinfo)]
if not explanation:
return None
@@ -132,7 +133,7 @@ def _diff_text(left, right, verbose=False):
"""
explanation = []
if not verbose:
i = 0 # just in case left or right has zero length
i = 0 # just in case left or right has zero length
for i in range(min(len(left), len(right))):
if left[i] != right[i]:
break
@@ -166,13 +167,15 @@ def _compare_eq_sequence(left, right, verbose=False):
(i, left[i], right[i])]
break
if len(left) > len(right):
explanation += ['Left contains more items, '
'first extra item: %s' % py.io.saferepr(left[len(right)],)]
explanation += [
'Left contains more items, first extra item: %s' %
py.io.saferepr(left[len(right)],)]
elif len(left) < len(right):
explanation += ['Right contains more items, '
'first extra item: %s' % py.io.saferepr(right[len(left)],)]
return explanation # + _diff_text(py.std.pprint.pformat(left),
# py.std.pprint.pformat(right))
explanation += [
'Right contains more items, first extra item: %s' %
py.io.saferepr(right[len(left)],)]
return explanation # + _diff_text(py.std.pprint.pformat(left),
# py.std.pprint.pformat(right))
def _compare_eq_set(left, right, verbose=False):
@@ -210,12 +213,12 @@ def _compare_eq_dict(left, right, verbose=False):
if extra_left:
explanation.append('Left contains more items:')
explanation.extend(py.std.pprint.pformat(
dict((k, left[k]) for k in extra_left)).splitlines())
dict((k, left[k]) for k in extra_left)).splitlines())
extra_right = set(right) - set(left)
if extra_right:
explanation.append('Right contains more items:')
explanation.extend(py.std.pprint.pformat(
dict((k, right[k]) for k in extra_right)).splitlines())
dict((k, right[k]) for k in extra_right)).splitlines())
return explanation

View File

@@ -34,6 +34,14 @@ class ReprFailDoctest(TerminalRepr):
self.reprlocation.toterminal(tw)
class DoctestItem(pytest.Item):
def __init__(self, name, parent, runner=None, dtest=None):
super(DoctestItem, self).__init__(name, parent)
self.runner = runner
self.dtest = dtest
def runtest(self):
self.runner.run(self.dtest)
def repr_failure(self, excinfo):
doctest = py.std.doctest
if excinfo.errisinstance((doctest.DocTestFailure,
@@ -76,7 +84,7 @@ class DoctestItem(pytest.Item):
return super(DoctestItem, self).repr_failure(excinfo)
def reportinfo(self):
return self.fspath, None, "[doctest]"
return self.fspath, None, "[doctest] %s" % self.name
class DoctestTextfile(DoctestItem, pytest.File):
def runtest(self):
@@ -91,8 +99,8 @@ class DoctestTextfile(DoctestItem, pytest.File):
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
raise_on_error=True, verbose=0)
class DoctestModule(DoctestItem, pytest.File):
def runtest(self):
class DoctestModule(pytest.File):
def collect(self):
doctest = py.std.doctest
if self.fspath.basename == "conftest.py":
module = self.config._conftest.importconftest(self.fspath)
@@ -102,7 +110,11 @@ class DoctestModule(DoctestItem, pytest.File):
self.funcargs = {}
self._fixtureinfo = FuncFixtureInfo((), [], {})
fixture_request = FixtureRequest(self)
failed, tot = doctest.testmod(
module, raise_on_error=True, verbose=0,
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
optionflags=doctest.ELLIPSIS)
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
runner = doctest.DebugRunner(verbose=0, optionflags=doctest.ELLIPSIS)
for test in finder.find(module, module.__name__,
extraglobs=doctest_globals):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)

View File

@@ -36,7 +36,8 @@ class Junit(py.xml.Namespace):
# | [#x10000-#x10FFFF]
_legal_chars = (0x09, 0x0A, 0x0d)
_legal_ranges = (
(0x20, 0xD7FF),
(0x20, 0x7E),
(0x80, 0xD7FF),
(0xE000, 0xFFFD),
(0x10000, 0x10FFFF),
)
@@ -103,7 +104,7 @@ class LogXML(object):
classnames.insert(0, self.prefix)
self.tests.append(Junit.testcase(
classname=".".join(classnames),
name=names[-1],
name=bin_xml_escape(names[-1]),
time=getattr(report, 'duration', 0)
))

View File

@@ -97,6 +97,7 @@ def wrap_session(config, doit):
if session._testsfailed:
session.exitstatus = EXIT_TESTSFAILED
finally:
session.startdir.chdir()
if initstate >= 2:
config.hook.pytest_sessionfinish(
session=session,
@@ -216,6 +217,9 @@ class Node(object):
#: keywords/markers collected from all scopes
self.keywords = NodeKeywords(self)
#: allow adding of extra keywords to use for matching
self.extra_keyword_matches = set()
#self.extrainit()
@property
@@ -307,6 +311,14 @@ class Node(object):
chain.reverse()
return chain
def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
extra_keywords = set()
item = self
for item in self.listchain():
extra_keywords.update(item.extra_keyword_matches)
return extra_keywords
def listnames(self):
return [x.name for x in self.listchain()]
@@ -441,6 +453,7 @@ class Session(FSCollector):
self.shouldstop = False
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local()
def pytest_collectstart(self):
if self.shouldstop:

View File

@@ -1,44 +1,56 @@
""" 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',
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 keywords. Example: -k 'test_method or test_other' "
"matches all test functions whose name contains "
"'test_method' or 'test_other'.")
"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",
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).")
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.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
@@ -67,32 +79,76 @@ def pytest_collection_modifyitems(items, config):
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
class SubstringDict:
def __init__(self, mydict):
self._mydict = mydict
def __getitem__(self, name):
for key in self._mydict:
if name in key:
class MarkMapping:
"""Provides a local mapping for markers.
Only the marker names from the given :class:`NodeKeywords` will be mapped,
so the names are taken only from :class:`MarkInfo` or
:class:`MarkDecorator` items.
"""
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, markname):
return markname 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, matchexpr):
return eval(matchexpr, {}, BoolDict(colitem.keywords))
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.
"""
keywordexpr = keywordexpr.replace("-", "not ")
return eval(keywordexpr, {}, SubstringDict(colitem.keywords))
mapped_names = set()
# Add the names of the current item and any parent items
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
for name in colitem.function.__dict__:
mapped_names.add(name)
return eval(keywordexpr, {}, KeywordMapping(mapped_names))
def pytest_configure(config):
if config.option.strict:
pytest.mark._config = config
class MarkGenerator:
""" Factory for :class:`MarkDecorator` objects - exposed as
a ``py.test.mark`` singleton instance. Example::
@@ -126,6 +182,7 @@ class MarkGenerator:
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
@@ -149,7 +206,7 @@ class MarkDecorator:
def __repr__(self):
d = self.__dict__.copy()
name = d.pop('markname')
return "<MarkDecorator %r %r>" %(name, d)
return "<MarkDecorator %r %r>" % (name, d)
def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info.
@@ -162,15 +219,17 @@ class MarkDecorator:
if hasattr(func, 'pytestmark'):
l = func.pytestmark
if not isinstance(l, list):
func.pytestmark = [l, self]
func.pytestmark = [l, self]
else:
l.append(self)
l.append(self)
else:
func.pytestmark = [self]
func.pytestmark = [self]
else:
holder = getattr(func, self.markname, None)
if holder is None:
holder = MarkInfo(self.markname, self.args, self.kwargs)
holder = MarkInfo(
self.markname, self.args, self.kwargs
)
setattr(func, self.markname, holder)
else:
holder.add(self.args, self.kwargs)
@@ -180,6 +239,7 @@ class MarkDecorator:
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):
@@ -193,7 +253,8 @@ class MarkInfo:
def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self.name, self.args, self.kwargs)
self.name, self.args, self.kwargs
)
def add(self, args, kwargs):
""" add a MarkInfo with the given args and kwargs. """
@@ -205,4 +266,3 @@ class MarkInfo:
""" yield MarkInfo objects each relating to a marking-call. """
for args, kwargs in self._arglist:
yield MarkInfo(self.name, args, kwargs)

View File

@@ -4,6 +4,7 @@ import inspect
import sys
import pytest
from _pytest.main import getfslineno
from _pytest.mark import MarkDecorator, MarkInfo
from _pytest.monkeypatch import monkeypatch
from py._code.code import TerminalRepr
@@ -177,7 +178,8 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
if collector.classnamefilter(name):
Class = collector._getcustomclass("Class")
return Class(name, parent=collector)
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
elif collector.funcnamefilter(name) and hasattr(obj, '__call__') and \
getfixturemarker(obj) is None:
if is_generator(obj):
return Generator(name, parent=collector)
else:
@@ -369,7 +371,9 @@ class Module(pytest.File, PyCollector):
return mod
def setup(self):
setup_module = xunitsetup(self.obj, "setup_module")
setup_module = xunitsetup(self.obj, "setUpModule")
if setup_module is None:
setup_module = xunitsetup(self.obj, "setup_module")
if setup_module is not None:
#XXX: nose compat hack, move to nose plugin
# if it takes a positional arg, its probably a pytest style one
@@ -380,7 +384,9 @@ class Module(pytest.File, PyCollector):
setup_module()
def teardown(self):
teardown_module = xunitsetup(self.obj, 'teardown_module')
teardown_module = xunitsetup(self.obj, 'tearDownModule')
if teardown_module is None:
teardown_module = xunitsetup(self.obj, 'teardown_module')
if teardown_module is not None:
#XXX: nose compat hack, move to nose plugin
# if it takes a positional arg, its probably a py.test style one
@@ -564,11 +570,13 @@ class CallSpec2(object):
self._globalid_args = set()
self._globalparam = _notexists
self._arg2scopenum = {} # used for sorting parametrized resources
self.keywords = {}
def copy(self, metafunc):
cs = CallSpec2(self.metafunc)
cs.funcargs.update(self.funcargs)
cs.params.update(self.params)
cs.keywords.update(self.keywords)
cs._arg2scopenum.update(self._arg2scopenum)
cs._idlist = list(self._idlist)
cs._globalid = self._globalid
@@ -592,7 +600,7 @@ class CallSpec2(object):
def id(self):
return "-".join(map(str, filter(None, self._idlist)))
def setmulti(self, valtype, argnames, valset, id, scopenum=0):
def setmulti(self, valtype, argnames, valset, id, keywords, scopenum=0):
for arg,val in zip(argnames, valset):
self._checkargnotcontained(arg)
getattr(self, valtype)[arg] = val
@@ -604,6 +612,7 @@ class CallSpec2(object):
if val is _notexists:
self._emptyparamspecified = True
self._idlist.append(id)
self.keywords.update(keywords)
def setall(self, funcargs, id, param):
for x in funcargs:
@@ -644,13 +653,15 @@ class Metafunc(FuncargnamesCompatAttr):
during the collection phase. If you need to setup expensive resources
see about setting indirect=True to do it rather at test setup time.
:arg argnames: an argument name or a list of argument names
:arg argnames: a comma-separated string denoting one or more argument
names, or a list/tuple of argument strings.
:arg argvalues: The list of argvalues determines how often a test is invoked
with different argument values. If only one argname was specified argvalues
is a list of simple values. If N argnames were specified, argvalues must
be a list of N-tuples, where each tuple-element specifies a value for its
respective argname.
:arg argvalues: The list of argvalues determines how often a
test is invoked with different argument values. If only one
argname was specified argvalues is a list of simple values. If N
argnames were specified, argvalues must be a list of N-tuples,
where each tuple-element specifies a value for its respective
argname.
:arg indirect: if True each argvalue corresponding to an argname will
be passed as request.param to its respective argname fixture
@@ -666,8 +677,24 @@ class Metafunc(FuncargnamesCompatAttr):
It will also override any fixture-function defined scope, allowing
to set a dynamic scope using test context or configuration.
"""
# individual parametrized argument sets can be wrapped in a
# marker in which case we unwrap the values and apply the mark
# at Function init
newkeywords = {}
unwrapped_argvalues = []
for i, argval in enumerate(argvalues):
if isinstance(argval, MarkDecorator):
newmark = MarkDecorator(argval.markname,
argval.args[:-1], argval.kwargs)
newkeywords[i] = {newmark.markname: newmark}
argval = argval.args[-1]
unwrapped_argvalues.append(argval)
argvalues = unwrapped_argvalues
if not isinstance(argnames, (tuple, list)):
argnames = (argnames,)
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if len(argnames) == 1:
argvalues = [(val,) for val in argvalues]
if not argvalues:
argvalues = [(_notexists,) * len(argnames)]
@@ -690,7 +717,7 @@ class Metafunc(FuncargnamesCompatAttr):
assert len(valset) == len(argnames)
newcallspec = callspec.copy(self)
newcallspec.setmulti(valtype, argnames, valset, ids[i],
scopenum)
newkeywords.get(i, {}), scopenum)
newcalls.append(newcallspec)
self._calls = newcalls
@@ -907,6 +934,9 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
for name, val in (py.builtin._getfuncdict(self.obj) or {}).items():
self.keywords[name] = val
if callspec:
for name, val in callspec.keywords.items():
self.keywords[name] = val
if keywords:
for name, val in keywords.items():
self.keywords[name] = val
@@ -917,20 +947,25 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
self.cls,
funcargs=not isyield)
self.fixturenames = fi.names_closure
if isyield:
assert not callspec, (
if callspec is not None:
self.callspec = callspec
self._initrequest()
def _initrequest(self):
if self._isyieldedfunction():
assert not hasattr(self, "callspec"), (
"yielded functions (deprecated) cannot have funcargs")
self.funcargs = {}
else:
if callspec is not None:
self.callspec = callspec
self.funcargs = callspec.funcargs or {}
if hasattr(self, "callspec"):
callspec = self.callspec
self.funcargs = callspec.funcargs.copy()
self._genid = callspec.id
if hasattr(callspec, "param"):
self.param = callspec.param
else:
self.funcargs = {}
self._request = req = FixtureRequest(self)
self._request = FixtureRequest(self)
@property
def function(self):
@@ -1561,15 +1596,7 @@ class FixtureManager:
continue
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
# or are "@pytest.fixture" marked
try:
marker = obj._pytestfixturefunction
except KeyboardInterrupt:
raise
except Exception:
# some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions
marker = None
marker = getfixturemarker(obj)
if marker is None:
if not name.startswith(self._argprefix):
continue
@@ -1615,6 +1642,29 @@ class FixtureManager:
except ValueError:
pass
def call_fixture_func(fixturefunc, request, kwargs):
if is_generator(fixturefunc):
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:
fs, lineno = getfslineno(fixturefunc)
location = "%s:%s" % (fs, lineno+1)
pytest.fail(
"fixture function %s has more than one 'yield': \n%s" %
(fixturefunc.__name__, location), pytrace=False)
request.addfinalizer(teardown)
else:
res = fixturefunc(**kwargs)
return res
class FixtureDef:
""" A container for a factory definition. """
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
@@ -1665,7 +1715,7 @@ class FixtureDef:
fixturefunc = fixturefunc.__get__(request.instance)
except AttributeError:
pass
result = fixturefunc(**kwargs)
result = call_fixture_func(fixturefunc, request, kwargs)
assert not hasattr(self, "cached_result")
self.cached_result = result
return result
@@ -1697,29 +1747,39 @@ def getfuncargnames(function, startindex=None):
def parametrize_sorted(items, ignore, cache, scopenum):
if scopenum >= 3:
return items
newitems = []
olditems = []
# we pick the first item which has a arg/param combo in the
# requested scope and sort other items with the same combo
# into "newitems" which then is a list of all items using this
# arg/param.
similar_items = []
other_items = []
slicing_argparam = None
slicing_index = 0
for item in items:
argparamlist = getfuncargparams(item, ignore, scopenum, cache)
if slicing_argparam is None and argparamlist:
slicing_argparam = argparamlist[0]
slicing_index = len(olditems)
slicing_index = len(other_items)
if slicing_argparam in argparamlist:
newitems.append(item)
similar_items.append(item)
else:
olditems.append(item)
if newitems:
other_items.append(item)
if (len(similar_items) + slicing_index) > 1:
newignore = ignore.copy()
newignore.add(slicing_argparam)
newitems = parametrize_sorted(newitems + olditems[slicing_index:],
newignore, cache, scopenum)
old1 = parametrize_sorted(olditems[:slicing_index], newignore,
cache, scopenum+1)
return old1 + newitems
part2 = parametrize_sorted(
similar_items + other_items[slicing_index:],
newignore, cache, scopenum)
part1 = parametrize_sorted(
other_items[:slicing_index], newignore,
cache, scopenum+1)
return part1 + part2
else:
olditems = parametrize_sorted(olditems, ignore, cache, scopenum+1)
return olditems + newitems
other_items = parametrize_sorted(other_items, ignore, cache, scopenum+1)
return other_items + similar_items
def getfuncargparams(item, ignore, scopenum, cache):
""" return list of (arg,param) tuple, sorted by broader scope first. """
@@ -1766,6 +1826,18 @@ def getfuncargparams(item, ignore, scopenum, cache):
def xunitsetup(obj, name):
meth = getattr(obj, name, None)
if meth is not None:
if not hasattr(meth, "_pytestfixturefunction"):
return meth
if getfixturemarker(meth) is None:
return meth
def getfixturemarker(obj):
""" return fixturemarker or None if it doesn't exist or raised
exceptions."""
try:
return getattr(obj, "_pytestfixturefunction", None)
except KeyboardInterrupt:
raise
except Exception:
# some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions
return None

View File

@@ -63,12 +63,20 @@ def pytest_runtest_protocol(item, nextitem):
return True
def runtestprotocol(item, log=True, nextitem=None):
hasrequest = hasattr(item, "_request")
if hasrequest and not item._request:
item._initrequest()
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log,
nextitem=nextitem))
# after all teardown hooks have been called
# want funcargs and request info to go away
if hasrequest:
item._request = False
item.funcargs = None
return reports
def pytest_runtest_setup(item):
@@ -190,7 +198,8 @@ def pytest_runtest_makereport(item, call):
if call.when == "call":
longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo)
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
duration=duration)

View File

@@ -89,7 +89,11 @@ class MarkEvaluator:
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
pytest.fail("expression is not a string")
if self.get("reason") is None:
# XXX better be checked at collection time
pytest.fail("you need to specify reason=STRING "
"when using booleans as conditions.")
result = bool(expr)
if result:
self.result = True
self.expr = expr

View File

@@ -454,10 +454,14 @@ class TerminalReporter:
if val:
parts.append("%d %s" %(len(val), key))
line = ", ".join(parts)
# XXX coloring
msg = "%s in %.2f seconds" %(line, session_duration)
if self.verbosity >= 0:
self.write_sep("=", msg, bold=True)
markup = dict(bold=True)
if 'failed' in self.stats:
markup['red'] = True
else:
markup['green'] = True
self.write_sep("=", msg, **markup)
#else:
# self.write_line(msg, bold=True)