aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorholger krekel <holger@merlinux.eu>2011-03-06 09:25:40 +0100
committerholger krekel <holger@merlinux.eu>2011-03-06 09:25:40 +0100
commit1ba1e3fabee7550dbec4fbaa29509f26028922b5 (patch)
tree96340fc090285cddbb8a6ba12f69bc70b996b0c0 /_pytest
parentmerge default (diff)
downloadpypy-1ba1e3fabee7550dbec4fbaa29509f26028922b5.tar.gz
pypy-1ba1e3fabee7550dbec4fbaa29509f26028922b5.tar.bz2
pypy-1ba1e3fabee7550dbec4fbaa29509f26028922b5.zip
update to current py and pytest trunk
Diffstat (limited to '_pytest')
-rw-r--r--_pytest/assertion.py17
-rw-r--r--_pytest/capture.py14
-rw-r--r--_pytest/main.py19
-rw-r--r--_pytest/mark.py4
-rw-r--r--_pytest/monkeypatch.py4
-rw-r--r--_pytest/python.py55
-rw-r--r--_pytest/recwarn.py3
-rw-r--r--_pytest/skipping.py61
-rw-r--r--_pytest/terminal.py2
-rw-r--r--_pytest/tmpdir.py2
-rw-r--r--_pytest/unittest.py4
11 files changed, 130 insertions, 55 deletions
diff --git a/_pytest/assertion.py b/_pytest/assertion.py
index 8f09e66612..e2578242a9 100644
--- a/_pytest/assertion.py
+++ b/_pytest/assertion.py
@@ -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
diff --git a/_pytest/capture.py b/_pytest/capture.py
index 2da398383d..b7594be319 100644
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -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")
diff --git a/_pytest/main.py b/_pytest/main.py
index f1be30601c..a257ea56f2 100644
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -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
diff --git a/_pytest/mark.py b/_pytest/mark.py
index 5383e3a9d5..d9a45930c9 100644
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -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
diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py
index 1ab0b382f6..0a530b717b 100644
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -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.
"""
diff --git a/_pytest/python.py b/_pytest/python.py
index a2b7bb35d1..61a3f63c90 100644
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -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
diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py
index e2fb2b17e4..5e5a92aa6b 100644
--- a/_pytest/recwarn.py
+++ b/_pytest/recwarn.py
@@ -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
diff --git a/_pytest/skipping.py b/_pytest/skipping.py
index 106c8518c3..fe69f295e7 100644
--- a/_pytest/skipping.py
+++ b/_pytest/skipping.py
@@ -1,6 +1,7 @@
""" 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
diff --git a/_pytest/terminal.py b/_pytest/terminal.py
index 0b0ab1ee2f..2172ce6a58 100644
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -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).")
diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py
index 9eab52d545..2fd9992ccf 100644
--- a/_pytest/tmpdir.py
+++ b/_pytest/tmpdir.py
@@ -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.
diff --git a/_pytest/unittest.py b/_pytest/unittest.py
index 5131a93e35..3bcf91e901 100644
--- a/_pytest/unittest.py
+++ b/_pytest/unittest.py
@@ -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):