Commit 1ba1e3fa authored by holger krekel's avatar holger krekel

update to current py and pytest trunk

parent 16a77948
......@@ -12,7 +12,7 @@ def pytest_addoption(parser):
help="disable python assert expression reinterpretation."),
def pytest_configure(config):
# The _pytesthook attribute on the AssertionError is used by
# The _reprcompare attribute on the py.code module is used by
# py._code._assertionnew to detect this plugin was loaded and in
# turn call the hooks defined here as part of the
# DebugInterpreter.
......@@ -51,7 +51,7 @@ except NameError:
def pytest_assertrepr_compare(op, left, right):
"""return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=width/2)
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)
......@@ -165,4 +165,15 @@ def _notin_text(term, text):
head = text[:index]
tail = text[index+len(term):]
correct_text = head + tail
return _diff_text(correct_text, text)
diff = _diff_text(correct_text, text)
newdiff = ['%s is contained here:' % py.io.saferepr(term, maxsize=42)]
for line in diff:
if line.startswith('Skipping'):
continue
if line.startswith('- '):
continue
if line.startswith('+ '):
newdiff.append(' ' + line[2:])
else:
newdiff.append(line)
return newdiff
......@@ -192,18 +192,16 @@ class CaptureManager:
return rep
def pytest_funcarg__capsys(request):
"""captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
"""enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
"""
return CaptureFuncarg(py.io.StdCapture)
def pytest_funcarg__capfd(request):
"""captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
"""enables capturing of writes to file descriptors 1 and 2 and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
"""
if not hasattr(os, 'dup'):
py.test.skip("capfd funcarg needs os.dup")
......
......@@ -121,9 +121,6 @@ class HookProxy:
def compatproperty(name):
def fget(self):
#print "retrieving %r property from %s" %(name, self.fspath)
py.log._apiwarn("2.0", "use pytest.%s for "
"test collection and item classes" % name)
return getattr(pytest, name)
return property(fget, None, None,
"deprecated attribute %r, use pytest.%s" % (name,name))
......@@ -157,6 +154,14 @@ class Node(object):
File = compatproperty("File")
Item = compatproperty("Item")
def _getcustomclass(self, name):
cls = getattr(self, name)
if cls != getattr(pytest, name):
py.log._apiwarn("2.0", "use of node.%s is deprecated, "
"use pytest_pycollect_makeitem(...) to create custom "
"collection nodes" % name)
return cls
def __repr__(self):
return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
......@@ -449,7 +454,7 @@ class Session(FSCollector):
p = p.dirpath()
else:
p = p.new(basename=p.purebasename+".py")
return p
return str(p)
def _parsearg(self, arg):
""" return (fspath, names) tuple after checking the file exists. """
......@@ -495,9 +500,15 @@ class Session(FSCollector):
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
has_matched = False
for x in rep.result:
if x.name == name:
resultnodes.extend(self.matchnodes([x], nextnames))
has_matched = True
# XXX accept IDs that don't have "()" for class instances
if not has_matched and len(rep.result) == 1 and x.name == "()":
nextnames.insert(0, name)
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
return resultnodes
......
......@@ -89,8 +89,8 @@ class MarkGenerator:
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 usually created by writing::
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
MarkDecorator instances are often created like this::
mark1 = py.test.mark.NAME # simple MarkDecorator
mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator
......
......@@ -14,8 +14,8 @@ def pytest_funcarg__monkeypatch(request):
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
All modifications will be undone after the requesting
test function has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
"""
......
......@@ -73,7 +73,8 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
if collector._istestclasscandidate(name, obj):
#if hasattr(collector.obj, 'unittest'):
# return # we assume it's a mixin class for a TestCase derived one
return collector.Class(name, parent=collector)
Class = collector._getcustomclass("Class")
return Class(name, parent=collector)
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
if is_generator(obj):
return Generator(name, parent=collector)
......@@ -213,16 +214,18 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
extra.append(cls())
plugins = self.getplugins() + extra
gentesthook.pcall(plugins, metafunc=metafunc)
Function = self._getcustomclass("Function")
if not metafunc._calls:
return self.Function(name, parent=self)
return Function(name, parent=self)
l = []
for callspec in metafunc._calls:
subname = "%s[%s]" %(name, callspec.id)
function = self.Function(name=subname, parent=self,
function = Function(name=subname, parent=self,
callspec=callspec, callobj=funcobj, keywords={callspec.id:True})
l.append(function)
return l
class Module(pytest.File, PyCollectorMixin):
def _getobj(self):
return self._memoizedcall('_obj', self._importtestmodule)
......@@ -272,7 +275,7 @@ class Module(pytest.File, PyCollectorMixin):
class Class(PyCollectorMixin, pytest.Collector):
def collect(self):
return [self.Instance(name="()", parent=self)]
return [self._getcustomclass("Instance")(name="()", parent=self)]
def setup(self):
setup_class = getattr(self.obj, 'setup_class', None)
......@@ -297,13 +300,8 @@ class Instance(PyCollectorMixin, pytest.Collector):
class FunctionMixin(PyobjMixin):
""" mixin for the code common to Function and Generator.
"""
def setup(self):
""" perform setup for this test function. """
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
if hasattr(self, '_preservedparent'):
obj = self._preservedparent
elif isinstance(self.parent, Instance):
......@@ -311,6 +309,10 @@ class FunctionMixin(PyobjMixin):
self.obj = self._getobj()
else:
obj = self.parent.obj
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
setup_func_or_method = getattr(obj, name, None)
if setup_func_or_method is not None:
setup_func_or_method(self.obj)
......@@ -487,10 +489,11 @@ def hasinit(obj):
return True
def getfuncargnames(function):
def getfuncargnames(function, startindex=None):
# XXX merge with main.py's varnames
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
startindex = py.std.inspect.ismethod(function) and 1 or 0
if startindex is None:
startindex = py.std.inspect.ismethod(function) and 1 or 0
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)
......@@ -519,7 +522,8 @@ class Metafunc:
self.config = config
self.module = module
self.function = function
self.funcargnames = getfuncargnames(function)
self.funcargnames = getfuncargnames(function,
startindex=int(cls is not None))
self.cls = cls
self.module = module
self._calls = []
......@@ -527,7 +531,11 @@ class Metafunc:
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
""" add a new call to the underlying test function during the
collection phase of a test run.
collection phase of a test run. Note that request.addcall() is
called during the test collection phase prior and independently
to actual test execution. Therefore you should perform setup
of resources in a funcarg factory which can be instrumented
with the ``param``.
:arg funcargs: argument keyword dictionary used when invoking
the test function.
......@@ -537,14 +545,15 @@ class Metafunc:
list of calls to the test function will be used.
:arg param: will be exposed to a later funcarg factory invocation
through the ``request.param`` attribute. Setting it (instead of
directly providing a ``funcargs`` ditionary) is called
*indirect parametrization*. Indirect parametrization is
preferable if test values are expensive to setup or can
only be created after certain fixtures or test-run related
initialization code has been run.
through the ``request.param`` attribute. It allows to
defer test fixture setup activities to when an actual
test is run.
"""
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:
if name not in self.funcargnames:
pytest.fail("funcarg %r not used in this function." % name)
if id is None:
raise ValueError("id=None not allowed")
if id is _notexists:
......@@ -556,7 +565,13 @@ class Metafunc:
self._calls.append(CallSpec(funcargs, id, param))
class FuncargRequest:
""" A request for function arguments from a test function. """
""" A request for function arguments from a test function.
Note that there is an optional ``param`` attribute in case
there was an invocation to metafunc.addcall(param=...).
If no such call was done in a ``pytest_generate_tests``
hook, the attribute will not be present.
"""
_argprefix = "pytest_funcarg__"
_argname = None
......
......@@ -8,6 +8,9 @@ def pytest_funcarg__recwarn(request):
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
See http://docs.python.org/library/warnings.html for information
on warning categories.
"""
if sys.version_info >= (2,7):
import warnings
......
""" support for skip/xfail functions and markers. """
import py, pytest
import sys
def pytest_addoption(parser):
group = parser.getgroup("general")
......@@ -32,9 +33,39 @@ class MarkEvaluator:
return bool(self.holder)
__nonzero__ = __bool__
def wasvalid(self):
return not hasattr(self, 'exc')
def istrue(self):
try:
return self._istrue()
except KeyboardInterrupt:
raise
except:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",]
msg.append("SyntaxError: invalid syntax")
else:
msg = py.std.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': py.std.os, 'sys': py.std.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 self.holder:
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
d = self._getglobals()
if self.holder.args:
self.result = False
for expr in self.holder.args:
......@@ -42,7 +73,7 @@ class MarkEvaluator:
if isinstance(expr, str):
result = cached_eval(self.item.config, expr, d)
else:
result = expr
pytest.fail("expression is not a string")
if result:
self.result = True
self.expr = expr
......@@ -60,7 +91,7 @@ class MarkEvaluator:
if not hasattr(self, 'expr'):
return ""
else:
return "condition: " + self.expr
return "condition: " + str(self.expr)
return expl
......@@ -99,16 +130,17 @@ def pytest_runtest_makereport(__multicall__, item, call):
return rep
rep = __multicall__.execute()
evalxfail = item._evalxfail
if not item.config.option.runxfail and evalxfail.istrue():
if call.excinfo:
rep.outcome = "skipped"
rep.keywords['xfail'] = evalxfail.getexplanation()
elif call.when == "call":
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
else:
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
if not item.config.option.runxfail:
if evalxfail.wasvalid() and evalxfail.istrue():
if call.excinfo:
rep.outcome = "skipped"
rep.keywords['xfail'] = evalxfail.getexplanation()
elif call.when == "call":
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
return rep
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
return rep
# called by terminalreporter progress reporting
......@@ -179,7 +211,8 @@ def cached_eval(config, expr, d):
except KeyError:
#import sys
#print >>sys.stderr, ("cache-miss: %r" % expr)
config._evalcache[expr] = x = eval(expr, d)
exprcode = py.code.compile(expr, mode="eval")
config._evalcache[expr] = x = eval(exprcode, d)
return x
......
......@@ -25,7 +25,7 @@ def pytest_addoption(parser):
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/no).")
help="traceback print mode (long/short/line/native/no).")
group._addoption('--fulltrace',
action="store_true", dest="fulltrace", default=False,
help="don't cut any tracebacks (default is to cut).")
......
......@@ -59,7 +59,7 @@ def pytest_unconfigure(config):
def pytest_funcarg__tmpdir(request):
"""return a temporary directory path object
unique to each test function invocation,
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
......
......@@ -102,6 +102,10 @@ class TestCaseFunction(pytest.Function):
def runtest(self):
self._testcase(result=self)
def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo)
excinfo.traceback = excinfo.traceback.filter(lambda x:not x.frame.f_globals.get('__unittest'))
@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):
......
......@@ -8,7 +8,7 @@ dictionary or an import path.
(c) Holger Krekel and others, 2004-2010
"""
__version__ = '1.4.1.dev2'
__version__ = '1.4.2.dev0'
from py import _apipkg
......@@ -145,4 +145,3 @@ _apipkg.initpkg(__name__, attr={'_apipkg': _apipkg}, exportdefs={
},
})
......@@ -267,20 +267,9 @@ class DebugInterpreter(ast.NodeVisitor):
result = self.frame.eval(co, **ns)
except Exception:
raise Failure(explanation)
# Only show result explanation if it's not a builtin call or returns a
# bool.
if not isinstance(call.func, ast.Name) or \
not self._is_builtin_name(call.func):
source = "isinstance(__exprinfo_value, bool)"
co = self._compile(source)
try:
is_bool = self.frame.eval(co, __exprinfo_value=result)
except Exception:
is_bool = False
if not is_bool:
pattern = "%s\n{%s = %s\n}"
rep = self.frame.repr(result)
explanation = pattern % (rep, rep, explanation)
pattern = "%s\n{%s = %s\n}"
rep = self.frame.repr(result)
explanation = pattern % (rep, rep, explanation)
return explanation, result
def _is_builtin_name(self, name):
......
......@@ -215,7 +215,7 @@ class Source(object):
msglines = self.lines[:ex.lineno]
if ex.offset:
msglines.append(" "*ex.offset + '^')
msglines.append("syntax error probably generated here: %s" % filename)
msglines.append("(code was compiled probably from here: %s)" % filename)
newex = SyntaxError('\n'.join(msglines))
newex.offset = ex.offset
newex.lineno = ex.lineno
......
......@@ -37,6 +37,8 @@ class ErrorMaker(object):
_errno2class = {}
def __getattr__(self, name):
if name[0] == "_":
raise AttributeError(name)
eno = getattr(errno, name)
cls = self._geterrnoclass(eno)
setattr(self, name, cls)
......
......@@ -158,11 +158,13 @@ class LocalPath(FSBase):
def samefile(self, other):
""" return True if 'other' references the same file as 'self'. """
if not iswin32:
return py.error.checked_call(
os.path.samefile, str(self), str(other))
if self == other:
return True
if not iswin32:
return py.error.checked_call(os.path.samefile, str(self), str(other))
return False
other = os.path.abspath(str(other))
return self == other
def remove(self, rec=1, ignore_errors=False):
""" remove a file or directory (or a directory tree if rec=1).
......@@ -747,7 +749,7 @@ class LocalPath(FSBase):
pass
try:
os.symlink(src, dest)
except (OSError, AttributeError): # AttributeError on win32
except (OSError, AttributeError, NotImplementedError):
pass
return udir
......
"""
unit and functional testing with Python.
"""
__version__ = '2.0.1.dev9'
__version__ = '2.0.2.dev4'
__all__ = ['main']
from _pytest.core import main, UsageError, _preloadplugins
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment