* reworked per-test output capturing into the pytest_iocapture.py plugin
* removed all capturing code from config object and pytest_default plugins * item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr) * added a few logging tests --HG-- branch : 1.0.x
This commit is contained in:
parent
875ebc18ef
commit
04e9197fd6
|
@ -1,6 +1,11 @@
|
|||
Changes between 1.0.0b8 and 1.0.0b9
|
||||
=====================================
|
||||
|
||||
* reworked per-test output capturing into the pytest_iocapture.py plugin
|
||||
and thus removed capturing code from config object
|
||||
|
||||
* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
|
||||
|
||||
|
||||
Changes between 1.0.0b7 and 1.0.0b8
|
||||
=====================================
|
||||
|
|
|
@ -247,7 +247,8 @@ class Node(object):
|
|||
return col._getitembynames(names)
|
||||
_fromtrail = staticmethod(_fromtrail)
|
||||
|
||||
def _repr_failure_py(self, excinfo, outerr):
|
||||
def _repr_failure_py(self, excinfo, outerr=None):
|
||||
assert outerr is None, "XXX deprecated"
|
||||
excinfo.traceback = self._prunetraceback(excinfo.traceback)
|
||||
# XXX temporary hack: getrepr() should not take a 'style' argument
|
||||
# at all; it should record all data in all cases, and the style
|
||||
|
@ -256,13 +257,9 @@ class Node(object):
|
|||
style = "short"
|
||||
else:
|
||||
style = "long"
|
||||
repr = excinfo.getrepr(funcargs=True,
|
||||
return excinfo.getrepr(funcargs=True,
|
||||
showlocals=self.config.option.showlocals,
|
||||
style=style)
|
||||
for secname, content in zip(["out", "err"], outerr):
|
||||
if content:
|
||||
repr.addsection("Captured std%s" % secname, content.rstrip())
|
||||
return repr
|
||||
|
||||
repr_failure = _repr_failure_py
|
||||
shortfailurerepr = "F"
|
||||
|
@ -291,9 +288,10 @@ class Collector(Node):
|
|||
if colitem.name == name:
|
||||
return colitem
|
||||
|
||||
def repr_failure(self, excinfo, outerr):
|
||||
def repr_failure(self, excinfo, outerr=None):
|
||||
""" represent a failure. """
|
||||
return self._repr_failure_py(excinfo, outerr)
|
||||
assert outerr is None, "XXX deprecated"
|
||||
return self._repr_failure_py(excinfo)
|
||||
|
||||
def _memocollect(self):
|
||||
""" internal helper method to cache results of calling collect(). """
|
||||
|
|
|
@ -240,20 +240,6 @@ class Config(object):
|
|||
finally:
|
||||
config_per_process = py.test.config = oldconfig
|
||||
|
||||
def _getcapture(self, path=None):
|
||||
if self.option.nocapture:
|
||||
iocapture = "no"
|
||||
else:
|
||||
iocapture = self.getvalue("iocapture", path=path)
|
||||
if iocapture == "fd":
|
||||
return py.io.StdCaptureFD()
|
||||
elif iocapture == "sys":
|
||||
return py.io.StdCapture()
|
||||
elif iocapture == "no":
|
||||
return py.io.StdCapture(out=False, err=False, in_=False)
|
||||
else:
|
||||
raise self.Error("unknown io capturing: " + iocapture)
|
||||
|
||||
def getxspecs(self):
|
||||
xspeclist = []
|
||||
for xspec in self.getvalue("tx"):
|
||||
|
@ -287,29 +273,6 @@ class Config(object):
|
|||
roots.append(pydir)
|
||||
return roots
|
||||
|
||||
def guardedcall(self, func):
|
||||
excinfo = result = None
|
||||
capture = self._getcapture()
|
||||
try:
|
||||
try:
|
||||
result = func()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
finally:
|
||||
stdout, stderr = capture.reset()
|
||||
return CallResult(result, excinfo, stdout, stderr)
|
||||
|
||||
class CallResult:
|
||||
def __init__(self, result, excinfo, stdout, stderr):
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.outerr = (self.stdout, self.stderr)
|
||||
self.excinfo = excinfo
|
||||
if excinfo is None:
|
||||
self.result = result
|
||||
|
||||
#
|
||||
# helpers
|
||||
#
|
||||
|
|
|
@ -10,5 +10,5 @@ Generator = py.test.collect.Generator
|
|||
Function = py.test.collect.Function
|
||||
Instance = py.test.collect.Instance
|
||||
|
||||
pytest_plugins = "default runner terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb unittest".split()
|
||||
pytest_plugins = "default iocapture runner terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb unittest".split()
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ XSpec = py.execnet.XSpec
|
|||
def run(item, node, excinfo=None):
|
||||
runner = item.config.pluginmanager.getplugin("runner")
|
||||
rep = runner.ItemTestReport(item=item,
|
||||
excinfo=excinfo, when="call", outerr=("", ""))
|
||||
excinfo=excinfo, when="call")
|
||||
rep.node = node
|
||||
return rep
|
||||
|
||||
|
|
|
@ -60,9 +60,6 @@ def pytest_addoption(parser):
|
|||
action="store", dest="tbstyle", default='long',
|
||||
type="choice", choices=['long', 'short', 'no'],
|
||||
help="traceback verboseness (long/short/no).")
|
||||
group._addoption('-s',
|
||||
action="store_true", dest="nocapture", default=False,
|
||||
help="disable catching of stdout/stderr during test run.")
|
||||
group._addoption('-p', action="append", dest="plugin", default = [],
|
||||
help=("load the specified plugin after command line parsing. "))
|
||||
group._addoption('-f', '--looponfail',
|
||||
|
|
|
@ -45,7 +45,7 @@ class DoctestItem(py.test.collect.Item):
|
|||
super(DoctestItem, self).__init__(name=name, parent=parent)
|
||||
self.fspath = path
|
||||
|
||||
def repr_failure(self, excinfo, outerr):
|
||||
def repr_failure(self, excinfo):
|
||||
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
|
||||
doctestfailure = excinfo.value
|
||||
example = doctestfailure.example
|
||||
|
@ -67,9 +67,9 @@ class DoctestItem(py.test.collect.Item):
|
|||
return ReprFailDoctest(reprlocation, lines)
|
||||
elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
|
||||
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
||||
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
else:
|
||||
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
|
||||
class DoctestTextfile(DoctestItem):
|
||||
def runtest(self):
|
||||
|
|
|
@ -28,6 +28,64 @@ will be restored.
|
|||
|
||||
import py
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group._addoption('-s',
|
||||
action="store_true", dest="nocapture", default=False,
|
||||
help="disable catching of stdout/stderr during test run.")
|
||||
|
||||
def pytest_configure(config):
|
||||
if not config.option.nocapture:
|
||||
config.pluginmanager.register(CapturePerTest())
|
||||
|
||||
def determine_capturing(config, path=None):
|
||||
iocapture = config.getvalue("iocapture", path=path)
|
||||
if iocapture == "fd":
|
||||
return py.io.StdCaptureFD()
|
||||
elif iocapture == "sys":
|
||||
return py.io.StdCapture()
|
||||
elif iocapture == "no":
|
||||
return py.io.StdCapture(out=False, err=False, in_=False)
|
||||
else:
|
||||
# how to raise errors here?
|
||||
raise config.Error("unknown io capturing: " + iocapture)
|
||||
|
||||
class CapturePerTest:
|
||||
def __init__(self):
|
||||
self.item2capture = {}
|
||||
|
||||
def _setcapture(self, item):
|
||||
assert item not in self.item2capture
|
||||
cap = determine_capturing(item.config, path=item.fspath)
|
||||
self.item2capture[item] = cap
|
||||
|
||||
def pytest_runtest_setup(self, item):
|
||||
self._setcapture(item)
|
||||
|
||||
def pytest_runtest_call(self, item):
|
||||
self._setcapture(item)
|
||||
|
||||
def pytest_runtest_teardown(self, item):
|
||||
self._setcapture(item)
|
||||
|
||||
def pytest_keyboard_interrupt(self, excinfo):
|
||||
for cap in self.item2capture.values():
|
||||
cap.reset()
|
||||
self.item2capture.clear()
|
||||
|
||||
def pytest_runtest_makereport(self, __call__, item, call):
|
||||
capture = self.item2capture.pop(item)
|
||||
outerr = capture.reset()
|
||||
# XXX shift reporting elsewhere
|
||||
rep = __call__.execute(firstresult=True)
|
||||
if hasattr(rep, 'longrepr'):
|
||||
repr = rep.longrepr
|
||||
if hasattr(repr, 'addsection'):
|
||||
for secname, content in zip(["out", "err"], outerr):
|
||||
if content:
|
||||
repr.addsection("Captured std%s" % secname, content.rstrip())
|
||||
return rep
|
||||
|
||||
def pytest_funcarg__capsys(request):
|
||||
"""captures writes to sys.stdout/sys.stderr and makes
|
||||
them available successively via a ``capsys.reset()`` method
|
||||
|
@ -52,7 +110,7 @@ def pytest_pyfunc_call(pyfuncitem):
|
|||
if funcarg == "capsys" or funcarg == "capfd":
|
||||
value.reset()
|
||||
|
||||
class Capture:
|
||||
class Capture: # funcarg
|
||||
_capture = None
|
||||
def __init__(self, captureclass):
|
||||
self._captureclass = captureclass
|
||||
|
|
|
@ -31,15 +31,15 @@ def pytest_sessionfinish(session, exitstatus):
|
|||
mod.raiseExceptions = False
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
call = collector.config.guardedcall(
|
||||
lambda: collector._memocollect()
|
||||
)
|
||||
result = None
|
||||
if not call.excinfo:
|
||||
result = call.result
|
||||
return CollectReport(collector, result, call.excinfo, call.outerr)
|
||||
|
||||
return report
|
||||
# XXX capturing is missing
|
||||
result = excinfo = None
|
||||
try:
|
||||
result = collector._memocollect()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
return CollectReport(collector, result, excinfo)
|
||||
|
||||
def pytest_runtest_protocol(item):
|
||||
if item.config.getvalue("boxed"):
|
||||
|
@ -66,7 +66,7 @@ def pytest_runtest_call(item):
|
|||
item.runtest()
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
return ItemTestReport(item, call.excinfo, call.when, call.outerr)
|
||||
return ItemTestReport(item, call.excinfo, call.when)
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
item.config._setupstate.teardown_exact(item)
|
||||
|
@ -82,7 +82,6 @@ def call_and_report(item, when, log=True):
|
|||
hook.pytest_runtest_logreport(rep=report)
|
||||
return report
|
||||
|
||||
|
||||
class RuntestHookCall:
|
||||
excinfo = None
|
||||
_prefix = "pytest_runtest_"
|
||||
|
@ -90,16 +89,12 @@ class RuntestHookCall:
|
|||
self.when = when
|
||||
hookname = self._prefix + when
|
||||
hook = getattr(item.config.hook, hookname)
|
||||
capture = item.config._getcapture()
|
||||
try:
|
||||
try:
|
||||
self.result = hook(item=item)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.excinfo = py.code.ExceptionInfo()
|
||||
finally:
|
||||
self.outerr = capture.reset()
|
||||
|
||||
def forked_run_report(item):
|
||||
# for now, we run setup/teardown in the subprocess
|
||||
|
@ -149,10 +144,9 @@ class BaseReport(object):
|
|||
class ItemTestReport(BaseReport):
|
||||
failed = passed = skipped = False
|
||||
|
||||
def __init__(self, item, excinfo=None, when=None, outerr=None):
|
||||
def __init__(self, item, excinfo=None, when=None):
|
||||
self.item = item
|
||||
self.when = when
|
||||
self.outerr = outerr
|
||||
if item and when != "setup":
|
||||
self.keywords = item.readkeywords()
|
||||
else:
|
||||
|
@ -173,14 +167,14 @@ class ItemTestReport(BaseReport):
|
|||
elif excinfo.errisinstance(Skipped):
|
||||
self.skipped = True
|
||||
shortrepr = "s"
|
||||
longrepr = self.item._repr_failure_py(excinfo, outerr)
|
||||
longrepr = self.item._repr_failure_py(excinfo)
|
||||
else:
|
||||
self.failed = True
|
||||
shortrepr = self.item.shortfailurerepr
|
||||
if self.when == "call":
|
||||
longrepr = self.item.repr_failure(excinfo, outerr)
|
||||
longrepr = self.item.repr_failure(excinfo)
|
||||
else: # exception in setup or teardown
|
||||
longrepr = self.item._repr_failure_py(excinfo, outerr)
|
||||
longrepr = self.item._repr_failure_py(excinfo)
|
||||
shortrepr = shortrepr.lower()
|
||||
self.shortrepr = shortrepr
|
||||
self.longrepr = longrepr
|
||||
|
@ -191,14 +185,13 @@ class ItemTestReport(BaseReport):
|
|||
class CollectReport(BaseReport):
|
||||
skipped = failed = passed = False
|
||||
|
||||
def __init__(self, collector, result, excinfo=None, outerr=None):
|
||||
def __init__(self, collector, result, excinfo=None):
|
||||
self.collector = collector
|
||||
if not excinfo:
|
||||
self.passed = True
|
||||
self.result = result
|
||||
else:
|
||||
self.outerr = outerr
|
||||
self.longrepr = self.collector._repr_failure_py(excinfo, outerr)
|
||||
self.longrepr = self.collector._repr_failure_py(excinfo)
|
||||
if excinfo.errisinstance(Skipped):
|
||||
self.skipped = True
|
||||
self.reason = str(excinfo.value)
|
||||
|
|
|
@ -126,7 +126,7 @@ class BaseFunctionalTests:
|
|||
testdir.makepyfile(conftest="""
|
||||
import py
|
||||
class Function(py.test.collect.Function):
|
||||
def repr_failure(self, excinfo, outerr):
|
||||
def repr_failure(self, excinfo):
|
||||
return "hello"
|
||||
""")
|
||||
reports = testdir.runitem("""
|
||||
|
@ -143,7 +143,7 @@ class BaseFunctionalTests:
|
|||
#assert rep.failed.where.path.basename == "test_func.py"
|
||||
#assert rep.failed.failurerepr == "hello"
|
||||
|
||||
def test_failure_in_setup_function_ignores_custom_failure_repr(self, testdir):
|
||||
def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
|
||||
testdir.makepyfile(conftest="""
|
||||
import py
|
||||
class Function(py.test.collect.Function):
|
||||
|
@ -168,21 +168,6 @@ class BaseFunctionalTests:
|
|||
#assert rep.outcome.where.path.basename == "test_func.py"
|
||||
#assert instanace(rep.failed.failurerepr, PythonFailureRepr)
|
||||
|
||||
def test_capture_in_func(self, testdir):
|
||||
reports = testdir.runitem("""
|
||||
import sys
|
||||
def setup_function(func):
|
||||
print "in setup"
|
||||
def test_func():
|
||||
print "in function"
|
||||
assert 0
|
||||
def teardown_function(func):
|
||||
print "in teardown"
|
||||
""")
|
||||
assert reports[0].outerr[0] == "in setup\n"
|
||||
assert reports[1].outerr[0] == "in function\n"
|
||||
assert reports[2].outerr[0] == "in teardown\n"
|
||||
|
||||
def test_systemexit_does_not_bail_out(self, testdir):
|
||||
try:
|
||||
reports = testdir.runitem("""
|
||||
|
@ -208,6 +193,23 @@ class BaseFunctionalTests:
|
|||
else:
|
||||
py.test.fail("did not raise")
|
||||
|
||||
@py.test.mark.xfail
|
||||
def test_capture_per_func(self, testdir):
|
||||
reports = testdir.runitem("""
|
||||
import sys
|
||||
def setup_function(func):
|
||||
print "in setup"
|
||||
def test_func():
|
||||
print "in function"
|
||||
assert 0
|
||||
def teardown_function(func):
|
||||
print "in teardown"
|
||||
""")
|
||||
assert reports[0].outerr[0] == "in setup\n"
|
||||
assert reports[1].outerr[0] == "in function\n"
|
||||
assert reports[2].outerr[0] == "in teardown\n"
|
||||
|
||||
|
||||
|
||||
class TestExecutionNonForked(BaseFunctionalTests):
|
||||
def getrunner(self):
|
||||
|
@ -287,16 +289,3 @@ def test_functional_boxed(testdir):
|
|||
"*1 failed*"
|
||||
])
|
||||
|
||||
def test_logging_interaction(testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def test_logging():
|
||||
import logging
|
||||
import StringIO
|
||||
stream = StringIO.StringIO()
|
||||
logging.basicConfig(stream=stream)
|
||||
stream.close() # to free memory/release resources
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.stderr.str().find("atexit") == -1
|
||||
|
||||
|
||||
|
|
|
@ -271,8 +271,9 @@ class FunctionMixin(PyobjMixin):
|
|||
traceback = ntraceback.filter()
|
||||
return traceback
|
||||
|
||||
def repr_failure(self, excinfo, outerr):
|
||||
return self._repr_failure_py(excinfo, outerr)
|
||||
def repr_failure(self, excinfo, outerr=None):
|
||||
assert outerr is None, "XXX outerr usage is deprecated"
|
||||
return self._repr_failure_py(excinfo)
|
||||
|
||||
shortfailurerepr = "F"
|
||||
|
||||
|
|
|
@ -227,28 +227,6 @@ class TestGeneralUsage:
|
|||
"*test_traceback_failure.py:4: AssertionError"
|
||||
])
|
||||
|
||||
def test_capturing_outerr(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
import sys
|
||||
def test_capturing():
|
||||
print 42
|
||||
print >>sys.stderr, 23
|
||||
def test_capturing_error():
|
||||
print 1
|
||||
print >>sys.stderr, 2
|
||||
raise ValueError
|
||||
""")
|
||||
result = testdir.runpytest(p1)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_capturing_outerr.py .F",
|
||||
"====* FAILURES *====",
|
||||
"____*____",
|
||||
"*test_capturing_outerr.py:8: ValueError",
|
||||
"*--- Captured stdout ---*",
|
||||
"1",
|
||||
"*--- Captured stderr ---*",
|
||||
"2",
|
||||
])
|
||||
|
||||
def test_showlocals(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
|
|
|
@ -212,38 +212,6 @@ class TestConfigApi_getcolitems:
|
|||
for col in col.listchain():
|
||||
assert col.config is config
|
||||
|
||||
|
||||
class TestGuardedCall:
|
||||
def test_guardedcall_ok(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
def myfunc(x):
|
||||
print x
|
||||
print >>py.std.sys.stderr, "hello"
|
||||
return 7
|
||||
call = config.guardedcall(lambda: myfunc(3))
|
||||
assert call.excinfo is None
|
||||
assert call.result == 7
|
||||
assert call.stdout.startswith("3")
|
||||
assert call.stderr.startswith("hello")
|
||||
|
||||
def test_guardedcall_fail(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
def myfunc(x):
|
||||
print x
|
||||
raise ValueError(17)
|
||||
call = config.guardedcall(lambda: myfunc(3))
|
||||
assert call.excinfo
|
||||
assert call.excinfo.type == ValueError
|
||||
assert not hasattr(call, 'result')
|
||||
assert call.stdout.startswith("3")
|
||||
assert not call.stderr
|
||||
|
||||
def test_guardedcall_keyboardinterrupt(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
def myfunc():
|
||||
raise KeyboardInterrupt
|
||||
py.test.raises(KeyboardInterrupt, config.guardedcall, myfunc)
|
||||
|
||||
class TestOptionEffects:
|
||||
def test_boxed_option_default(self, testdir):
|
||||
tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
|
||||
|
@ -258,29 +226,6 @@ class TestOptionEffects:
|
|||
config = py.test.config._reparse([testdir.tmpdir])
|
||||
assert not config.option.boxed
|
||||
|
||||
def test_config_iocapturing(self, testdir):
|
||||
config = testdir.parseconfig(testdir.tmpdir)
|
||||
assert config.getvalue("iocapture")
|
||||
tmpdir = testdir.tmpdir.ensure("sub-with-conftest", dir=1)
|
||||
tmpdir.join("conftest.py").write(py.code.Source("""
|
||||
pytest_option_iocapture = "no"
|
||||
"""))
|
||||
config = py.test.config._reparse([tmpdir])
|
||||
assert config.getvalue("iocapture") == "no"
|
||||
capture = config._getcapture()
|
||||
assert isinstance(capture, py.io.StdCapture)
|
||||
assert not capture._out
|
||||
assert not capture._err
|
||||
assert not capture._in
|
||||
assert isinstance(capture, py.io.StdCapture)
|
||||
for opt, cls in (("sys", py.io.StdCapture),
|
||||
("fd", py.io.StdCaptureFD),
|
||||
):
|
||||
config.option.iocapture = opt
|
||||
capture = config._getcapture()
|
||||
assert isinstance(capture, cls)
|
||||
|
||||
|
||||
class TestConfig_gettopdir:
|
||||
def test_gettopdir(self, testdir):
|
||||
from py.__.test.config import gettopdir
|
||||
|
|
Loading…
Reference in New Issue