[Pytest-commit] commit/pytest: 22 new changesets
commits-noreply at bitbucket.org
commits-noreply at bitbucket.org
Wed Apr 2 09:17:50 CEST 2014
22 new commits in pytest:
https://bitbucket.org/hpk42/pytest/commits/595633b5b497/
Changeset: 595633b5b497
User: hpk42
Date: 2014-03-14 12:49:34
Summary: remove unneccessary indirections and options
Affected #: 1 file
diff -r 52e080dd60eda8dcf4732baebebebb79160d14ff -r 595633b5b497a24d4c06896290ff4a0783ada19a _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -120,9 +120,6 @@
f.close()
return newf
- def _makestringio(self):
- return TextIO()
-
def _getcapture(self, method):
if method == "fd":
return StdCaptureFD(
@@ -130,10 +127,7 @@
err=self._maketempfile(),
)
elif method == "sys":
- return StdCapture(
- out=self._makestringio(),
- err=self._makestringio(),
- )
+ return StdCapture(out=TextIO(), err=TextIO())
elif method == "no":
return NoCapture()
else:
https://bitbucket.org/hpk42/pytest/commits/eef0bd2fa258/
Changeset: eef0bd2fa258
User: hpk42
Date: 2014-03-14 12:49:34
Summary: remove externally setting and dealing with "item.outerr" from capturing in favor of a direct interface for adding reporting sections to items.
* * *
refactor makereport implementation to avoid recursion with __multicall__
Affected #: 7 files
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -151,8 +151,6 @@
def resumecapture_item(self, item):
method = self._getmethod(item.config, item.fspath)
- if not hasattr(item, 'outerr'):
- item.outerr = ('', '') # we accumulate outerr on the item
return self.resumecapture(method)
def resumecapture(self, method=None):
@@ -174,16 +172,10 @@
self.deactivate_funcargs()
if hasattr(self, '_capturing'):
method = self._capturing
+ del self._capturing
cap = self._method2capture.get(method)
if cap is not None:
- outerr = cap.suspend()
- del self._capturing
- if item:
- outerr = (item.outerr[0] + outerr[0],
- item.outerr[1] + outerr[1])
- return outerr
- if hasattr(item, 'outerr'):
- return item.outerr
+ return cap.suspend()
return "", ""
def activate_funcargs(self, pyfuncitem):
@@ -235,18 +227,14 @@
self.suspendcapture()
@pytest.mark.tryfirst
- def pytest_runtest_makereport(self, __multicall__, item, call):
+ def pytest_runtest_makereport(self, item, call):
funcarg_outerr = self.deactivate_funcargs()
- rep = __multicall__.execute()
- outerr = self.suspendcapture(item)
+ out, err = self.suspendcapture(item)
if funcarg_outerr is not None:
- outerr = (outerr[0] + funcarg_outerr[0],
- outerr[1] + funcarg_outerr[1])
- addouterr(rep, outerr)
- if not rep.passed or rep.when == "teardown":
- outerr = ('', '')
- item.outerr = outerr
- return rep
+ out += funcarg_outerr[0]
+ err += funcarg_outerr[1]
+ item.add_report_section(call.when, "out", out)
+ item.add_report_section(call.when, "err", err)
error_capsysfderror = "cannot use capsys and capfd at the same time"
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c _pytest/junitxml.py
--- a/_pytest/junitxml.py
+++ b/_pytest/junitxml.py
@@ -108,12 +108,14 @@
))
def _write_captured_output(self, report):
- sec = dict(report.sections)
- for name in ('out', 'err'):
- content = sec.get("Captured std%s" % name)
- if content:
- tag = getattr(Junit, 'system-'+name)
- self.append(tag(bin_xml_escape(content)))
+ for capname in ('out', 'err'):
+ allcontent = ""
+ for name, content in report.get_sections("Captured std%s" %
+ capname):
+ allcontent += content
+ if allcontent:
+ tag = getattr(Junit, 'system-'+capname)
+ self.append(tag(bin_xml_escape(allcontent)))
def append(self, obj):
self.tests[-1].append(obj)
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -233,6 +233,7 @@
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
+
#self.extrainit()
@property
@@ -469,6 +470,14 @@
"""
nextitem = None
+ def __init__(self, name, parent=None, config=None, session=None):
+ super(Item, self).__init__(name, parent, config, session)
+ self._report_sections = []
+
+ def add_report_section(self, when, key, content):
+ if content:
+ self._report_sections.append((when, key, content))
+
def reportinfo(self):
return self.fspath, None, ""
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c _pytest/pdb.py
--- a/_pytest/pdb.py
+++ b/_pytest/pdb.py
@@ -35,8 +35,8 @@
if item is not None:
capman = item.config.pluginmanager.getplugin("capturemanager")
out, err = capman.suspendcapture()
- if hasattr(item, 'outerr'):
- item.outerr = (item.outerr[0] + out, item.outerr[1] + err)
+ #if hasattr(item, 'outerr'):
+ # item.outerr = (item.outerr[0] + out, item.outerr[1] + err)
tw = py.io.TerminalWriter()
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -178,6 +178,11 @@
except UnicodeEncodeError:
out.line("<unprintable longrepr>")
+ def get_sections(self, prefix):
+ for name, content in self.sections:
+ if name.startswith(prefix):
+ yield prefix, content
+
passed = property(lambda x: x.outcome == "passed")
failed = property(lambda x: x.outcome == "failed")
skipped = property(lambda x: x.outcome == "skipped")
@@ -191,6 +196,7 @@
duration = call.stop-call.start
keywords = dict([(x,1) for x in item.keywords])
excinfo = call.excinfo
+ sections = []
if not call.excinfo:
outcome = "passed"
longrepr = None
@@ -209,16 +215,18 @@
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
+ for rwhen, key, content in item._report_sections:
+ sections.append(("Captured std%s %s" %(key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
- duration=duration)
+ sections, duration)
class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
- def __init__(self, nodeid, location,
- keywords, outcome, longrepr, when, sections=(), duration=0, **extra):
+ def __init__(self, nodeid, location, keywords, outcome,
+ longrepr, when, sections=(), duration=0, **extra):
#: normalized collection node id
self.nodeid = nodeid
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -282,9 +282,9 @@
"====* FAILURES *====",
"____*____",
"*test_capturing_outerr.py:8: ValueError",
- "*--- Captured stdout ---*",
+ "*--- Captured stdout *call*",
"1",
- "*--- Captured stderr ---*",
+ "*--- Captured stderr *call*",
"2",
])
diff -r 595633b5b497a24d4c06896290ff4a0783ada19a -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c testing/test_junitxml.py
--- a/testing/test_junitxml.py
+++ b/testing/test_junitxml.py
@@ -478,10 +478,12 @@
path = testdir.tmpdir.join("test.xml")
log = LogXML(str(path), None)
ustr = py.builtin._totext("ВНИ!", "utf-8")
- class report:
+ from _pytest.runner import BaseReport
+ class Report(BaseReport):
longrepr = ustr
sections = []
nodeid = "something"
+ report = Report()
# hopefully this is not too brittle ...
log.pytest_sessionstart()
https://bitbucket.org/hpk42/pytest/commits/d3abe2e5e194/
Changeset: d3abe2e5e194
User: hpk42
Date: 2014-03-14 12:49:35
Summary: implement a new hook type: hook wrappers using a "yield" to distinguish
between working at the front and at the end of a hook call chain.
The idea is to make it easier for a plugin to "wrap" a certain hook
call and use context managers, in particular allow a major cleanup of
capturing.
Affected #: 2 files
diff -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -240,18 +240,22 @@
pass
l = []
last = []
+ wrappers = []
for plugin in plugins:
try:
meth = getattr(plugin, attrname)
- if hasattr(meth, 'tryfirst'):
- last.append(meth)
- elif hasattr(meth, 'trylast'):
- l.insert(0, meth)
- else:
- l.append(meth)
except AttributeError:
continue
+ if hasattr(meth, 'hookwrapper'):
+ wrappers.append(meth)
+ elif hasattr(meth, 'tryfirst'):
+ last.append(meth)
+ elif hasattr(meth, 'trylast'):
+ l.insert(0, meth)
+ else:
+ l.append(meth)
l.extend(last)
+ l.extend(wrappers)
self._listattrcache[key] = list(l)
return l
@@ -272,6 +276,14 @@
class MultiCall:
""" execute a call into multiple python functions/methods. """
+
+ class WrongHookWrapper(Exception):
+ """ a hook wrapper does not behave correctly. """
+ def __init__(self, func, message):
+ Exception.__init__(self, func, message)
+ self.func = func
+ self.message = message
+
def __init__(self, methods, kwargs, firstresult=False):
self.methods = list(methods)
self.kwargs = kwargs
@@ -283,16 +295,39 @@
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
def execute(self):
- while self.methods:
- method = self.methods.pop()
- kwargs = self.getkwargs(method)
- res = method(**kwargs)
- if res is not None:
- self.results.append(res)
- if self.firstresult:
- return res
- if not self.firstresult:
- return self.results
+ next_finalizers = []
+ try:
+ while self.methods:
+ method = self.methods.pop()
+ kwargs = self.getkwargs(method)
+ if hasattr(method, "hookwrapper"):
+ it = method(**kwargs)
+ next = getattr(it, "next", None)
+ if next is None:
+ next = getattr(it, "__next__", None)
+ if next is None:
+ raise self.WrongHookWrapper(method,
+ "wrapper does not contain a yield")
+ res = next()
+ next_finalizers.append((method, next))
+ else:
+ res = method(**kwargs)
+ if res is not None:
+ self.results.append(res)
+ if self.firstresult:
+ return res
+ if not self.firstresult:
+ return self.results
+ finally:
+ for method, fin in reversed(next_finalizers):
+ try:
+ fin()
+ except StopIteration:
+ pass
+ else:
+ raise self.WrongHookWrapper(method,
+ "wrapper contain more than one yield")
+
def getkwargs(self, method):
kwargs = {}
diff -r eef0bd2fa2583a8ede9efcf8dbd2e9f96d11f77c -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -523,6 +523,95 @@
res = MultiCall([m1, m2], {}).execute()
assert res == [1]
+ def test_hookwrapper(self):
+ l = []
+ def m1():
+ l.append("m1 init")
+ yield None
+ l.append("m1 finish")
+ m1.hookwrapper = True
+
+ def m2():
+ l.append("m2")
+ return 2
+ res = MultiCall([m2, m1], {}).execute()
+ assert res == [2]
+ assert l == ["m1 init", "m2", "m1 finish"]
+ l[:] = []
+ res = MultiCall([m2, m1], {}, firstresult=True).execute()
+ assert res == 2
+ assert l == ["m1 init", "m2", "m1 finish"]
+
+ def test_hookwrapper_order(self):
+ l = []
+ def m1():
+ l.append("m1 init")
+ yield 1
+ l.append("m1 finish")
+ m1.hookwrapper = True
+
+ def m2():
+ l.append("m2 init")
+ yield 2
+ l.append("m2 finish")
+ m2.hookwrapper = True
+ res = MultiCall([m2, m1], {}).execute()
+ assert res == [1, 2]
+ assert l == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
+
+ def test_listattr_hookwrapper_ordering(self):
+ class P1:
+ @pytest.mark.hookwrapper
+ def m(self):
+ return 17
+
+ class P2:
+ def m(self):
+ return 23
+
+ class P3:
+ @pytest.mark.tryfirst
+ def m(self):
+ return 19
+
+ pluginmanager = PluginManager()
+ p1 = P1()
+ p2 = P2()
+ p3 = P3()
+ pluginmanager.register(p1)
+ pluginmanager.register(p2)
+ pluginmanager.register(p3)
+ methods = pluginmanager.listattr('m')
+ assert methods == [p2.m, p3.m, p1.m]
+ ## listattr keeps a cache and deleting
+ ## a function attribute requires clearing it
+ #pluginmanager._listattrcache.clear()
+ #del P1.m.__dict__['tryfirst']
+
+ def test_hookwrapper_not_yield(self):
+ def m1():
+ pass
+ m1.hookwrapper = True
+
+ mc = MultiCall([m1], {})
+ with pytest.raises(mc.WrongHookWrapper) as ex:
+ mc.execute()
+ assert ex.value.func == m1
+ assert ex.value.message
+
+ def test_hookwrapper_too_many_yield(self):
+ def m1():
+ yield 1
+ yield 2
+ m1.hookwrapper = True
+
+ mc = MultiCall([m1], {})
+ with pytest.raises(mc.WrongHookWrapper) as ex:
+ mc.execute()
+ assert ex.value.func == m1
+ assert ex.value.message
+
+
class TestHookRelay:
def test_happypath(self):
pm = PluginManager()
https://bitbucket.org/hpk42/pytest/commits/cfac82eca2e8/
Changeset: cfac82eca2e8
User: hpk42
Date: 2014-03-14 12:49:36
Summary: - turn on capturing before early conftest loading and make terminal writer
use the original stream.
- avoid resetting capture FDs/sys.stdout for each test by keeping capturing
always turned on and looking at snapshotted capturing data during runtest
and collection phases.
Affected #: 11 files
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -1,12 +1,11 @@
"""
- per-test stdout/stderr capturing mechanisms,
- ``capsys`` and ``capfd`` function arguments.
+per-test stdout/stderr capturing mechanism.
+
"""
-# note: py.io capture was where copied from
-# pylib 1.4.20.dev2 (rev 13d9af95547e)
import sys
import os
import tempfile
+import contextlib
import py
import pytest
@@ -58,8 +57,18 @@
method = "fd"
if method == "fd" and not hasattr(os, "dup"):
method = "sys"
+ pluginmanager = early_config.pluginmanager
+ if method != "no":
+ try:
+ sys.stdout.fileno()
+ except Exception:
+ dupped_stdout = sys.stdout
+ else:
+ dupped_stdout = dupfile(sys.stdout, buffering=1)
+ pluginmanager.register(dupped_stdout, "dupped_stdout")
+ #pluginmanager.add_shutdown(dupped_stdout.close)
capman = CaptureManager(method)
- early_config.pluginmanager.register(capman, "capturemanager")
+ pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
def teardown():
@@ -68,13 +77,13 @@
except ValueError:
pass
- early_config.pluginmanager.add_shutdown(teardown)
+ pluginmanager.add_shutdown(teardown)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
if "logging" in sys.modules:
sys.modules["logging"].raiseExceptions = False
- early_config.pluginmanager.add_shutdown(silence_logging_at_shutdown)
+ pluginmanager.add_shutdown(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.resumecapture()
@@ -89,23 +98,20 @@
raise
-def addouterr(rep, outerr):
- for secname, content in zip(["out", "err"], outerr):
- if content:
- rep.sections.append(("Captured std%s" % secname, content))
-
-
class NoCapture:
- def startall(self):
+ def start_capturing(self):
pass
- def resume(self):
+ def stop_capturing(self):
+ pass
+
+ def pop_outerr_to_orig(self):
pass
def reset(self):
pass
- def suspend(self):
+ def readouterr(self):
return "", ""
@@ -147,7 +153,9 @@
def reset_capturings(self):
for cap in self._method2capture.values():
+ cap.pop_outerr_to_orig()
cap.reset()
+ self._method2capture.clear()
def resumecapture_item(self, item):
method = self._getmethod(item.config, item.fspath)
@@ -164,9 +172,9 @@
self._capturing = method
if cap is None:
self._method2capture[method] = cap = self._getcapture(method)
- cap.startall()
+ cap.start_capturing()
else:
- cap.resume()
+ cap.pop_outerr_to_orig()
def suspendcapture(self, item=None):
self.deactivate_funcargs()
@@ -175,7 +183,7 @@
del self._capturing
cap = self._method2capture.get(method)
if cap is not None:
- return cap.suspend()
+ return cap.readouterr()
return "", ""
def activate_funcargs(self, pyfuncitem):
@@ -194,47 +202,68 @@
del self._capturing_funcarg
return outerr
+ @pytest.mark.hookwrapper
def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath)
try:
self.resumecapture(method)
except ValueError:
+ yield
# recursive collect, XXX refactor capturing
# to allow for more lightweight recursive capturing
return
- try:
- rep = __multicall__.execute()
- finally:
- outerr = self.suspendcapture()
- addouterr(rep, outerr)
- return rep
+ yield
+ out, err = self.suspendcapture()
+ # XXX getting the report from the ongoing hook call is a bit
+ # of a hack. We need to think about capturing during collection
+ # and find out if it's really needed fine-grained (per
+ # collector).
+ if __multicall__.results:
+ rep = __multicall__.results[0]
+ if out:
+ rep.sections.append(("Captured stdout", out))
+ if err:
+ rep.sections.append(("Captured stderr", err))
+
+ @pytest.mark.hookwrapper
+ def pytest_runtest_setup(self, item):
+ with self.item_capture_wrapper(item, "setup"):
+ yield
+
+ @pytest.mark.hookwrapper
+ def pytest_runtest_call(self, item):
+ with self.item_capture_wrapper(item, "call"):
+ yield
+
+ @pytest.mark.hookwrapper
+ def pytest_runtest_teardown(self, item):
+ with self.item_capture_wrapper(item, "teardown"):
+ yield
@pytest.mark.tryfirst
- def pytest_runtest_setup(self, item):
- self.resumecapture_item(item)
+ def pytest_keyboard_interrupt(self, excinfo):
+ self.reset_capturings()
@pytest.mark.tryfirst
- def pytest_runtest_call(self, item):
+ def pytest_internalerror(self, excinfo):
+ self.reset_capturings()
+
+ @contextlib.contextmanager
+ def item_capture_wrapper(self, item, when):
self.resumecapture_item(item)
- self.activate_funcargs(item)
-
- @pytest.mark.tryfirst
- def pytest_runtest_teardown(self, item):
- self.resumecapture_item(item)
-
- def pytest_keyboard_interrupt(self, excinfo):
- if hasattr(self, '_capturing'):
- self.suspendcapture()
-
- @pytest.mark.tryfirst
- def pytest_runtest_makereport(self, item, call):
- funcarg_outerr = self.deactivate_funcargs()
+ if when == "call":
+ self.activate_funcargs(item)
+ yield
+ funcarg_outerr = self.deactivate_funcargs()
+ else:
+ yield
+ funcarg_outerr = None
out, err = self.suspendcapture(item)
if funcarg_outerr is not None:
out += funcarg_outerr[0]
err += funcarg_outerr[1]
- item.add_report_section(call.when, "out", out)
- item.add_report_section(call.when, "err", err)
+ item.add_report_section(when, "out", out)
+ item.add_report_section(when, "err", err)
error_capsysfderror = "cannot use capsys and capfd at the same time"
@@ -263,10 +292,10 @@
class CaptureFixture:
def __init__(self, captureclass):
- self._capture = captureclass()
+ self._capture = captureclass(in_=False)
def _start(self):
- self._capture.startall()
+ self._capture.start_capturing()
def _finalize(self):
if hasattr(self, '_capture'):
@@ -295,6 +324,8 @@
"""
self.targetfd = targetfd
if tmpfile is None and targetfd != 0:
+ # this code path is covered in the tests
+ # but not used by a regular pytest run
f = tempfile.TemporaryFile('wb+')
tmpfile = dupfile(f, encoding="UTF-8")
f.close()
@@ -390,13 +421,13 @@
return getattr(self._stream, name)
-class Capture(object):
+class StdCaptureBase(object):
def reset(self):
""" reset sys.stdout/stderr and return captured output as strings. """
if hasattr(self, '_reset'):
raise ValueError("was already reset")
self._reset = True
- outfile, errfile = self.done(save=False)
+ outfile, errfile = self.stop_capturing(save=False)
out, err = "", ""
if outfile and not outfile.closed:
out = outfile.read()
@@ -406,14 +437,16 @@
errfile.close()
return out, err
- def suspend(self):
- """ return current snapshot captures, memorize tempfiles. """
- outerr = self.readouterr()
- outfile, errfile = self.done()
- return outerr
+ def pop_outerr_to_orig(self):
+ """ pop current snapshot out/err capture and flush to orig streams. """
+ out, err = self.readouterr()
+ if out:
+ self.out.writeorg(out)
+ if err:
+ self.err.writeorg(err)
-class StdCaptureFD(Capture):
+class StdCaptureFD(StdCaptureBase):
""" This class allows to capture writes to FD1 and FD2
and may connect a NULL file to FD0 (and prevent
reads from sys.stdin). If any of the 0,1,2 file descriptors
@@ -464,7 +497,7 @@
except OSError:
pass
- def startall(self):
+ def start_capturing(self):
if hasattr(self, 'in_'):
self.in_.start()
if hasattr(self, 'out'):
@@ -472,11 +505,10 @@
if hasattr(self, 'err'):
self.err.start()
- def resume(self):
- """ resume capturing with original temp files. """
- self.startall()
+ #def pytest_sessionfinish(self):
+ # self.reset_capturings()
- def done(self, save=True):
+ def stop_capturing(self, save=True):
""" return (outfile, errfile) and stop capturing. """
outfile = errfile = None
if hasattr(self, 'out') and not self.out.tmpfile.closed:
@@ -491,16 +523,15 @@
def readouterr(self):
""" return snapshot value of stdout/stderr capturings. """
- out = self._readsnapshot('out')
- err = self._readsnapshot('err')
- return out, err
+ return self._readsnapshot('out'), self._readsnapshot('err')
def _readsnapshot(self, name):
- if hasattr(self, name):
+ try:
f = getattr(self, name).tmpfile
- else:
+ except AttributeError:
return ''
-
+ if f.tell() == 0:
+ return ''
f.seek(0)
res = f.read()
enc = getattr(f, "encoding", None)
@@ -510,8 +541,17 @@
f.seek(0)
return res
+class TextCapture(TextIO):
+ def __init__(self, oldout):
+ super(TextCapture, self).__init__()
+ self._oldout = oldout
-class StdCapture(Capture):
+ def writeorg(self, data):
+ self._oldout.write(data)
+ self._oldout.flush()
+
+
+class StdCapture(StdCaptureBase):
""" This class allows to capture writes to sys.stdout|stderr "in-memory"
and will raise errors on tries to read from sys.stdin. It only
modifies sys.stdout|stderr|stdin attributes and does not
@@ -522,15 +562,15 @@
self._olderr = sys.stderr
self._oldin = sys.stdin
if out and not hasattr(out, 'file'):
- out = TextIO()
+ out = TextCapture(self._oldout)
self.out = out
if err:
if not hasattr(err, 'write'):
- err = TextIO()
+ err = TextCapture(self._olderr)
self.err = err
self.in_ = in_
- def startall(self):
+ def start_capturing(self):
if self.out:
sys.stdout = self.out
if self.err:
@@ -538,7 +578,7 @@
if self.in_:
sys.stdin = self.in_ = DontReadFromInput()
- def done(self, save=True):
+ def stop_capturing(self, save=True):
""" return (outfile, errfile) and stop capturing. """
outfile = errfile = None
if self.out and not self.out.closed:
@@ -553,9 +593,6 @@
sys.stdin = self._oldin
return outfile, errfile
- def resume(self):
- """ resume capturing with original temp files. """
- self.startall()
def readouterr(self):
""" return snapshot value of stdout/stderr capturings. """
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -56,11 +56,15 @@
raise ValueError("not a string or argument list: %r" % (args,))
args = py.std.shlex.split(args)
pluginmanager = get_plugin_manager()
- if plugins:
- for plugin in plugins:
- pluginmanager.register(plugin)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args)
+ try:
+ if plugins:
+ for plugin in plugins:
+ pluginmanager.register(plugin)
+ return pluginmanager.hook.pytest_cmdline_parse(
+ pluginmanager=pluginmanager, args=args)
+ except Exception:
+ pluginmanager.ensure_shutdown()
+ raise
class PytestPluginManager(PluginManager):
def __init__(self, hookspecs=[hookspec]):
@@ -612,6 +616,9 @@
self.hook.pytest_logwarning(code=code, message=message,
fslocation=None, nodeid=None)
+ def get_terminal_writer(self):
+ return self.pluginmanager.getplugin("terminalreporter")._tw
+
def pytest_cmdline_parse(self, pluginmanager, args):
assert self == pluginmanager.config, (self, pluginmanager.config)
self.parse(args)
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/genscript.py
--- a/_pytest/genscript.py
+++ b/_pytest/genscript.py
@@ -60,6 +60,7 @@
def pytest_cmdline_main(config):
genscript = config.getvalue("genscript")
if genscript:
+ #tw = config.get_terminal_writer()
tw = py.io.TerminalWriter()
deps = ['py', '_pytest', 'pytest']
if sys.version_info < (2,7):
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -47,6 +47,8 @@
def pytest_cmdline_main(config):
if config.option.version:
+ capman = config.pluginmanager.getplugin("capturemanager")
+ capman.reset_capturings()
p = py.path.local(pytest.__file__)
sys.stderr.write("This is pytest version %s, imported from %s\n" %
(pytest.__version__, p))
@@ -62,7 +64,7 @@
return 0
def showhelp(config):
- tw = py.io.TerminalWriter()
+ tw = config.get_terminal_writer()
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -40,7 +40,7 @@
def pytest_cmdline_main(config):
if config.option.markers:
config.do_configure()
- tw = py.io.TerminalWriter()
+ tw = config.get_terminal_writer()
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/pdb.py
--- a/_pytest/pdb.py
+++ b/_pytest/pdb.py
@@ -34,10 +34,9 @@
if item is not None:
capman = item.config.pluginmanager.getplugin("capturemanager")
- out, err = capman.suspendcapture()
- #if hasattr(item, 'outerr'):
- # item.outerr = (item.outerr[0] + out, item.outerr[1] + err)
- tw = py.io.TerminalWriter()
+ if capman:
+ capman.reset_capturings()
+ tw = item.config.get_terminal_writer()
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
py.std.pdb.Pdb().set_trace(frame)
@@ -46,19 +45,20 @@
pytestPDB.item = item
pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
- at pytest.mark.tryfirst
-def pytest_make_collect_report(__multicall__, collector):
- try:
- pytestPDB.collector = collector
- return __multicall__.execute()
- finally:
- pytestPDB.collector = None
+ at pytest.mark.hookwrapper
+def pytest_make_collect_report(collector):
+ pytestPDB.collector = collector
+ yield
+ pytestPDB.collector = None
def pytest_runtest_makereport():
pytestPDB.item = None
class PdbInvoke:
def pytest_exception_interact(self, node, call, report):
+ capman = node.config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.reset_capturings()
return _enter_pdb(node, call.excinfo, report)
def pytest_internalerror(self, excrepr, excinfo):
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -885,7 +885,7 @@
nodeid = "::".join(map(str, [curdir.bestrelpath(part[0])] + part[1:]))
nodeid.replace(session.fspath.sep, "/")
- tw = py.io.TerminalWriter()
+ tw = config.get_terminal_writer()
verbose = config.getvalue("verbose")
fm = session._fixturemanager
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -135,14 +135,13 @@
self.when = when
self.start = time()
try:
- try:
- self.result = func()
- except KeyboardInterrupt:
- raise
- except:
- self.excinfo = py.code.ExceptionInfo()
- finally:
+ self.result = func()
+ except KeyboardInterrupt:
self.stop = time()
+ raise
+ except:
+ self.excinfo = py.code.ExceptionInfo()
+ self.stop = time()
def __repr__(self):
if self.excinfo:
@@ -292,7 +291,8 @@
class CollectReport(BaseReport):
- def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
+ def __init__(self, nodeid, outcome, longrepr, result,
+ sections=(), **extra):
self.nodeid = nodeid
self.outcome = outcome
self.longrepr = longrepr
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -36,7 +36,10 @@
def pytest_configure(config):
config.option.verbose -= config.option.quiet
- reporter = TerminalReporter(config, sys.stdout)
+ out = config.pluginmanager.getplugin("dupped_stdout")
+ #if out is None:
+ # out = sys.stdout
+ reporter = TerminalReporter(config, out)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
def mywriter(tags, args):
@@ -44,6 +47,11 @@
reporter.write_line("[traceconfig] " + msg)
config.trace.root.setprocessor("pytest:config", mywriter)
+def get_terminal_writer(config):
+ tr = config.pluginmanager.getplugin("terminalreporter")
+ return tr._tw
+
+
def getreportopt(config):
reportopts = ""
optvalue = config.option.report
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 bench/bench.py
--- a/bench/bench.py
+++ b/bench/bench.py
@@ -4,8 +4,8 @@
import cProfile
import pytest
import pstats
- script = sys.argv[1] if len(sys.argv) > 1 else "empty.py"
- stats = cProfile.run('pytest.cmdline.main([%r])' % script, 'prof')
+ script = sys.argv[1:] if len(sys.argv) > 1 else "empty.py"
+ stats = cProfile.run('pytest.cmdline.main(%r)' % script, 'prof')
p = pstats.Stats("prof")
p.strip_dirs()
p.sort_stats('cumulative')
diff -r d3abe2e5e19434f0a08d23b20997e7f31f3f88d1 -r cfac82eca2e810f0c6bf74945981f57e66d1d316 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -736,14 +736,14 @@
class TestStdCapture:
def getcapture(self, **kw):
cap = capture.StdCapture(**kw)
- cap.startall()
+ cap.start_capturing()
return cap
def test_capturing_done_simple(self):
cap = self.getcapture()
sys.stdout.write("hello")
sys.stderr.write("world")
- outfile, errfile = cap.done()
+ outfile, errfile = cap.stop_capturing()
s = outfile.read()
assert s == "hello"
s = errfile.read()
@@ -787,6 +787,7 @@
print('\xa6')
out, err = cap.readouterr()
assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
+ cap.reset()
def test_reset_twice_error(self):
cap = self.getcapture()
@@ -859,12 +860,13 @@
try:
print ("hello")
sys.stderr.write("error\n")
- out, err = cap.suspend()
+ out, err = cap.readouterr()
+ cap.stop_capturing()
assert out == "hello\n"
assert not err
print ("in between")
sys.stderr.write("in between\n")
- cap.resume()
+ cap.start_capturing()
print ("after")
sys.stderr.write("error_after\n")
finally:
@@ -878,7 +880,7 @@
def getcapture(self, **kw):
cap = capture.StdCaptureFD(**kw)
- cap.startall()
+ cap.start_capturing()
return cap
def test_intermingling(self):
@@ -908,7 +910,7 @@
try:
os.write(1, "hello".encode("ascii"))
os.write(2, "world".encode("ascii"))
- outf, errf = capfd.done()
+ outf, errf = capfd.stop_capturing()
finally:
capfd.reset()
assert outf == tmpfile
@@ -924,15 +926,15 @@
def test_stdout():
os.close(1)
cap = StdCaptureFD(out=True, err=False, in_=False)
- cap.done()
+ cap.stop_capturing()
def test_stderr():
os.close(2)
cap = StdCaptureFD(out=False, err=True, in_=False)
- cap.done()
+ cap.stop_capturing()
def test_stdin():
os.close(0)
cap = StdCaptureFD(out=False, err=False, in_=True)
- cap.done()
+ cap.stop_capturing()
""")
result = testdir.runpytest("--capture=fd")
assert result.ret == 0
@@ -941,8 +943,8 @@
def test_capture_not_started_but_reset():
capsys = capture.StdCapture()
- capsys.done()
- capsys.done()
+ capsys.stop_capturing()
+ capsys.stop_capturing()
capsys.reset()
@@ -951,7 +953,7 @@
capsys = capture.StdCapture()
try:
cap = capture.StdCaptureFD(patchsys=False)
- cap.startall()
+ cap.start_capturing()
sys.stdout.write("hello")
sys.stderr.write("world")
oswritebytes(1, "1")
@@ -970,10 +972,9 @@
tmpfile = True
cap = capture.StdCaptureFD(out=False, err=tmpfile)
try:
- cap.startall()
+ cap.start_capturing()
capfile = cap.err.tmpfile
- cap.suspend()
- cap.resume()
+ cap.readouterr()
finally:
cap.reset()
capfile2 = cap.err.tmpfile
@@ -990,22 +991,25 @@
import py, logging
from _pytest import capture
cap = capture.%s(out=False, in_=False)
- cap.startall()
+ cap.start_capturing()
logging.warn("hello1")
- outerr = cap.suspend()
+ outerr = cap.readouterr()
print ("suspend, captured %%s" %%(outerr,))
logging.warn("hello2")
- cap.resume()
+ cap.pop_outerr_to_orig()
logging.warn("hello3")
- outerr = cap.suspend()
+ outerr = cap.readouterr()
print ("suspend2, captured %%s" %% (outerr,))
""" % (method,))
result = testdir.runpython(p)
- result.stdout.fnmatch_lines([
- "suspend, captured*hello1*",
- "suspend2, captured*hello2*WARNING:root:hello3*",
- ])
+ result.stdout.fnmatch_lines("""
+ suspend, captured*hello1*
+ suspend2, captured*WARNING:root:hello3*
+ """)
+ result.stderr.fnmatch_lines("""
+ WARNING:root:hello2
+ """)
assert "atexit" not in result.stderr.str()
https://bitbucket.org/hpk42/pytest/commits/d774e7c11ee6/
Changeset: d774e7c11ee6
User: hpk42
Date: 2014-03-14 12:49:37
Summary: simplify pdb disabling of capturing, also accomodate the new semantics
that capturing is always on during a test session.
Affected #: 1 file
diff -r cfac82eca2e810f0c6bf74945981f57e66d1d316 -r d774e7c11ee60f641f360b366d2f4d8165ec8ed1 _pytest/pdb.py
--- a/_pytest/pdb.py
+++ b/_pytest/pdb.py
@@ -16,43 +16,30 @@
if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
- old_trace = py.std.pdb.set_trace
+ old = (py.std.pdb.set_trace, pytestPDB._pluginmanager)
def fin():
- py.std.pdb.set_trace = old_trace
+ py.std.pdb.set_trace, pytestPDB._pluginmanager = old
py.std.pdb.set_trace = pytest.set_trace
+ pytestPDB._pluginmanager = config.pluginmanager
config._cleanup.append(fin)
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
- item = None
- collector = None
+ _pluginmanager = None
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
frame = sys._getframe().f_back
- item = self.item or self.collector
-
- if item is not None:
- capman = item.config.pluginmanager.getplugin("capturemanager")
+ capman = None
+ if self._pluginmanager is not None:
+ capman = self._pluginmanager.getplugin("capturemanager")
if capman:
capman.reset_capturings()
- tw = item.config.get_terminal_writer()
+ tw = py.io.TerminalWriter()
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
py.std.pdb.Pdb().set_trace(frame)
-def pdbitem(item):
- pytestPDB.item = item
-pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
-
- at pytest.mark.hookwrapper
-def pytest_make_collect_report(collector):
- pytestPDB.collector = collector
- yield
- pytestPDB.collector = None
-
-def pytest_runtest_makereport():
- pytestPDB.item = None
class PdbInvoke:
def pytest_exception_interact(self, node, call, report):
https://bitbucket.org/hpk42/pytest/commits/25c6de59b09b/
Changeset: 25c6de59b09b
User: hpk42
Date: 2014-03-14 15:58:16
Summary: tentatively fix py33 and py25 compat
Affected #: 1 file
diff -r d774e7c11ee60f641f360b366d2f4d8165ec8ed1 -r 25c6de59b09b45455976fa3139cdff360d95b82c _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -2,6 +2,8 @@
per-test stdout/stderr capturing mechanism.
"""
+from __future__ import with_statement
+
import sys
import os
import tempfile
@@ -366,12 +368,9 @@
def writeorg(self, data):
""" write a string to the original file descriptor
"""
- tempfp = tempfile.TemporaryFile()
- try:
- os.dup2(self._savefd, tempfp.fileno())
- tempfp.write(data)
- finally:
- tempfp.close()
+ if py.builtin._istext(data):
+ data = data.encode("utf8") # XXX use encoding of original stream
+ os.write(self._savefd, data)
def dupfile(f, mode=None, buffering=0, raising=False, encoding=None):
https://bitbucket.org/hpk42/pytest/commits/b36800a30a51/
Changeset: b36800a30a51
User: hpk42
Date: 2014-03-27 13:57:54
Summary: merge in current default
Affected #: 22 files
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 .hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -9,6 +9,7 @@
bin/
include/
.Python/
+.env/
# These lines are suggested according to the svn:ignore property
# Feel free to enable them by uncommenting them
@@ -27,6 +28,7 @@
*.egg-info
issue/
env/
+env3/
3rdparty/
.tox
.cache
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 AUTHORS
--- a/AUTHORS
+++ b/AUTHORS
@@ -38,3 +38,5 @@
Mark Abramowitz
Piotr Banaszkiewicz
Jurko Gospodnetić
+Marc Schlaich
+Christopher Gilling
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,19 @@
- change skips into warnings for test classes with an __init__ and
callables in test modules which look like a test but are not functions.
+- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions
+ during collection/loading of test modules. Thanks to Marc Schlaich
+ for the complete PR.
+
+- fix issue490: include pytest_load_initial_conftests in documentation
+ and improve docstring.
+
+- fix issue472: clarify that ``pytest.config.getvalue()`` cannot work
+ if it's triggered ahead of command line parsing.
+
+- merge PR123: improved integration with mock.patch decorator on tests.
+
+
2.5.2
-----------------------------------
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -440,7 +440,7 @@
if len(option) == 2 or option[2] == ' ':
return_list.append(option)
if option[2:] == short_long.get(option.replace('-', '')):
- return_list.append(option)
+ return_list.append(option.replace(' ', '='))
action._formatted_action_invocation = ', '.join(return_list)
return action._formatted_action_invocation
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -53,8 +53,8 @@
pytest_cmdline_main.firstresult = True
def pytest_load_initial_conftests(args, early_config, parser):
- """ implements loading initial conftests.
- """
+ """ implements the loading of initial conftest files ahead
+ of command line option parsing. """
def pytest_configure(config):
""" called after command line options have been parsed
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -8,7 +8,7 @@
except ImportError:
from UserDict import DictMixin as MappingMixin
-from _pytest.runner import collect_one_node, Skipped
+from _pytest.runner import collect_one_node
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@@ -409,10 +409,6 @@
and thus iteratively build a tree.
"""
- # the set of exceptions to interpret as "Skip the whole module" during
- # collection
- skip_exceptions = (Skipped,)
-
class CollectError(Exception):
""" an error during collection, contains a custom message. """
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 _pytest/nose.py
--- a/_pytest/nose.py
+++ b/_pytest/nose.py
@@ -1,17 +1,27 @@
""" run test suites written for nose. """
-import pytest, py
import sys
+
+import py
+import pytest
from _pytest import unittest
+
+def get_skip_exceptions():
+ skip_classes = set()
+ for module_name in ('unittest', 'unittest2', 'nose'):
+ mod = sys.modules.get(module_name)
+ if hasattr(mod, 'SkipTest'):
+ skip_classes.add(mod.SkipTest)
+ return tuple(skip_classes)
+
+
def pytest_runtest_makereport(__multicall__, item, call):
- SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
- if SkipTest:
- if call.excinfo and call.excinfo.errisinstance(SkipTest):
- # let's substitute the excinfo with a pytest.skip one
- call2 = call.__class__(lambda:
- pytest.skip(str(call.excinfo.value)), call.when)
- call.excinfo = call2.excinfo
+ if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
+ # let's substitute the excinfo with a pytest.skip one
+ call2 = call.__class__(lambda:
+ pytest.skip(str(call.excinfo.value)), call.when)
+ call.excinfo = call2.excinfo
@pytest.mark.trylast
@@ -38,13 +48,8 @@
# #call_optional(item._nosegensetup, 'teardown')
# del item.parent._nosegensetup
+
def pytest_make_collect_report(collector):
- SkipTest = getattr(sys.modules.get('unittest', None), 'SkipTest', None)
- if SkipTest is not None:
- collector.skip_exceptions += (SkipTest,)
- SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
- if SkipTest is not None:
- collector.skip_exceptions += (SkipTest,)
if isinstance(collector, pytest.Generator):
call_optional(collector.obj, 'setup')
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -1841,7 +1841,13 @@
if startindex is None:
startindex = inspect.ismethod(function) and 1 or 0
if realfunction != function:
- startindex += len(getattr(function, "patchings", []))
+ mock = sys.modules.get('mock')
+ if mock is not None:
+ for patching in getattr(function, "patchings", []):
+ if not patching.attribute_name and patching.new is mock.DEFAULT:
+ startindex += 1
+ else:
+ startindex += len(getattr(function, "patchings", []))
function = realfunction
argnames = inspect.getargs(py.code.getrawcode(function))[0]
defaults = getattr(function, 'func_defaults',
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -274,7 +274,9 @@
if not call.excinfo:
outcome = "passed"
else:
- if call.excinfo.errisinstance(collector.skip_exceptions):
+ from _pytest import nose
+ skip_exceptions = (Skipped,) + nose.get_skip_exceptions()
+ if call.excinfo.errisinstance(skip_exceptions):
outcome = "skipped"
r = collector._repr_failure_py(call.excinfo, "line").reprcrash
longrepr = (str(r.path), r.lineno, r.message)
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/_templates/links.html
--- a/doc/en/_templates/links.html
+++ b/doc/en/_templates/links.html
@@ -4,7 +4,7 @@
<li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li><li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li><li><a href="https://bitbucket.org/hpk42/pytest/">pytest @ Bitbucket</a></li>
- <li><a href="http://pytest.org/latest/plugins_index/index.html">3rd party plugins (beta)</a></li>
+ <li><a href="http://pytest.org/latest/plugins_index/index.html">3rd party plugins</a></li><li><a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">Issue Tracker</a></li><li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a></ul>
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/goodpractises.txt
--- a/doc/en/goodpractises.txt
+++ b/doc/en/goodpractises.txt
@@ -1,4 +1,3 @@
-
.. highlightlang:: python
.. _`goodpractises`:
@@ -69,7 +68,7 @@
- **avoid "__init__.py" files in your test directories**.
This way your tests can run easily against an installed version
- of ``mypkg``, independently from if the installed package contains
+ of ``mypkg``, independently from the installed package if it contains
the tests or not.
- With inlined tests you might put ``__init__.py`` into test
@@ -190,12 +189,16 @@
user_options = []
def initialize_options(self):
pass
+
def finalize_options(self):
pass
+
def run(self):
import sys,subprocess
errno = subprocess.call([sys.executable, 'runtests.py'])
raise SystemExit(errno)
+
+
setup(
#...,
cmdclass = {'test': PyTest},
@@ -220,20 +223,24 @@
Most often it is better to use tox_ instead, but here is how you can
get started with setuptools integration::
+ import sys
+
from setuptools.command.test import test as TestCommand
- import sys
+
class PyTest(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
+
def run_tests(self):
#import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(self.test_args)
sys.exit(errno)
+
setup(
#...,
tests_require=['pytest'],
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/plugins.txt
--- a/doc/en/plugins.txt
+++ b/doc/en/plugins.txt
@@ -64,9 +64,10 @@
pip uninstall pytest-NAME
If a plugin is installed, ``pytest`` automatically finds and integrates it,
-there is no need to activate it. We have a :doc:`beta page listing
-all 3rd party plugins and their status <plugins_index/index>` and here
-is a little annotated list for some popular plugins:
+there is no need to activate it. We have a :doc:`page listing
+all 3rd party plugins and their status against the latest py.test version
+<plugins_index/index>` and here is a little annotated list
+for some popular plugins:
.. _`django`: https://www.djangoproject.com/
@@ -109,7 +110,11 @@
* `oejskit <http://pypi.python.org/pypi/oejskit>`_:
a plugin to run javascript unittests in life browsers
-You may discover more plugins through a `pytest- pypi.python.org search`_.
+To see a complete list of all plugins with their latest testing
+status against different py.test and Python versions, please visit
+`pytest-plugs <http://pytest-plugs.herokuapp.com/>`_.
+
+You may also discover more plugins through a `pytest- pypi.python.org search`_.
.. _`available installable plugins`:
.. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search
@@ -304,6 +309,7 @@
.. currentmodule:: _pytest.hookspec
+.. autofunction:: pytest_load_initial_conftests
.. autofunction:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_parse
.. autofunction:: pytest_namespace
@@ -315,7 +321,7 @@
Generic "runtest" hooks
------------------------------
-All all runtest related hooks receive a :py:class:`pytest.Item` object.
+All runtest related hooks receive a :py:class:`pytest.Item` object.
.. autofunction:: pytest_runtest_protocol
.. autofunction:: pytest_runtest_setup
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/plugins_index/bitbucket.png
Binary file doc/en/plugins_index/bitbucket.png has changed
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/plugins_index/github.png
Binary file doc/en/plugins_index/github.png has changed
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/plugins_index/index.txt
--- a/doc/en/plugins_index/index.txt
+++ b/doc/en/plugins_index/index.txt
@@ -3,114 +3,125 @@
List of Third-Party Plugins
===========================
-================================================================================== =========================================================================================================== =========================================================================================================== ============================================================= =============================================================================================================================================
- Name Py27 Py33 Repository Summary
-================================================================================== =========================================================================================================== =========================================================================================================== ============================================================= =============================================================================================================================================
- `pytest-bdd <http://pypi.python.org/pypi/pytest-bdd>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-latest?py=py33&pytest=2.5.2 https://github.com/olegpidsadnyi/pytest-bdd BDD for pytest
- :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-latest?py=py33&pytest=2.5.2
- `pytest-bdd-splinter <http://pypi.python.org/pypi/pytest-bdd-splinter>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-splinter-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-splinter-latest?py=py33&pytest=2.5.2 https://github.com/olegpidsadnyi/pytest-bdd-splinter Splinter subplugin for Pytest BDD plugin
- :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-splinter-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-splinter-latest?py=py33&pytest=2.5.2
- `pytest-bench <http://pypi.python.org/pypi/pytest-bench>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bench-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bench-latest?py=py33&pytest=2.5.2 http://github.com/concordusapps/pytest-bench Benchmark utility that plugs into pytest.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-bench-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bench-latest?py=py33&pytest=2.5.2
- `pytest-blockage <http://pypi.python.org/pypi/pytest-blockage>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-blockage-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-blockage-latest?py=py33&pytest=2.5.2 https://github.com/rob-b/pytest-blockage Disable network requests during a test run.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-blockage-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-blockage-latest?py=py33&pytest=2.5.2
- `pytest-browsermob-proxy <http://pypi.python.org/pypi/pytest-browsermob-proxy>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py33&pytest=2.5.2 https://github.com/davehunt/pytest-browsermob-proxy BrowserMob proxy plugin for py.test.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py33&pytest=2.5.2
- `pytest-bugzilla <http://pypi.python.org/pypi/pytest-bugzilla>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bugzilla-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bugzilla-latest?py=py33&pytest=2.5.2 http://github.com/nibrahim/pytest_bugzilla py.test bugzilla integration plugin
- :target: http://pytest-plugs.herokuapp.com/output/pytest-bugzilla-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bugzilla-latest?py=py33&pytest=2.5.2
- `pytest-cache <http://pypi.python.org/pypi/pytest-cache>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cache-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cache-latest?py=py33&pytest=2.5.2 http://bitbucket.org/hpk42/pytest-cache/ pytest plugin with mechanisms for caching across test runs
- :target: http://pytest-plugs.herokuapp.com/output/pytest-cache-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-cache-latest?py=py33&pytest=2.5.2
- `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-capturelog-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-capturelog-latest?py=py33&pytest=2.5.2 http://bitbucket.org/memedough/pytest-capturelog/overview py.test plugin to capture log messages
- :target: http://pytest-plugs.herokuapp.com/output/pytest-capturelog-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-capturelog-latest?py=py33&pytest=2.5.2
- `pytest-codecheckers <http://pypi.python.org/pypi/pytest-codecheckers>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-codecheckers-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-codecheckers-latest?py=py33&pytest=2.5.2 http://bitbucket.org/RonnyPfannschmidt/pytest-codecheckers/ pytest plugin to add source code sanity checks (pep8 and friends)
- :target: http://pytest-plugs.herokuapp.com/output/pytest-codecheckers-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-codecheckers-latest?py=py33&pytest=2.5.2
- `pytest-contextfixture <http://pypi.python.org/pypi/pytest-contextfixture>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-contextfixture-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-contextfixture-latest?py=py33&pytest=2.5.2 http://github.com/pelme/pytest-contextfixture/ Define pytest fixtures as context managers.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-contextfixture-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-contextfixture-latest?py=py33&pytest=2.5.2
- `pytest-couchdbkit <http://pypi.python.org/pypi/pytest-couchdbkit>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-couchdbkit-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-couchdbkit-latest?py=py33&pytest=2.5.2 http://bitbucket.org/RonnyPfannschmidt/pytest-couchdbkit py.test extension for per-test couchdb databases using couchdbkit
- :target: http://pytest-plugs.herokuapp.com/output/pytest-couchdbkit-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-couchdbkit-latest?py=py33&pytest=2.5.2
- `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cov-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cov-latest?py=py33&pytest=2.5.2 http://bitbucket.org/memedough/pytest-cov/overview py.test plugin for coverage reporting with support for both centralised and distributed testing, including subprocesses and multiprocessing
- :target: http://pytest-plugs.herokuapp.com/output/pytest-cov-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-cov-latest?py=py33&pytest=2.5.2
- `pytest-dbfixtures <http://pypi.python.org/pypi/pytest-dbfixtures>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-dbfixtures-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-dbfixtures-latest?py=py33&pytest=2.5.2 https://github.com/clearcode/pytest-dbfixtures dbfixtures plugin for py.test.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-dbfixtures-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-dbfixtures-latest?py=py33&pytest=2.5.2
- `pytest-django <http://pypi.python.org/pypi/pytest-django>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-latest?py=py33&pytest=2.5.2 http://pytest-django.readthedocs.org/ A Django plugin for py.test.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-django-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-django-latest?py=py33&pytest=2.5.2
- `pytest-django-lite <http://pypi.python.org/pypi/pytest-django-lite>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-lite-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-lite-latest?py=py33&pytest=2.5.2 https://github.com/dcramer/pytest-django-lite The bare minimum to integrate py.test with Django.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-django-lite-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-django-lite-latest?py=py33&pytest=2.5.2
- `pytest-figleaf <http://pypi.python.org/pypi/pytest-figleaf>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-figleaf-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-figleaf-latest?py=py33&pytest=2.5.2 http://bitbucket.org/hpk42/pytest-figleaf py.test figleaf coverage plugin
- :target: http://pytest-plugs.herokuapp.com/output/pytest-figleaf-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-figleaf-latest?py=py33&pytest=2.5.2
- `pytest-flakes <http://pypi.python.org/pypi/pytest-flakes>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-flakes-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-flakes-latest?py=py33&pytest=2.5.2 https://github.com/fschulze/pytest-flakes pytest plugin to check source code with pyflakes
- :target: http://pytest-plugs.herokuapp.com/output/pytest-flakes-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-flakes-latest?py=py33&pytest=2.5.2
- `pytest-greendots <http://pypi.python.org/pypi/pytest-greendots>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-greendots-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-greendots-latest?py=py33&pytest=2.5.2 UNKNOWN Green progress dots
- :target: http://pytest-plugs.herokuapp.com/output/pytest-greendots-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-greendots-latest?py=py33&pytest=2.5.2
- `pytest-growl <http://pypi.python.org/pypi/pytest-growl>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-growl-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-growl-latest?py=py33&pytest=2.5.2 UNKNOWN Growl notifications for pytest results.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-growl-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-growl-latest?py=py33&pytest=2.5.2
- `pytest-httpretty <http://pypi.python.org/pypi/pytest-httpretty>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-httpretty-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-httpretty-latest?py=py33&pytest=2.5.2 http://github.com/papaeye/pytest-httpretty A thin wrapper of HTTPretty for pytest
- :target: http://pytest-plugs.herokuapp.com/output/pytest-httpretty-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-httpretty-latest?py=py33&pytest=2.5.2
- `pytest-incremental <http://pypi.python.org/pypi/pytest-incremental>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-incremental-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-incremental-latest?py=py33&pytest=2.5.2 https://bitbucket.org/schettino72/pytest-incremental an incremental test runner (pytest plugin)
- :target: http://pytest-plugs.herokuapp.com/output/pytest-incremental-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-incremental-latest?py=py33&pytest=2.5.2
- `pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-instafail-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-instafail-latest?py=py33&pytest=2.5.2 https://github.com/jpvanhal/pytest-instafail py.test plugin to show failures instantly
- :target: http://pytest-plugs.herokuapp.com/output/pytest-instafail-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-instafail-latest?py=py33&pytest=2.5.2
- `pytest-ipdb <http://pypi.python.org/pypi/pytest-ipdb>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-ipdb-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-ipdb-latest?py=py33&pytest=2.5.2 https://github.com/mverteuil/pytest-ipdb A py.test plug-in to enable drop to ipdb debugger on test failure.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-ipdb-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-ipdb-latest?py=py33&pytest=2.5.2
- `pytest-jira <http://pypi.python.org/pypi/pytest-jira>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-jira-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-jira-latest?py=py33&pytest=2.5.2 http://github.com/jlaska/pytest_jira py.test JIRA integration plugin, using markers
- :target: http://pytest-plugs.herokuapp.com/output/pytest-jira-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-jira-latest?py=py33&pytest=2.5.2
- `pytest-konira <http://pypi.python.org/pypi/pytest-konira>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-konira-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-konira-latest?py=py33&pytest=2.5.2 http://github.com/alfredodeza/pytest-konira Run Konira DSL tests with py.test
- :target: http://pytest-plugs.herokuapp.com/output/pytest-konira-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-konira-latest?py=py33&pytest=2.5.2
- `pytest-localserver <http://pypi.python.org/pypi/pytest-localserver>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-localserver-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-localserver-latest?py=py33&pytest=2.5.2 http://bitbucket.org/basti/pytest-localserver/ py.test plugin to test server connections locally.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-localserver-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-localserver-latest?py=py33&pytest=2.5.2
- `pytest-marker-bugzilla <http://pypi.python.org/pypi/pytest-marker-bugzilla>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py33&pytest=2.5.2 http://github.com/eanxgeek/pytest_marker_bugzilla py.test bugzilla integration plugin, using markers
- :target: http://pytest-plugs.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py33&pytest=2.5.2
- `pytest-markfiltration <http://pypi.python.org/pypi/pytest-markfiltration>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-markfiltration-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-markfiltration-latest?py=py33&pytest=2.5.2 https://github.com/adamgoucher/pytest-markfiltration UNKNOWN
- :target: http://pytest-plugs.herokuapp.com/output/pytest-markfiltration-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-markfiltration-latest?py=py33&pytest=2.5.2
- `pytest-marks <http://pypi.python.org/pypi/pytest-marks>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marks-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marks-latest?py=py33&pytest=2.5.2 https://github.com/adamgoucher/pytest-marks UNKNOWN
- :target: http://pytest-plugs.herokuapp.com/output/pytest-marks-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-marks-latest?py=py33&pytest=2.5.2
- `pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-monkeyplus-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-monkeyplus-latest?py=py33&pytest=2.5.2 http://bitbucket.org/hsoft/pytest-monkeyplus/ pytest's monkeypatch subclass with extra functionalities
- :target: http://pytest-plugs.herokuapp.com/output/pytest-monkeyplus-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-monkeyplus-latest?py=py33&pytest=2.5.2
- `pytest-mozwebqa <http://pypi.python.org/pypi/pytest-mozwebqa>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-mozwebqa-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-mozwebqa-latest?py=py33&pytest=2.5.2 https://github.com/davehunt/pytest-mozwebqa Mozilla WebQA plugin for py.test.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-mozwebqa-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-mozwebqa-latest?py=py33&pytest=2.5.2
- `pytest-oerp <http://pypi.python.org/pypi/pytest-oerp>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-oerp-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-oerp-latest?py=py33&pytest=2.5.2 http://github.com/santagada/pytest-oerp/ pytest plugin to test OpenERP modules
- :target: http://pytest-plugs.herokuapp.com/output/pytest-oerp-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-oerp-latest?py=py33&pytest=2.5.2
- `pytest-osxnotify <http://pypi.python.org/pypi/pytest-osxnotify>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-osxnotify-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-osxnotify-latest?py=py33&pytest=2.5.2 https://github.com/dbader/pytest-osxnotify OS X notifications for py.test results.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-osxnotify-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-osxnotify-latest?py=py33&pytest=2.5.2
- `pytest-paste-config <http://pypi.python.org/pypi/pytest-paste-config>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-paste-config-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-paste-config-latest?py=py33&pytest=2.5.2 UNKNOWN Allow setting the path to a paste config file
- :target: http://pytest-plugs.herokuapp.com/output/pytest-paste-config-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-paste-config-latest?py=py33&pytest=2.5.2
- `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pep8-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pep8-latest?py=py33&pytest=2.5.2 http://bitbucket.org/hpk42/pytest-pep8/ pytest plugin to check PEP8 requirements
- :target: http://pytest-plugs.herokuapp.com/output/pytest-pep8-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-pep8-latest?py=py33&pytest=2.5.2
- `pytest-poo <http://pypi.python.org/pypi/pytest-poo>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-poo-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-poo-latest?py=py33&pytest=2.5.2 http://github.com/pelme/pytest-poo Visualize your crappy tests
- :target: http://pytest-plugs.herokuapp.com/output/pytest-poo-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-poo-latest?py=py33&pytest=2.5.2
- `pytest-pydev <http://pypi.python.org/pypi/pytest-pydev>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pydev-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pydev-latest?py=py33&pytest=2.5.2 http://bitbucket.org/basti/pytest-pydev/ py.test plugin to connect to a remote debug server with PyDev or PyCharm.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-pydev-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-pydev-latest?py=py33&pytest=2.5.2
- `pytest-pythonpath <http://pypi.python.org/pypi/pytest-pythonpath>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pythonpath-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pythonpath-latest?py=py33&pytest=2.5.2 https://github.com/bigsassy/pytest-pythonpath pytest plugin for adding to the PYTHONPATH from command line or configs.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-pythonpath-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-pythonpath-latest?py=py33&pytest=2.5.2
- `pytest-qt <http://pypi.python.org/pypi/pytest-qt>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-qt-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-qt-latest?py=py33&pytest=2.5.2 http://github.com/nicoddemus/pytest-qt pytest plugin that adds fixtures for testing Qt (PyQt and PySide) applications.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-qt-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-qt-latest?py=py33&pytest=2.5.2
- `pytest-quickcheck <http://pypi.python.org/pypi/pytest-quickcheck>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-quickcheck-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-quickcheck-latest?py=py33&pytest=2.5.2 http://bitbucket.org/t2y/pytest-quickcheck/ pytest plugin to generate random data inspired by QuickCheck
- :target: http://pytest-plugs.herokuapp.com/output/pytest-quickcheck-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-quickcheck-latest?py=py33&pytest=2.5.2
- `pytest-rage <http://pypi.python.org/pypi/pytest-rage>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rage-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rage-latest?py=py33&pytest=2.5.2 http://github.com/santagada/pytest-rage/ pytest plugin to implement PEP712
- :target: http://pytest-plugs.herokuapp.com/output/pytest-rage-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-rage-latest?py=py33&pytest=2.5.2
- `pytest-random <http://pypi.python.org/pypi/pytest-random>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-random-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-random-latest?py=py33&pytest=2.5.2 https://github.com/klrmn/pytest-random py.test plugin to randomize tests
- :target: http://pytest-plugs.herokuapp.com/output/pytest-random-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-random-latest?py=py33&pytest=2.5.2
- `pytest-rerunfailures <http://pypi.python.org/pypi/pytest-rerunfailures>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rerunfailures-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rerunfailures-latest?py=py33&pytest=2.5.2 https://github.com/klrmn/pytest-rerunfailures py.test plugin to re-run tests to eliminate flakey failures
- :target: http://pytest-plugs.herokuapp.com/output/pytest-rerunfailures-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-rerunfailures-latest?py=py33&pytest=2.5.2
- `pytest-runfailed <http://pypi.python.org/pypi/pytest-runfailed>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runfailed-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runfailed-latest?py=py33&pytest=2.5.2 http://github.com/dmerejkowsky/pytest-runfailed implement a --failed option for pytest
- :target: http://pytest-plugs.herokuapp.com/output/pytest-runfailed-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-runfailed-latest?py=py33&pytest=2.5.2
- `pytest-runner <http://pypi.python.org/pypi/pytest-runner>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runner-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runner-latest?py=py33&pytest=2.5.2 https://bitbucket.org/jaraco/pytest-runner UNKNOWN
- :target: http://pytest-plugs.herokuapp.com/output/pytest-runner-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-runner-latest?py=py33&pytest=2.5.2
- `pytest-sugar <http://pypi.python.org/pypi/pytest-sugar>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-sugar-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-sugar-latest?py=py33&pytest=2.5.2 http://pivotfinland.com/pytest-sugar/ py.test is a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly).
- :target: http://pytest-plugs.herokuapp.com/output/pytest-sugar-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-sugar-latest?py=py33&pytest=2.5.2
- `pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-timeout-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-timeout-latest?py=py33&pytest=2.5.2 http://bitbucket.org/flub/pytest-timeout/ pytest plugin to abort tests after a timeout
- :target: http://pytest-plugs.herokuapp.com/output/pytest-timeout-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-timeout-latest?py=py33&pytest=2.5.2
- `pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-twisted-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-twisted-latest?py=py33&pytest=2.5.2 https://github.com/schmir/pytest-twisted A twisted plugin for py.test.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-twisted-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-twisted-latest?py=py33&pytest=2.5.2
- `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xdist-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xdist-latest?py=py33&pytest=2.5.2 http://bitbucket.org/hpk42/pytest-xdist py.test xdist plugin for distributed testing and loop-on-failing modes
- :target: http://pytest-plugs.herokuapp.com/output/pytest-xdist-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-xdist-latest?py=py33&pytest=2.5.2
- `pytest-xprocess <http://pypi.python.org/pypi/pytest-xprocess>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xprocess-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xprocess-latest?py=py33&pytest=2.5.2 http://bitbucket.org/hpk42/pytest-xprocess/ pytest plugin to manage external processes across test runs
- :target: http://pytest-plugs.herokuapp.com/output/pytest-xprocess-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-xprocess-latest?py=py33&pytest=2.5.2
- `pytest-yamlwsgi <http://pypi.python.org/pypi/pytest-yamlwsgi>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-yamlwsgi-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-yamlwsgi-latest?py=py33&pytest=2.5.2 UNKNOWN Run tests against wsgi apps defined in yaml
- :target: http://pytest-plugs.herokuapp.com/output/pytest-yamlwsgi-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-yamlwsgi-latest?py=py33&pytest=2.5.2
- `pytest-zap <http://pypi.python.org/pypi/pytest-zap>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-zap-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-zap-latest?py=py33&pytest=2.5.2 https://github.com/davehunt/pytest-zap OWASP ZAP plugin for py.test.
- :target: http://pytest-plugs.herokuapp.com/output/pytest-zap-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-zap-latest?py=py33&pytest=2.5.2
+The table below contains a listing of plugins found in PyPI and
+their status when tested using py.test **2.5.2** and python 2.7 and
+3.3.
-================================================================================== =========================================================================================================== =========================================================================================================== ============================================================= =============================================================================================================================================
+A complete listing can also be found at
+`pytest-plugs <http://pytest-plugs.herokuapp.com/>`_, which contains tests
+status against other py.test releases.
-*(Updated on 2014-02-11)*
+
+==================================================================================== ============================================================================================================ ============================================================================================================ ========================================================================= =============================================================================================================================================
+ Name Py27 Py33 Repo Summary
+==================================================================================== ============================================================================================================ ============================================================================================================ ========================================================================= =============================================================================================================================================
+ `pytest-bdd <http://pypi.python.org/pypi/pytest-bdd>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-latest?py=py33&pytest=2.5.2 .. image:: github.png BDD for pytest
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-latest?py=py33&pytest=2.5.2 :target: https://github.com/olegpidsadnyi/pytest-bdd
+ `pytest-bdd-splinter <http://pypi.python.org/pypi/pytest-bdd-splinter>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-splinter-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bdd-splinter-latest?py=py33&pytest=2.5.2 .. image:: github.png Splinter subplugin for Pytest BDD plugin
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-splinter-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bdd-splinter-latest?py=py33&pytest=2.5.2 :target: https://github.com/olegpidsadnyi/pytest-bdd-splinter
+ `pytest-bench <http://pypi.python.org/pypi/pytest-bench>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bench-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bench-latest?py=py33&pytest=2.5.2 .. image:: github.png Benchmark utility that plugs into pytest.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-bench-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bench-latest?py=py33&pytest=2.5.2 :target: http://github.com/concordusapps/pytest-bench
+ `pytest-blockage <http://pypi.python.org/pypi/pytest-blockage>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-blockage-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-blockage-latest?py=py33&pytest=2.5.2 .. image:: github.png Disable network requests during a test run.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-blockage-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-blockage-latest?py=py33&pytest=2.5.2 :target: https://github.com/rob-b/pytest-blockage
+ `pytest-browsermob-proxy <http://pypi.python.org/pypi/pytest-browsermob-proxy>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-browsermob-proxy-latest?py=py33&pytest=2.5.2 .. image:: github.png BrowserMob proxy plugin for py.test.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-browsermob-proxy-latest?py=py33&pytest=2.5.2 :target: https://github.com/davehunt/pytest-browsermob-proxy
+ `pytest-bugzilla <http://pypi.python.org/pypi/pytest-bugzilla>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bugzilla-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-bugzilla-latest?py=py33&pytest=2.5.2 .. image:: github.png py.test bugzilla integration plugin
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-bugzilla-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-bugzilla-latest?py=py33&pytest=2.5.2 :target: http://github.com/nibrahim/pytest_bugzilla
+ `pytest-cache <http://pypi.python.org/pypi/pytest-cache>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cache-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cache-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest plugin with mechanisms for caching across test runs
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-cache-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-cache-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/hpk42/pytest-cache/
+ `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-capturelog-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-capturelog-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test plugin to capture log messages
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-capturelog-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-capturelog-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/memedough/pytest-capturelog/overview
+ `pytest-codecheckers <http://pypi.python.org/pypi/pytest-codecheckers>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-codecheckers-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-codecheckers-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest plugin to add source code sanity checks (pep8 and friends)
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-codecheckers-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-codecheckers-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/RonnyPfannschmidt/pytest-codecheckers/
+ `pytest-contextfixture <http://pypi.python.org/pypi/pytest-contextfixture>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-contextfixture-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-contextfixture-latest?py=py33&pytest=2.5.2 .. image:: github.png Define pytest fixtures as context managers.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-contextfixture-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-contextfixture-latest?py=py33&pytest=2.5.2 :target: http://github.com/pelme/pytest-contextfixture/
+ `pytest-couchdbkit <http://pypi.python.org/pypi/pytest-couchdbkit>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-couchdbkit-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-couchdbkit-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test extension for per-test couchdb databases using couchdbkit
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-couchdbkit-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-couchdbkit-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/RonnyPfannschmidt/pytest-couchdbkit
+ `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cov-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-cov-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test plugin for coverage reporting with support for both centralised and distributed testing, including subprocesses and multiprocessing
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-cov-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-cov-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/memedough/pytest-cov/overview
+ `pytest-dbfixtures <http://pypi.python.org/pypi/pytest-dbfixtures>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-dbfixtures-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-dbfixtures-latest?py=py33&pytest=2.5.2 .. image:: github.png dbfixtures plugin for py.test.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-dbfixtures-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-dbfixtures-latest?py=py33&pytest=2.5.2 :target: https://github.com/clearcode/pytest-dbfixtures
+ `pytest-dbus-notification <http://pypi.python.org/pypi/pytest-dbus-notification>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-dbus-notification-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-dbus-notification-latest?py=py33&pytest=2.5.2 .. image:: github.png D-BUS notifications for pytest results.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-dbus-notification-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-dbus-notification-latest?py=py33&pytest=2.5.2 :target: https://github.com/bmathieu33/pytest-dbus-notification
+ `pytest-django <http://pypi.python.org/pypi/pytest-django>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-latest?py=py33&pytest=2.5.2 `link <http://pytest-django.readthedocs.org/>`_ A Django plugin for py.test.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-django-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-django-latest?py=py33&pytest=2.5.2
+ `pytest-django-lite <http://pypi.python.org/pypi/pytest-django-lite>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-lite-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-django-lite-latest?py=py33&pytest=2.5.2 .. image:: github.png The bare minimum to integrate py.test with Django.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-django-lite-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-django-lite-latest?py=py33&pytest=2.5.2 :target: https://github.com/dcramer/pytest-django-lite
+ `pytest-figleaf <http://pypi.python.org/pypi/pytest-figleaf>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-figleaf-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-figleaf-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test figleaf coverage plugin
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-figleaf-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-figleaf-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/hpk42/pytest-figleaf
+ `pytest-flakes <http://pypi.python.org/pypi/pytest-flakes>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-flakes-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-flakes-latest?py=py33&pytest=2.5.2 .. image:: github.png pytest plugin to check source code with pyflakes
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-flakes-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-flakes-latest?py=py33&pytest=2.5.2 :target: https://github.com/fschulze/pytest-flakes
+ `pytest-greendots <http://pypi.python.org/pypi/pytest-greendots>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-greendots-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-greendots-latest?py=py33&pytest=2.5.2 ? Green progress dots
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-greendots-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-greendots-latest?py=py33&pytest=2.5.2
+ `pytest-growl <http://pypi.python.org/pypi/pytest-growl>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-growl-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-growl-latest?py=py33&pytest=2.5.2 ? Growl notifications for pytest results.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-growl-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-growl-latest?py=py33&pytest=2.5.2
+ `pytest-httpretty <http://pypi.python.org/pypi/pytest-httpretty>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-httpretty-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-httpretty-latest?py=py33&pytest=2.5.2 .. image:: github.png A thin wrapper of HTTPretty for pytest
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-httpretty-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-httpretty-latest?py=py33&pytest=2.5.2 :target: http://github.com/papaeye/pytest-httpretty
+ `pytest-incremental <http://pypi.python.org/pypi/pytest-incremental>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-incremental-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-incremental-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png an incremental test runner (pytest plugin)
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-incremental-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-incremental-latest?py=py33&pytest=2.5.2 :target: https://bitbucket.org/schettino72/pytest-incremental
+ `pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-instafail-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-instafail-latest?py=py33&pytest=2.5.2 .. image:: github.png py.test plugin to show failures instantly
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-instafail-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-instafail-latest?py=py33&pytest=2.5.2 :target: https://github.com/jpvanhal/pytest-instafail
+ `pytest-ipdb <http://pypi.python.org/pypi/pytest-ipdb>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-ipdb-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-ipdb-latest?py=py33&pytest=2.5.2 .. image:: github.png A py.test plug-in to enable drop to ipdb debugger on test failure.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-ipdb-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-ipdb-latest?py=py33&pytest=2.5.2 :target: https://github.com/mverteuil/pytest-ipdb
+ `pytest-jira <http://pypi.python.org/pypi/pytest-jira>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-jira-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-jira-latest?py=py33&pytest=2.5.2 .. image:: github.png py.test JIRA integration plugin, using markers
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-jira-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-jira-latest?py=py33&pytest=2.5.2 :target: http://github.com/jlaska/pytest_jira
+ `pytest-konira <http://pypi.python.org/pypi/pytest-konira>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-konira-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-konira-latest?py=py33&pytest=2.5.2 .. image:: github.png Run Konira DSL tests with py.test
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-konira-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-konira-latest?py=py33&pytest=2.5.2 :target: http://github.com/alfredodeza/pytest-konira
+ `pytest-localserver <http://pypi.python.org/pypi/pytest-localserver>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-localserver-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-localserver-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test plugin to test server connections locally.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-localserver-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-localserver-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/basti/pytest-localserver/
+ `pytest-marker-bugzilla <http://pypi.python.org/pypi/pytest-marker-bugzilla>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marker-bugzilla-latest?py=py33&pytest=2.5.2 .. image:: github.png py.test bugzilla integration plugin, using markers
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-marker-bugzilla-latest?py=py33&pytest=2.5.2 :target: http://github.com/eanxgeek/pytest_marker_bugzilla
+ `pytest-markfiltration <http://pypi.python.org/pypi/pytest-markfiltration>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-markfiltration-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-markfiltration-latest?py=py33&pytest=2.5.2 .. image:: github.png UNKNOWN
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-markfiltration-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-markfiltration-latest?py=py33&pytest=2.5.2 :target: https://github.com/adamgoucher/pytest-markfiltration
+ `pytest-marks <http://pypi.python.org/pypi/pytest-marks>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marks-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-marks-latest?py=py33&pytest=2.5.2 .. image:: github.png UNKNOWN
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-marks-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-marks-latest?py=py33&pytest=2.5.2 :target: https://github.com/adamgoucher/pytest-marks
+ `pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-monkeyplus-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-monkeyplus-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest's monkeypatch subclass with extra functionalities
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-monkeyplus-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-monkeyplus-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/hsoft/pytest-monkeyplus/
+ `pytest-mozwebqa <http://pypi.python.org/pypi/pytest-mozwebqa>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-mozwebqa-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-mozwebqa-latest?py=py33&pytest=2.5.2 .. image:: github.png Mozilla WebQA plugin for py.test.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-mozwebqa-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-mozwebqa-latest?py=py33&pytest=2.5.2 :target: https://github.com/davehunt/pytest-mozwebqa
+ `pytest-oerp <http://pypi.python.org/pypi/pytest-oerp>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-oerp-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-oerp-latest?py=py33&pytest=2.5.2 .. image:: github.png pytest plugin to test OpenERP modules
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-oerp-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-oerp-latest?py=py33&pytest=2.5.2 :target: http://github.com/santagada/pytest-oerp/
+ `pytest-osxnotify <http://pypi.python.org/pypi/pytest-osxnotify>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-osxnotify-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-osxnotify-latest?py=py33&pytest=2.5.2 .. image:: github.png OS X notifications for py.test results.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-osxnotify-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-osxnotify-latest?py=py33&pytest=2.5.2 :target: https://github.com/dbader/pytest-osxnotify
+ `pytest-paste-config <http://pypi.python.org/pypi/pytest-paste-config>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-paste-config-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-paste-config-latest?py=py33&pytest=2.5.2 ? Allow setting the path to a paste config file
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-paste-config-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-paste-config-latest?py=py33&pytest=2.5.2
+ `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pep8-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pep8-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest plugin to check PEP8 requirements
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-pep8-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-pep8-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/hpk42/pytest-pep8/
+ `pytest-poo <http://pypi.python.org/pypi/pytest-poo>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-poo-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-poo-latest?py=py33&pytest=2.5.2 .. image:: github.png Visualize your crappy tests
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-poo-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-poo-latest?py=py33&pytest=2.5.2 :target: http://github.com/pelme/pytest-poo
+ `pytest-pydev <http://pypi.python.org/pypi/pytest-pydev>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pydev-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pydev-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test plugin to connect to a remote debug server with PyDev or PyCharm.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-pydev-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-pydev-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/basti/pytest-pydev/
+ `pytest-pythonpath <http://pypi.python.org/pypi/pytest-pythonpath>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pythonpath-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-pythonpath-latest?py=py33&pytest=2.5.2 .. image:: github.png pytest plugin for adding to the PYTHONPATH from command line or configs.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-pythonpath-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-pythonpath-latest?py=py33&pytest=2.5.2 :target: https://github.com/bigsassy/pytest-pythonpath
+ `pytest-qt <http://pypi.python.org/pypi/pytest-qt>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-qt-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-qt-latest?py=py33&pytest=2.5.2 .. image:: github.png pytest plugin that adds fixtures for testing Qt (PyQt and PySide) applications.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-qt-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-qt-latest?py=py33&pytest=2.5.2 :target: http://github.com/nicoddemus/pytest-qt
+ `pytest-quickcheck <http://pypi.python.org/pypi/pytest-quickcheck>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-quickcheck-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-quickcheck-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest plugin to generate random data inspired by QuickCheck
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-quickcheck-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-quickcheck-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/t2y/pytest-quickcheck/
+ `pytest-rage <http://pypi.python.org/pypi/pytest-rage>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rage-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rage-latest?py=py33&pytest=2.5.2 .. image:: github.png pytest plugin to implement PEP712
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-rage-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-rage-latest?py=py33&pytest=2.5.2 :target: http://github.com/santagada/pytest-rage/
+ `pytest-random <http://pypi.python.org/pypi/pytest-random>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-random-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-random-latest?py=py33&pytest=2.5.2 .. image:: github.png py.test plugin to randomize tests
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-random-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-random-latest?py=py33&pytest=2.5.2 :target: https://github.com/klrmn/pytest-random
+ `pytest-rerunfailures <http://pypi.python.org/pypi/pytest-rerunfailures>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rerunfailures-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-rerunfailures-latest?py=py33&pytest=2.5.2 .. image:: github.png py.test plugin to re-run tests to eliminate flakey failures
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-rerunfailures-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-rerunfailures-latest?py=py33&pytest=2.5.2 :target: https://github.com/klrmn/pytest-rerunfailures
+ `pytest-runfailed <http://pypi.python.org/pypi/pytest-runfailed>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runfailed-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runfailed-latest?py=py33&pytest=2.5.2 .. image:: github.png implement a --failed option for pytest
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-runfailed-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-runfailed-latest?py=py33&pytest=2.5.2 :target: http://github.com/dmerejkowsky/pytest-runfailed
+ `pytest-runner <http://pypi.python.org/pypi/pytest-runner>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runner-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-runner-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png UNKNOWN
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-runner-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-runner-latest?py=py33&pytest=2.5.2 :target: https://bitbucket.org/jaraco/pytest-runner
+ `pytest-sugar <http://pypi.python.org/pypi/pytest-sugar>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-sugar-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-sugar-latest?py=py33&pytest=2.5.2 `link <http://pivotfinland.com/pytest-sugar/>`_ py.test is a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly).
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-sugar-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-sugar-latest?py=py33&pytest=2.5.2
+ `pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-timeout-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-timeout-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest plugin to abort tests after a timeout
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-timeout-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-timeout-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/flub/pytest-timeout/
+ `pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-twisted-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-twisted-latest?py=py33&pytest=2.5.2 .. image:: github.png A twisted plugin for py.test.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-twisted-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-twisted-latest?py=py33&pytest=2.5.2 :target: https://github.com/schmir/pytest-twisted
+ `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xdist-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xdist-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png py.test xdist plugin for distributed testing and loop-on-failing modes
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-xdist-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-xdist-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/hpk42/pytest-xdist
+ `pytest-xprocess <http://pypi.python.org/pypi/pytest-xprocess>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xprocess-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-xprocess-latest?py=py33&pytest=2.5.2 .. image:: bitbucket.png pytest plugin to manage external processes across test runs
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-xprocess-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-xprocess-latest?py=py33&pytest=2.5.2 :target: http://bitbucket.org/hpk42/pytest-xprocess/
+ `pytest-yamlwsgi <http://pypi.python.org/pypi/pytest-yamlwsgi>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-yamlwsgi-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-yamlwsgi-latest?py=py33&pytest=2.5.2 ? Run tests against wsgi apps defined in yaml
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-yamlwsgi-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-yamlwsgi-latest?py=py33&pytest=2.5.2
+ `pytest-zap <http://pypi.python.org/pypi/pytest-zap>`_ .. image:: http://pytest-plugs.herokuapp.com/status/pytest-zap-latest?py=py27&pytest=2.5.2 .. image:: http://pytest-plugs.herokuapp.com/status/pytest-zap-latest?py=py33&pytest=2.5.2 .. image:: github.png OWASP ZAP plugin for py.test.
+ :target: http://pytest-plugs.herokuapp.com/output/pytest-zap-latest?py=py27&pytest=2.5.2 :target: http://pytest-plugs.herokuapp.com/output/pytest-zap-latest?py=py33&pytest=2.5.2 :target: https://github.com/davehunt/pytest-zap
+
+==================================================================================== ============================================================================================================ ============================================================================================================ ========================================================================= =============================================================================================================================================
+
+*(Updated on 2014-02-18)*
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/plugins_index/plugins_index.py
--- a/doc/en/plugins_index/plugins_index.py
+++ b/doc/en/plugins_index/plugins_index.py
@@ -1,6 +1,13 @@
"""
Script to generate the file `index.txt` with information about
-pytest plugins taken directly from a live PyPI server.
+pytest plugins taken directly from PyPI.
+
+Usage:
+ python plugins_index.py
+
+This command will update `index.txt` in the same directory found as this script.
+This should be issued before every major documentation release to obtain latest
+versions from PyPI.
Also includes plugin compatibility between different python and pytest versions,
obtained from http://pytest-plugs.herokuapp.com.
@@ -66,9 +73,34 @@
:param plugins: list of (name, version)
:param client: ServerProxy
"""
+ def get_repo_markup(repo):
+ """
+ obtains appropriate markup for the given repository, as two lines
+ that should be output in the same table row. We use this to display an icon
+ for known repository hosts (github, etc), just a "?" char when
+ repository is not registered in pypi or a simple link otherwise.
+ """
+ target = repo
+ if 'github.com' in repo:
+ image = 'github.png'
+ elif 'bitbucket.org' in repo:
+ image = 'bitbucket.png'
+ elif repo.lower() == 'unknown':
+ return '?', ''
+ else:
+ image = None
+
+ if image is not None:
+ image_markup = '.. image:: %s' % image
+ target_markup = ' :target: %s' % repo
+ pad_right = ('%-' + str(len(target_markup)) + 's')
+ return pad_right % image_markup, target_markup
+ else:
+ return '`link <%s>`_' % target, ''
+
rows = []
ColumnData = namedtuple('ColumnData', 'text link')
- headers = ['Name', 'Py27', 'Py33', 'Repository', 'Summary']
+ headers = ['Name', 'Py27', 'Py33', 'Repo', 'Summary']
pytest_version = pytest.__version__
repositories = obtain_override_repositories()
print('*** pytest-{0} ***'.format(pytest_version))
@@ -83,6 +115,9 @@
name=package_name,
version=version)
+ repository = repositories.get(package_name, release_data['home_page'])
+ repo_markup_1, repo_markup_2 = get_repo_markup(repository)
+
# first row: name, images and simple links
url = '.. image:: {site}/status/{name}-latest'
image_url = url.format(**common_params)
@@ -94,7 +129,7 @@
ColumnData(image_url.format(py='py33', pytest=pytest_version),
None),
ColumnData(
- repositories.get(package_name, release_data['home_page']),
+ repo_markup_1,
None),
ColumnData(release_data['summary'], None),
)
@@ -112,8 +147,9 @@
None),
ColumnData(output_url.format(py='py33', pytest=pytest_version),
None),
+ ColumnData(repo_markup_2, None),
ColumnData('', None),
- ColumnData('', None),
+
)
assert len(row) == len(headers)
rows.append(row)
@@ -167,11 +203,9 @@
return ' '.join(char * length for length in column_lengths)
with open(filename, 'w') as f:
- # write welcome
- print('.. _plugins_index:', file=f)
- print(file=f)
- print('List of Third-Party Plugins', file=f)
- print('===========================', file=f)
+ # header
+ header_text = HEADER.format(pytest_version=pytest.__version__)
+ print(header_text, file=f)
print(file=f)
# table
@@ -232,5 +266,21 @@
return 0
+# header for the plugins_index page
+HEADER = '''.. _plugins_index:
+
+List of Third-Party Plugins
+===========================
+
+The table below contains a listing of plugins found in PyPI and
+their status when tested using py.test **{pytest_version}** and python 2.7 and
+3.3.
+
+A complete listing can also be found at
+`pytest-plugs <http://pytest-plugs.herokuapp.com/>`_, which contains tests
+status against other py.test releases.
+'''
+
+
if __name__ == '__main__':
sys.exit(main(sys.argv))
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 doc/en/skipping.txt
--- a/doc/en/skipping.txt
+++ b/doc/en/skipping.txt
@@ -286,4 +286,9 @@
def test_function(...):
pass
+.. note::
+ You cannot use ``pytest.config.getvalue()`` in code
+ imported before py.test's argument parsing takes place. For example,
+ ``conftest.py`` files are imported before command line parsing and thus
+ ``config.getvalue()`` will not execute correctly.
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 testing/python/collect.py
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -277,7 +277,7 @@
assert hasattr(modcol.obj, 'test_func')
def test_function_as_object_instance_ignored(self, testdir):
- item = testdir.makepyfile("""
+ testdir.makepyfile("""
class A:
def __call__(self, tmpdir):
0/0
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 testing/python/integration.py
--- a/testing/python/integration.py
+++ b/testing/python/integration.py
@@ -124,12 +124,16 @@
def test_hello(self, abspath):
os.path.abspath("hello")
abspath.assert_any_call("hello")
+ def mock_basename(path):
+ return "mock_basename"
@mock.patch("os.path.abspath")
@mock.patch("os.path.normpath")
+ @mock.patch("os.path.basename", new=mock_basename)
def test_someting(normpath, abspath, tmpdir):
abspath.return_value = "this"
os.path.normpath(os.path.abspath("hello"))
normpath.assert_any_call("this")
+ assert os.path.basename("123") == "mock_basename"
""")
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2)
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -533,6 +533,24 @@
assert 'hello19' not in result.stdout.str()
+ at pytest.mark.xfail(reason='demonstrate #412')
+def test_capture_badoutput(testdir):
+ testdir.makepyfile("""
+ import os
+
+ def test_func():
+ omg = bytearray([1,129,1])
+ os.write(1, omg)
+ assert 0
+ """)
+ result = testdir.runpytest('--cap=fd')
+ #this fails on python3 - fnmatch first for debugging
+ result.stdout.fnmatch_lines([
+ '*1 failed*',
+ ])
+ assert result.ret == 1
+
+
def test_capture_early_option_parsing(testdir):
testdir.makeconftest("""
def pytest_runtest_setup():
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 testing/test_nose.py
--- a/testing/test_nose.py
+++ b/testing/test_nose.py
@@ -330,12 +330,26 @@
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1, skipped=1)
+
def test_SkipTest_during_collection(testdir):
- testdir.makepyfile("""
+ p = testdir.makepyfile("""
import nose
raise nose.SkipTest("during collection")
def test_failing():
assert False
""")
+ result = testdir.runpytest(p)
+ outcome = result.parseoutcomes()
+ outcome.pop('seconds')
+ assert outcome == dict(skipped=1)
+
+
+def test_SkipTest_in_test(testdir):
+ testdir.makepyfile("""
+ import nose
+
+ def test_skipping():
+ raise nose.SkipTest("in test")
+ """)
reprec = testdir.inline_run()
reprec.assertoutcome(skipped=1)
diff -r 25c6de59b09b45455976fa3139cdff360d95b82c -r b36800a30a51911876ae23ff0fc187145e0f9f03 tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -7,6 +7,7 @@
commands= py.test --lsof -rfsxX --junitxml={envlogdir}/junit-{envname}.xml []
deps=
nose
+ mock
[testenv:genscript]
changedir=.
https://bitbucket.org/hpk42/pytest/commits/b7aa02ac2440/
Changeset: b7aa02ac2440
User: hpk42
Date: 2014-03-28 07:03:34
Summary: remove unused "suspend/resume" on capturing, some formatting cleanup
Affected #: 2 files
diff -r b36800a30a51911876ae23ff0fc187145e0f9f03 -r b7aa02ac2440be4829e6f9f5b983f149e169c4b1 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -426,7 +426,7 @@
if hasattr(self, '_reset'):
raise ValueError("was already reset")
self._reset = True
- outfile, errfile = self.stop_capturing(save=False)
+ outfile, errfile = self.stop_capturing()
out, err = "", ""
if outfile and not outfile.closed:
out = outfile.read()
@@ -452,24 +452,9 @@
is invalid it will not be captured.
"""
def __init__(self, out=True, err=True, in_=True, patchsys=True):
- self._options = {
- "out": out,
- "err": err,
- "in_": in_,
- "patchsys": patchsys,
- }
- self._save()
-
- def _save(self):
- in_ = self._options['in_']
- out = self._options['out']
- err = self._options['err']
- patchsys = self._options['patchsys']
if in_:
try:
- self.in_ = FDCapture(
- 0, tmpfile=None,
- patchsys=patchsys)
+ self.in_ = FDCapture(0, tmpfile=None, patchsys=patchsys)
except OSError:
pass
if out:
@@ -477,10 +462,7 @@
if hasattr(out, 'write'):
tmpfile = out
try:
- self.out = FDCapture(
- 1, tmpfile=tmpfile,
- patchsys=patchsys)
- self._options['out'] = self.out.tmpfile
+ self.out = FDCapture(1, tmpfile=tmpfile, patchsys=patchsys)
except OSError:
pass
if err:
@@ -489,10 +471,7 @@
else:
tmpfile = None
try:
- self.err = FDCapture(
- 2, tmpfile=tmpfile,
- patchsys=patchsys)
- self._options['err'] = self.err.tmpfile
+ self.err = FDCapture(2, tmpfile=tmpfile, patchsys=patchsys)
except OSError:
pass
@@ -507,7 +486,7 @@
#def pytest_sessionfinish(self):
# self.reset_capturings()
- def stop_capturing(self, save=True):
+ def stop_capturing(self):
""" return (outfile, errfile) and stop capturing. """
outfile = errfile = None
if hasattr(self, 'out') and not self.out.tmpfile.closed:
@@ -516,8 +495,6 @@
errfile = self.err.done()
if hasattr(self, 'in_'):
self.in_.done()
- if save:
- self._save()
return outfile, errfile
def readouterr(self):
@@ -577,7 +554,7 @@
if self.in_:
sys.stdin = self.in_ = DontReadFromInput()
- def stop_capturing(self, save=True):
+ def stop_capturing(self):
""" return (outfile, errfile) and stop capturing. """
outfile = errfile = None
if self.out and not self.out.closed:
diff -r b36800a30a51911876ae23ff0fc187145e0f9f03 -r b7aa02ac2440be4829e6f9f5b983f149e169c4b1 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -873,25 +873,6 @@
pytest.raises(IOError, "sys.stdin.read()")
out, err = cap.reset()
- def test_suspend_resume(self):
- cap = self.getcapture(out=True, err=False, in_=False)
- try:
- print ("hello")
- sys.stderr.write("error\n")
- out, err = cap.readouterr()
- cap.stop_capturing()
- assert out == "hello\n"
- assert not err
- print ("in between")
- sys.stderr.write("in between\n")
- cap.start_capturing()
- print ("after")
- sys.stderr.write("error_after\n")
- finally:
- out, err = cap.reset()
- assert out == "after\n"
- assert not err
-
class TestStdCaptureFD(TestStdCapture):
pytestmark = needsosdup
@@ -925,12 +906,9 @@
@needsosdup
def test_stdcapture_fd_tmpfile(tmpfile):
capfd = capture.StdCaptureFD(out=tmpfile)
- try:
- os.write(1, "hello".encode("ascii"))
- os.write(2, "world".encode("ascii"))
- outf, errf = capfd.stop_capturing()
- finally:
- capfd.reset()
+ os.write(1, "hello".encode("ascii"))
+ os.write(2, "world".encode("ascii"))
+ outf, errf = capfd.stop_capturing()
assert outf == tmpfile
https://bitbucket.org/hpk42/pytest/commits/080d7f3e486f/
Changeset: 080d7f3e486f
User: hpk42
Date: 2014-03-28 07:03:37
Summary: unify and normalize Sys/FD Capturing classes
* * *
more unification
Affected #: 2 files
diff -r b7aa02ac2440be4829e6f9f5b983f149e169c4b1 -r 080d7f3e486f387bb501369a0444e32313e59a7d _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -100,44 +100,25 @@
raise
-class NoCapture:
- def start_capturing(self):
- pass
- def stop_capturing(self):
- pass
-
- def pop_outerr_to_orig(self):
- pass
-
- def reset(self):
- pass
-
- def readouterr(self):
- return "", ""
-
+def maketmpfile():
+ f = py.std.tempfile.TemporaryFile()
+ newf = dupfile(f, encoding="UTF-8")
+ f.close()
+ return newf
class CaptureManager:
def __init__(self, defaultmethod=None):
self._method2capture = {}
self._defaultmethod = defaultmethod
- def _maketempfile(self):
- f = py.std.tempfile.TemporaryFile()
- newf = dupfile(f, encoding="UTF-8")
- f.close()
- return newf
-
def _getcapture(self, method):
if method == "fd":
- return StdCaptureFD(
- out=self._maketempfile(),
- err=self._maketempfile(),
- )
+ return StdCaptureBase(out=True, err=True, Capture=FDCapture)
elif method == "sys":
- return StdCapture(out=TextIO(), err=TextIO())
+ return StdCaptureBase(out=True, err=True, Capture=SysCapture)
elif method == "no":
- return NoCapture()
+ return StdCaptureBase(out=False, err=False, in_=False)
else:
raise ValueError("unknown capturing method: %r" % method)
@@ -277,8 +258,7 @@
"""
if "capfd" in request._funcargs:
raise request.raiseerror(error_capsysfderror)
- return CaptureFixture(StdCapture)
-
+ return CaptureFixture(SysCapture)
def pytest_funcarg__capfd(request):
"""enables capturing of writes to file descriptors 1 and 2 and makes
@@ -289,12 +269,13 @@
request.raiseerror(error_capsysfderror)
if not hasattr(os, 'dup'):
pytest.skip("capfd funcarg needs os.dup")
- return CaptureFixture(StdCaptureFD)
+ return CaptureFixture(FDCapture)
class CaptureFixture:
def __init__(self, captureclass):
- self._capture = captureclass(in_=False)
+ self._capture = StdCaptureBase(out=True, err=True, in_=False,
+ Capture=captureclass)
def _start(self):
self._capture.start_capturing()
@@ -315,63 +296,6 @@
self._finalize()
-class FDCapture:
- """ Capture IO to/from a given os-level filedescriptor. """
-
- def __init__(self, targetfd, tmpfile=None, patchsys=False):
- """ save targetfd descriptor, and open a new
- temporary file there. If no tmpfile is
- specified a tempfile.Tempfile() will be opened
- in text mode.
- """
- self.targetfd = targetfd
- if tmpfile is None and targetfd != 0:
- # this code path is covered in the tests
- # but not used by a regular pytest run
- f = tempfile.TemporaryFile('wb+')
- tmpfile = dupfile(f, encoding="UTF-8")
- f.close()
- self.tmpfile = tmpfile
- self._savefd = os.dup(self.targetfd)
- if patchsys:
- self._oldsys = getattr(sys, patchsysdict[targetfd])
-
- def start(self):
- try:
- os.fstat(self._savefd)
- except OSError:
- raise ValueError(
- "saved filedescriptor not valid, "
- "did you call start() twice?")
- if self.targetfd == 0 and not self.tmpfile:
- fd = os.open(os.devnull, os.O_RDONLY)
- os.dup2(fd, 0)
- os.close(fd)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], DontReadFromInput())
- else:
- os.dup2(self.tmpfile.fileno(), self.targetfd)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], self.tmpfile)
-
- def done(self):
- """ unpatch and clean up, returns the self.tmpfile (file object)
- """
- os.dup2(self._savefd, self.targetfd)
- os.close(self._savefd)
- if self.targetfd != 0:
- self.tmpfile.seek(0)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], self._oldsys)
- return self.tmpfile
-
- def writeorg(self, data):
- """ write a string to the original file descriptor
- """
- if py.builtin._istext(data):
- data = data.encode("utf8") # XXX use encoding of original stream
- os.write(self._savefd, data)
-
def dupfile(f, mode=None, buffering=0, raising=False, encoding=None):
""" return a new open file object that's a duplicate of f
@@ -421,6 +345,16 @@
class StdCaptureBase(object):
+ out = err = in_ = None
+
+ def __init__(self, out=True, err=True, in_=True, Capture=None):
+ if in_:
+ self.in_ = Capture(0)
+ if out:
+ self.out = Capture(1)
+ if err:
+ self.err = Capture(2)
+
def reset(self):
""" reset sys.stdout/stderr and return captured output as strings. """
if hasattr(self, '_reset'):
@@ -436,6 +370,25 @@
errfile.close()
return out, err
+ def start_capturing(self):
+ if self.in_:
+ self.in_.start()
+ if self.out:
+ self.out.start()
+ if self.err:
+ self.err.start()
+
+ def stop_capturing(self):
+ """ return (outfile, errfile) and stop capturing. """
+ outfile = errfile = None
+ if self.out:
+ outfile = self.out.done()
+ if self.err:
+ errfile = self.err.done()
+ if self.in_:
+ self.in_.done()
+ return outfile, errfile
+
def pop_outerr_to_orig(self):
""" pop current snapshot out/err capture and flush to orig streams. """
out, err = self.readouterr()
@@ -444,61 +397,8 @@
if err:
self.err.writeorg(err)
-
-class StdCaptureFD(StdCaptureBase):
- """ This class allows to capture writes to FD1 and FD2
- and may connect a NULL file to FD0 (and prevent
- reads from sys.stdin). If any of the 0,1,2 file descriptors
- is invalid it will not be captured.
- """
- def __init__(self, out=True, err=True, in_=True, patchsys=True):
- if in_:
- try:
- self.in_ = FDCapture(0, tmpfile=None, patchsys=patchsys)
- except OSError:
- pass
- if out:
- tmpfile = None
- if hasattr(out, 'write'):
- tmpfile = out
- try:
- self.out = FDCapture(1, tmpfile=tmpfile, patchsys=patchsys)
- except OSError:
- pass
- if err:
- if hasattr(err, 'write'):
- tmpfile = err
- else:
- tmpfile = None
- try:
- self.err = FDCapture(2, tmpfile=tmpfile, patchsys=patchsys)
- except OSError:
- pass
-
- def start_capturing(self):
- if hasattr(self, 'in_'):
- self.in_.start()
- if hasattr(self, 'out'):
- self.out.start()
- if hasattr(self, 'err'):
- self.err.start()
-
- #def pytest_sessionfinish(self):
- # self.reset_capturings()
-
- def stop_capturing(self):
- """ return (outfile, errfile) and stop capturing. """
- outfile = errfile = None
- if hasattr(self, 'out') and not self.out.tmpfile.closed:
- outfile = self.out.done()
- if hasattr(self, 'err') and not self.err.tmpfile.closed:
- errfile = self.err.done()
- if hasattr(self, 'in_'):
- self.in_.done()
- return outfile, errfile
-
def readouterr(self):
- """ return snapshot value of stdout/stderr capturings. """
+ """ return snapshot unicode value of stdout/stderr capturings. """
return self._readsnapshot('out'), self._readsnapshot('err')
def _readsnapshot(self, name):
@@ -511,77 +411,87 @@
f.seek(0)
res = f.read()
enc = getattr(f, "encoding", None)
- if enc:
+ if enc and isinstance(res, bytes):
res = py.builtin._totext(res, enc, "replace")
f.truncate(0)
f.seek(0)
return res
-class TextCapture(TextIO):
- def __init__(self, oldout):
- super(TextCapture, self).__init__()
- self._oldout = oldout
+
+class FDCapture:
+ """ Capture IO to/from a given os-level filedescriptor. """
+
+ def __init__(self, targetfd, tmpfile=None):
+ self.targetfd = targetfd
+ try:
+ self._savefd = os.dup(self.targetfd)
+ except OSError:
+ self.start = lambda: None
+ self.done = lambda: None
+ else:
+ if tmpfile is None:
+ if targetfd == 0:
+ tmpfile = open(os.devnull, "r")
+ else:
+ tmpfile = maketmpfile()
+ self.tmpfile = tmpfile
+ if targetfd in patchsysdict:
+ self._oldsys = getattr(sys, patchsysdict[targetfd])
+
+ def start(self):
+ """ Start capturing on targetfd using memorized tmpfile. """
+ try:
+ os.fstat(self._savefd)
+ except OSError:
+ raise ValueError("saved filedescriptor not valid anymore")
+ targetfd = self.targetfd
+ os.dup2(self.tmpfile.fileno(), targetfd)
+ if hasattr(self, '_oldsys'):
+ subst = self.tmpfile if targetfd != 0 else DontReadFromInput()
+ setattr(sys, patchsysdict[targetfd], subst)
+
+ def done(self):
+ """ stop capturing, restore streams, return original capture file,
+ seeked to position zero. """
+ os.dup2(self._savefd, self.targetfd)
+ os.close(self._savefd)
+ if self.targetfd != 0:
+ self.tmpfile.seek(0)
+ if hasattr(self, '_oldsys'):
+ setattr(sys, patchsysdict[self.targetfd], self._oldsys)
+ return self.tmpfile
def writeorg(self, data):
- self._oldout.write(data)
- self._oldout.flush()
+ """ write a string to the original file descriptor
+ """
+ if py.builtin._istext(data):
+ data = data.encode("utf8") # XXX use encoding of original stream
+ os.write(self._savefd, data)
-class StdCapture(StdCaptureBase):
- """ This class allows to capture writes to sys.stdout|stderr "in-memory"
- and will raise errors on tries to read from sys.stdin. It only
- modifies sys.stdout|stderr|stdin attributes and does not
- touch underlying File Descriptors (use StdCaptureFD for that).
- """
- def __init__(self, out=True, err=True, in_=True):
- self._oldout = sys.stdout
- self._olderr = sys.stderr
- self._oldin = sys.stdin
- if out and not hasattr(out, 'file'):
- out = TextCapture(self._oldout)
- self.out = out
- if err:
- if not hasattr(err, 'write'):
- err = TextCapture(self._olderr)
- self.err = err
- self.in_ = in_
+class SysCapture:
+ def __init__(self, fd):
+ name = patchsysdict[fd]
+ self._old = getattr(sys, name)
+ self.name = name
+ if name == "stdin":
+ self.tmpfile = DontReadFromInput()
+ else:
+ self.tmpfile = TextIO()
- def start_capturing(self):
- if self.out:
- sys.stdout = self.out
- if self.err:
- sys.stderr = self.err
- if self.in_:
- sys.stdin = self.in_ = DontReadFromInput()
+ def start(self):
+ setattr(sys, self.name, self.tmpfile)
- def stop_capturing(self):
- """ return (outfile, errfile) and stop capturing. """
- outfile = errfile = None
- if self.out and not self.out.closed:
- sys.stdout = self._oldout
- outfile = self.out
- outfile.seek(0)
- if self.err and not self.err.closed:
- sys.stderr = self._olderr
- errfile = self.err
- errfile.seek(0)
- if self.in_:
- sys.stdin = self._oldin
- return outfile, errfile
+ def done(self):
+ setattr(sys, self.name, self._old)
+ if self.name != "stdin":
+ self.tmpfile.seek(0)
+ return self.tmpfile
+ def writeorg(self, data):
+ self._old.write(data)
+ self._old.flush()
- def readouterr(self):
- """ return snapshot value of stdout/stderr capturings. """
- out = err = ""
- if self.out:
- out = self.out.getvalue()
- self.out.truncate(0)
- self.out.seek(0)
- if self.err:
- err = self.err.getvalue()
- self.err.truncate(0)
- self.err.seek(0)
- return out, err
class DontReadFromInput:
diff -r b7aa02ac2440be4829e6f9f5b983f149e169c4b1 -r 080d7f3e486f387bb501369a0444e32313e59a7d testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -4,6 +4,7 @@
import os
import sys
import py
+import tempfile
import pytest
import contextlib
@@ -44,6 +45,13 @@
+def StdCaptureFD(out=True, err=True, in_=True):
+ return capture.StdCaptureBase(out, err, in_, Capture=capture.FDCapture)
+
+def StdCapture(out=True, err=True, in_=True):
+ return capture.StdCaptureBase(out, err, in_, Capture=capture.SysCapture)
+
+
class TestCaptureManager:
def test_getmethod_default_no_fd(self, testdir, monkeypatch):
config = testdir.parseconfig(testdir.tmpdir)
@@ -75,7 +83,7 @@
@needsosdup
@pytest.mark.parametrize("method", ['no', 'fd', 'sys'])
def test_capturing_basic_api(self, method):
- capouter = capture.StdCaptureFD()
+ capouter = StdCaptureFD()
old = sys.stdout, sys.stderr, sys.stdin
try:
capman = CaptureManager()
@@ -99,7 +107,7 @@
@needsosdup
def test_juggle_capturings(self, testdir):
- capouter = capture.StdCaptureFD()
+ capouter = StdCaptureFD()
try:
#config = testdir.parseconfig(testdir.tmpdir)
capman = CaptureManager()
@@ -717,7 +725,7 @@
f.close()
def test_stderr(self):
- cap = capture.FDCapture(2, patchsys=True)
+ cap = capture.FDCapture(2)
cap.start()
print_("hello", file=sys.stderr)
f = cap.done()
@@ -727,7 +735,7 @@
def test_stdin(self, tmpfile):
tmpfile.write(tobytes("3"))
tmpfile.seek(0)
- cap = capture.FDCapture(0, tmpfile=tmpfile)
+ cap = capture.FDCapture(0, tmpfile)
cap.start()
# check with os.read() directly instead of raw_input(), because
# sys.stdin itself may be redirected (as pytest now does by default)
@@ -753,7 +761,7 @@
class TestStdCapture:
def getcapture(self, **kw):
- cap = capture.StdCapture(**kw)
+ cap = StdCapture(**kw)
cap.start_capturing()
return cap
@@ -878,7 +886,7 @@
pytestmark = needsosdup
def getcapture(self, **kw):
- cap = capture.StdCaptureFD(**kw)
+ cap = StdCaptureFD(**kw)
cap.start_capturing()
return cap
@@ -899,18 +907,10 @@
def test_many(self, capfd):
with lsof_check():
for i in range(10):
- cap = capture.StdCaptureFD()
+ cap = StdCaptureFD()
cap.reset()
- at needsosdup
-def test_stdcapture_fd_tmpfile(tmpfile):
- capfd = capture.StdCaptureFD(out=tmpfile)
- os.write(1, "hello".encode("ascii"))
- os.write(2, "world".encode("ascii"))
- outf, errf = capfd.stop_capturing()
- assert outf == tmpfile
-
class TestStdCaptureFDinvalidFD:
pytestmark = needsosdup
@@ -918,7 +918,10 @@
def test_stdcapture_fd_invalid_fd(self, testdir):
testdir.makepyfile("""
import os
- from _pytest.capture import StdCaptureFD
+ from _pytest import capture
+ def StdCaptureFD(out=True, err=True, in_=True):
+ return capture.StdCaptureBase(out, err, in_,
+ Capture=capture.FDCapture)
def test_stdout():
os.close(1)
cap = StdCaptureFD(out=True, err=False, in_=False)
@@ -938,27 +941,12 @@
def test_capture_not_started_but_reset():
- capsys = capture.StdCapture()
+ capsys = StdCapture()
capsys.stop_capturing()
capsys.stop_capturing()
capsys.reset()
- at needsosdup
-def test_capture_no_sys():
- capsys = capture.StdCapture()
- try:
- cap = capture.StdCaptureFD(patchsys=False)
- cap.start_capturing()
- sys.stdout.write("hello")
- sys.stderr.write("world")
- oswritebytes(1, "1")
- oswritebytes(2, "2")
- out, err = cap.reset()
- assert out == "1"
- assert err == "2"
- finally:
- capsys.reset()
@needsosdup
@@ -966,7 +954,7 @@
def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
if not use:
tmpfile = True
- cap = capture.StdCaptureFD(out=False, err=tmpfile)
+ cap = StdCaptureFD(out=False, err=tmpfile)
try:
cap.start_capturing()
capfile = cap.err.tmpfile
@@ -977,7 +965,7 @@
assert capfile2 == capfile
- at pytest.mark.parametrize('method', ['StdCapture', 'StdCaptureFD'])
+ at pytest.mark.parametrize('method', ['SysCapture', 'FDCapture'])
def test_capturing_and_logging_fundamentals(testdir, method):
if method == "StdCaptureFD" and not hasattr(os, 'dup'):
pytest.skip("need os.dup")
@@ -986,7 +974,8 @@
import sys, os
import py, logging
from _pytest import capture
- cap = capture.%s(out=False, in_=False)
+ cap = capture.StdCaptureBase(out=False, in_=False,
+ Capture=capture.%s)
cap.start_capturing()
logging.warn("hello1")
https://bitbucket.org/hpk42/pytest/commits/9d11be8dcb85/
Changeset: 9d11be8dcb85
User: hpk42
Date: 2014-03-28 07:11:25
Summary: simplify reset/stop_capturing and fix capturing wrt to capturing simple os.write() calls
Affected #: 2 files
diff -r 080d7f3e486f387bb501369a0444e32313e59a7d -r 9d11be8dcb85d493cdb1cd0612a2808be8152fee _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -6,7 +6,7 @@
import sys
import os
-import tempfile
+from tempfile import TemporaryFile
import contextlib
import py
@@ -101,12 +101,6 @@
-def maketmpfile():
- f = py.std.tempfile.TemporaryFile()
- newf = dupfile(f, encoding="UTF-8")
- f.close()
- return newf
-
class CaptureManager:
def __init__(self, defaultmethod=None):
self._method2capture = {}
@@ -137,7 +131,7 @@
def reset_capturings(self):
for cap in self._method2capture.values():
cap.pop_outerr_to_orig()
- cap.reset()
+ cap.stop_capturing()
self._method2capture.clear()
def resumecapture_item(self, item):
@@ -274,15 +268,16 @@
class CaptureFixture:
def __init__(self, captureclass):
- self._capture = StdCaptureBase(out=True, err=True, in_=False,
- Capture=captureclass)
+ self.captureclass = captureclass
def _start(self):
+ self._capture = StdCaptureBase(out=True, err=True, in_=False,
+ Capture=self.captureclass)
self._capture.start_capturing()
def _finalize(self):
if hasattr(self, '_capture'):
- outerr = self._outerr = self._capture.reset()
+ outerr = self._outerr = self._capture.stop_capturing()
del self._capture
return outerr
@@ -355,21 +350,6 @@
if err:
self.err = Capture(2)
- def reset(self):
- """ reset sys.stdout/stderr and return captured output as strings. """
- if hasattr(self, '_reset'):
- raise ValueError("was already reset")
- self._reset = True
- outfile, errfile = self.stop_capturing()
- out, err = "", ""
- if outfile and not outfile.closed:
- out = outfile.read()
- outfile.close()
- if errfile and errfile != outfile and not errfile.closed:
- err = errfile.read()
- errfile.close()
- return out, err
-
def start_capturing(self):
if self.in_:
self.in_.start()
@@ -378,17 +358,6 @@
if self.err:
self.err.start()
- def stop_capturing(self):
- """ return (outfile, errfile) and stop capturing. """
- outfile = errfile = None
- if self.out:
- outfile = self.out.done()
- if self.err:
- errfile = self.err.done()
- if self.in_:
- self.in_.done()
- return outfile, errfile
-
def pop_outerr_to_orig(self):
""" pop current snapshot out/err capture and flush to orig streams. """
out, err = self.readouterr()
@@ -397,25 +366,27 @@
if err:
self.err.writeorg(err)
+ def stop_capturing(self):
+ """ stop capturing and reset capturing streams """
+ if hasattr(self, '_reset'):
+ raise ValueError("was already stopped")
+ self._reset = True
+ if self.out:
+ self.out.done()
+ if self.err:
+ self.err.done()
+ if self.in_:
+ self.in_.done()
+
def readouterr(self):
""" return snapshot unicode value of stdout/stderr capturings. """
return self._readsnapshot('out'), self._readsnapshot('err')
def _readsnapshot(self, name):
- try:
- f = getattr(self, name).tmpfile
- except AttributeError:
- return ''
- if f.tell() == 0:
- return ''
- f.seek(0)
- res = f.read()
- enc = getattr(f, "encoding", None)
- if enc and isinstance(res, bytes):
- res = py.builtin._totext(res, enc, "replace")
- f.truncate(0)
- f.seek(0)
- return res
+ cap = getattr(self, name, None)
+ if cap is None:
+ return ""
+ return cap.snap()
class FDCapture:
@@ -433,11 +404,16 @@
if targetfd == 0:
tmpfile = open(os.devnull, "r")
else:
- tmpfile = maketmpfile()
+ f = TemporaryFile()
+ with f:
+ tmpfile = dupfile(f, encoding="UTF-8")
self.tmpfile = tmpfile
if targetfd in patchsysdict:
self._oldsys = getattr(sys, patchsysdict[targetfd])
+ def __repr__(self):
+ return "<FDCapture %s oldfd=%s>" % (self.targetfd, self._savefd)
+
def start(self):
""" Start capturing on targetfd using memorized tmpfile. """
try:
@@ -450,16 +426,26 @@
subst = self.tmpfile if targetfd != 0 else DontReadFromInput()
setattr(sys, patchsysdict[targetfd], subst)
+ def snap(self):
+ f = self.tmpfile
+ f.seek(0)
+ res = f.read()
+ if res:
+ enc = getattr(f, "encoding", None)
+ if enc and isinstance(res, bytes):
+ res = py.builtin._totext(res, enc, "replace")
+ f.truncate(0)
+ f.seek(0)
+ return res
+
def done(self):
""" stop capturing, restore streams, return original capture file,
seeked to position zero. """
os.dup2(self._savefd, self.targetfd)
os.close(self._savefd)
- if self.targetfd != 0:
- self.tmpfile.seek(0)
if hasattr(self, '_oldsys'):
setattr(sys, patchsysdict[self.targetfd], self._oldsys)
- return self.tmpfile
+ self.tmpfile.close()
def writeorg(self, data):
""" write a string to the original file descriptor
@@ -482,18 +468,22 @@
def start(self):
setattr(sys, self.name, self.tmpfile)
+ def snap(self):
+ f = self.tmpfile
+ res = f.getvalue()
+ f.truncate(0)
+ f.seek(0)
+ return res
+
def done(self):
setattr(sys, self.name, self._old)
- if self.name != "stdin":
- self.tmpfile.seek(0)
- return self.tmpfile
+ self.tmpfile.close()
def writeorg(self, data):
self._old.write(data)
self._old.flush()
-
class DontReadFromInput:
"""Temporary stub class. Ideally when stdin is accessed, the
capturing should be turned off, with possibly all data captured
diff -r 080d7f3e486f387bb501369a0444e32313e59a7d -r 9d11be8dcb85d493cdb1cd0612a2808be8152fee testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -103,7 +103,7 @@
assert not out and not err
capman.reset_capturings()
finally:
- capouter.reset()
+ capouter.stop_capturing()
@needsosdup
def test_juggle_capturings(self, testdir):
@@ -127,7 +127,7 @@
finally:
capman.reset_capturings()
finally:
- capouter.reset()
+ capouter.stop_capturing()
@pytest.mark.parametrize("method", ['fd', 'sys'])
@@ -696,17 +696,15 @@
cap = capture.FDCapture(fd)
data = tobytes("hello")
os.write(fd, data)
- f = cap.done()
- s = f.read()
- f.close()
+ s = cap.snap()
+ cap.done()
assert not s
cap = capture.FDCapture(fd)
cap.start()
os.write(fd, data)
- f = cap.done()
- s = f.read()
+ s = cap.snap()
+ cap.done()
assert s == "hello"
- f.close()
def test_simple_many(self, tmpfile):
for i in range(10):
@@ -720,16 +718,15 @@
def test_simple_fail_second_start(self, tmpfile):
fd = tmpfile.fileno()
cap = capture.FDCapture(fd)
- f = cap.done()
+ cap.done()
pytest.raises(ValueError, cap.start)
- f.close()
def test_stderr(self):
cap = capture.FDCapture(2)
cap.start()
print_("hello", file=sys.stderr)
- f = cap.done()
- s = f.read()
+ s = cap.snap()
+ cap.done()
assert s == "hello\n"
def test_stdin(self, tmpfile):
@@ -752,8 +749,8 @@
cap.writeorg(data2)
finally:
tmpfile.close()
- f = cap.done()
- scap = f.read()
+ scap = cap.snap()
+ cap.done()
assert scap == totext(data1)
stmp = open(tmpfile.name, 'rb').read()
assert stmp == data2
@@ -769,17 +766,17 @@
cap = self.getcapture()
sys.stdout.write("hello")
sys.stderr.write("world")
- outfile, errfile = cap.stop_capturing()
- s = outfile.read()
- assert s == "hello"
- s = errfile.read()
- assert s == "world"
+ out, err = cap.readouterr()
+ cap.stop_capturing()
+ assert out == "hello"
+ assert err == "world"
def test_capturing_reset_simple(self):
cap = self.getcapture()
print("hello world")
sys.stderr.write("hello error\n")
- out, err = cap.reset()
+ out, err = cap.readouterr()
+ cap.stop_capturing()
assert out == "hello world\n"
assert err == "hello error\n"
@@ -792,8 +789,9 @@
assert out == "hello world\n"
assert err == "hello error\n"
sys.stderr.write("error2")
+ out, err = cap.readouterr()
finally:
- out, err = cap.reset()
+ cap.stop_capturing()
assert err == "error2"
def test_capturing_readouterr_unicode(self):
@@ -802,7 +800,7 @@
print ("hx\xc4\x85\xc4\x87")
out, err = cap.readouterr()
finally:
- cap.reset()
+ cap.stop_capturing()
assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
@pytest.mark.skipif('sys.version_info >= (3,)',
@@ -813,13 +811,14 @@
print('\xa6')
out, err = cap.readouterr()
assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
- cap.reset()
+ cap.stop_capturing()
def test_reset_twice_error(self):
cap = self.getcapture()
print ("hello")
- out, err = cap.reset()
- pytest.raises(ValueError, cap.reset)
+ out, err = cap.readouterr()
+ cap.stop_capturing()
+ pytest.raises(ValueError, cap.stop_capturing)
assert out == "hello\n"
assert not err
@@ -833,7 +832,8 @@
sys.stderr = capture.TextIO()
print ("not seen")
sys.stderr.write("not seen\n")
- out, err = cap.reset()
+ out, err = cap.readouterr()
+ cap.stop_capturing()
assert out == "hello"
assert err == "world"
assert sys.stdout == oldout
@@ -844,8 +844,10 @@
print ("cap1")
cap2 = self.getcapture()
print ("cap2")
- out2, err2 = cap2.reset()
- out1, err1 = cap1.reset()
+ out2, err2 = cap2.readouterr()
+ out1, err1 = cap1.readouterr()
+ cap2.stop_capturing()
+ cap1.stop_capturing()
assert out1 == "cap1\n"
assert out2 == "cap2\n"
@@ -853,7 +855,8 @@
cap = self.getcapture(out=True, err=False)
sys.stdout.write("hello")
sys.stderr.write("world")
- out, err = cap.reset()
+ out, err = cap.readouterr()
+ cap.stop_capturing()
assert out == "hello"
assert not err
@@ -861,7 +864,8 @@
cap = self.getcapture(out=False, err=True)
sys.stdout.write("hello")
sys.stderr.write("world")
- out, err = cap.reset()
+ out, err = cap.readouterr()
+ cap.stop_capturing()
assert err == "world"
assert not out
@@ -869,7 +873,7 @@
old = sys.stdin
cap = self.getcapture(in_=True)
newstdin = sys.stdin
- out, err = cap.reset()
+ cap.stop_capturing()
assert newstdin != sys.stdin
assert sys.stdin is old
@@ -879,7 +883,7 @@
print ("XXX mechanisms")
cap = self.getcapture()
pytest.raises(IOError, "sys.stdin.read()")
- out, err = cap.reset()
+ cap.stop_capturing()
class TestStdCaptureFD(TestStdCapture):
@@ -890,6 +894,20 @@
cap.start_capturing()
return cap
+ def test_simple_only_fd(self, testdir):
+ testdir.makepyfile("""
+ import os
+ def test_x():
+ os.write(1, "hello\\n".encode("ascii"))
+ assert 0
+ """)
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines("""
+ *test_x*
+ *assert 0*
+ *Captured stdout*
+ """)
+
def test_intermingling(self):
cap = self.getcapture()
oswritebytes(1, "1")
@@ -900,7 +918,8 @@
sys.stderr.write("b")
sys.stderr.flush()
oswritebytes(2, "c")
- out, err = cap.reset()
+ out, err = cap.readouterr()
+ cap.stop_capturing()
assert out == "123"
assert err == "abc"
@@ -908,7 +927,7 @@
with lsof_check():
for i in range(10):
cap = StdCaptureFD()
- cap.reset()
+ cap.stop_capturing()
@@ -943,10 +962,6 @@
def test_capture_not_started_but_reset():
capsys = StdCapture()
capsys.stop_capturing()
- capsys.stop_capturing()
- capsys.reset()
-
-
@needsosdup
@@ -960,7 +975,7 @@
capfile = cap.err.tmpfile
cap.readouterr()
finally:
- cap.reset()
+ cap.stop_capturing()
capfile2 = cap.err.tmpfile
assert capfile2 == capfile
https://bitbucket.org/hpk42/pytest/commits/f9dfeecab061/
Changeset: f9dfeecab061
User: hpk42
Date: 2014-03-28 07:13:08
Summary: simplify capturing funcarg handling
Affected #: 1 file
diff -r 9d11be8dcb85d493cdb1cd0612a2808be8152fee -r f9dfeecab0611e3f4a8f0f369ad0cf14beba3820 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -155,29 +155,23 @@
def suspendcapture(self, item=None):
self.deactivate_funcargs()
- if hasattr(self, '_capturing'):
- method = self._capturing
- del self._capturing
+ method = self.__dict__.pop("_capturing", None)
+ if method is not None:
cap = self._method2capture.get(method)
if cap is not None:
return cap.readouterr()
return "", ""
def activate_funcargs(self, pyfuncitem):
- funcargs = getattr(pyfuncitem, "funcargs", None)
- if funcargs is not None:
- for name, capfuncarg in funcargs.items():
- if name in ('capsys', 'capfd'):
- assert not hasattr(self, '_capturing_funcarg')
- self._capturing_funcarg = capfuncarg
- capfuncarg._start()
+ capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
+ if capfuncarg is not None:
+ capfuncarg._start()
+ self._capfuncarg = capfuncarg
def deactivate_funcargs(self):
- capturing_funcarg = getattr(self, '_capturing_funcarg', None)
- if capturing_funcarg:
- outerr = capturing_funcarg._finalize()
- del self._capturing_funcarg
- return outerr
+ capfuncarg = self.__dict__.pop("_capfuncarg", None)
+ if capfuncarg is not None:
+ capfuncarg.close()
@pytest.mark.hookwrapper
def pytest_make_collect_report(self, __multicall__, collector):
@@ -210,7 +204,9 @@
@pytest.mark.hookwrapper
def pytest_runtest_call(self, item):
with self.item_capture_wrapper(item, "call"):
+ self.activate_funcargs(item)
yield
+ #self.deactivate_funcargs() called from ctx's suspendcapture()
@pytest.mark.hookwrapper
def pytest_runtest_teardown(self, item):
@@ -228,17 +224,8 @@
@contextlib.contextmanager
def item_capture_wrapper(self, item, when):
self.resumecapture_item(item)
- if when == "call":
- self.activate_funcargs(item)
- yield
- funcarg_outerr = self.deactivate_funcargs()
- else:
- yield
- funcarg_outerr = None
+ yield
out, err = self.suspendcapture(item)
- if funcarg_outerr is not None:
- out += funcarg_outerr[0]
- err += funcarg_outerr[1]
item.add_report_section(when, "out", out)
item.add_report_section(when, "err", err)
@@ -252,7 +239,8 @@
"""
if "capfd" in request._funcargs:
raise request.raiseerror(error_capsysfderror)
- return CaptureFixture(SysCapture)
+ request.node._capfuncarg = c = CaptureFixture(SysCapture)
+ return c
def pytest_funcarg__capfd(request):
"""enables capturing of writes to file descriptors 1 and 2 and makes
@@ -263,7 +251,8 @@
request.raiseerror(error_capsysfderror)
if not hasattr(os, 'dup'):
pytest.skip("capfd funcarg needs os.dup")
- return CaptureFixture(FDCapture)
+ request.node._capfuncarg = c = CaptureFixture(FDCapture)
+ return c
class CaptureFixture:
@@ -275,21 +264,17 @@
Capture=self.captureclass)
self._capture.start_capturing()
- def _finalize(self):
- if hasattr(self, '_capture'):
- outerr = self._outerr = self._capture.stop_capturing()
- del self._capture
- return outerr
+ def close(self):
+ cap = self.__dict__.pop("_capture", None)
+ if cap is not None:
+ cap.pop_outerr_to_orig()
+ cap.stop_capturing()
def readouterr(self):
try:
return self._capture.readouterr()
except AttributeError:
- return self._outerr
-
- def close(self):
- self._finalize()
-
+ return "", ""
def dupfile(f, mode=None, buffering=0, raising=False, encoding=None):
@@ -448,8 +433,7 @@
self.tmpfile.close()
def writeorg(self, data):
- """ write a string to the original file descriptor
- """
+ """ write to original file descriptor. """
if py.builtin._istext(data):
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self._savefd, data)
https://bitbucket.org/hpk42/pytest/commits/d3ebd61be2a1/
Changeset: d3ebd61be2a1
User: hpk42
Date: 2014-03-28 07:55:07
Summary: simplify some capturing tests
Affected #: 1 file
diff -r f9dfeecab0611e3f4a8f0f369ad0cf14beba3820 -r d3ebd61be2a14ea1bceb3acb7bb7caee98cae914 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -757,32 +757,35 @@
class TestStdCapture:
+ captureclass = staticmethod(StdCapture)
+
+ @contextlib.contextmanager
def getcapture(self, **kw):
- cap = StdCapture(**kw)
+ cap = self.__class__.captureclass(**kw)
cap.start_capturing()
- return cap
+ try:
+ yield cap
+ finally:
+ cap.stop_capturing()
def test_capturing_done_simple(self):
- cap = self.getcapture()
- sys.stdout.write("hello")
- sys.stderr.write("world")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture() as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ out, err = cap.readouterr()
assert out == "hello"
assert err == "world"
def test_capturing_reset_simple(self):
- cap = self.getcapture()
- print("hello world")
- sys.stderr.write("hello error\n")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture() as cap:
+ print("hello world")
+ sys.stderr.write("hello error\n")
+ out, err = cap.readouterr()
assert out == "hello world\n"
assert err == "hello error\n"
def test_capturing_readouterr(self):
- cap = self.getcapture()
- try:
+ with self.getcapture() as cap:
print ("hello world")
sys.stderr.write("hello error\n")
out, err = cap.readouterr()
@@ -790,34 +793,27 @@
assert err == "hello error\n"
sys.stderr.write("error2")
out, err = cap.readouterr()
- finally:
- cap.stop_capturing()
assert err == "error2"
def test_capturing_readouterr_unicode(self):
- cap = self.getcapture()
- try:
+ with self.getcapture() as cap:
print ("hx\xc4\x85\xc4\x87")
out, err = cap.readouterr()
- finally:
- cap.stop_capturing()
assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
@pytest.mark.skipif('sys.version_info >= (3,)',
reason='text output different for bytes on python3')
def test_capturing_readouterr_decode_error_handling(self):
- cap = self.getcapture()
- # triggered a internal error in pytest
- print('\xa6')
- out, err = cap.readouterr()
+ with self.getcapture() as cap:
+ # triggered a internal error in pytest
+ print('\xa6')
+ out, err = cap.readouterr()
assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
- cap.stop_capturing()
def test_reset_twice_error(self):
- cap = self.getcapture()
- print ("hello")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture() as cap:
+ print ("hello")
+ out, err = cap.readouterr()
pytest.raises(ValueError, cap.stop_capturing)
assert out == "hello\n"
assert not err
@@ -825,55 +821,49 @@
def test_capturing_modify_sysouterr_in_between(self):
oldout = sys.stdout
olderr = sys.stderr
- cap = self.getcapture()
- sys.stdout.write("hello")
- sys.stderr.write("world")
- sys.stdout = capture.TextIO()
- sys.stderr = capture.TextIO()
- print ("not seen")
- sys.stderr.write("not seen\n")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture() as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ sys.stdout = capture.TextIO()
+ sys.stderr = capture.TextIO()
+ print ("not seen")
+ sys.stderr.write("not seen\n")
+ out, err = cap.readouterr()
assert out == "hello"
assert err == "world"
assert sys.stdout == oldout
assert sys.stderr == olderr
def test_capturing_error_recursive(self):
- cap1 = self.getcapture()
- print ("cap1")
- cap2 = self.getcapture()
- print ("cap2")
- out2, err2 = cap2.readouterr()
- out1, err1 = cap1.readouterr()
- cap2.stop_capturing()
- cap1.stop_capturing()
+ with self.getcapture() as cap1:
+ print ("cap1")
+ with self.getcapture() as cap2:
+ print ("cap2")
+ out2, err2 = cap2.readouterr()
+ out1, err1 = cap1.readouterr()
assert out1 == "cap1\n"
assert out2 == "cap2\n"
def test_just_out_capture(self):
- cap = self.getcapture(out=True, err=False)
- sys.stdout.write("hello")
- sys.stderr.write("world")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture(out=True, err=False) as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ out, err = cap.readouterr()
assert out == "hello"
assert not err
def test_just_err_capture(self):
- cap = self.getcapture(out=False, err=True)
- sys.stdout.write("hello")
- sys.stderr.write("world")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture(out=False, err=True) as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ out, err = cap.readouterr()
assert err == "world"
assert not out
def test_stdin_restored(self):
old = sys.stdin
- cap = self.getcapture(in_=True)
- newstdin = sys.stdin
- cap.stop_capturing()
+ with self.getcapture(in_=True) as cap:
+ newstdin = sys.stdin
assert newstdin != sys.stdin
assert sys.stdin is old
@@ -881,18 +871,13 @@
print ("XXX this test may well hang instead of crashing")
print ("XXX which indicates an error in the underlying capturing")
print ("XXX mechanisms")
- cap = self.getcapture()
- pytest.raises(IOError, "sys.stdin.read()")
- cap.stop_capturing()
+ with self.getcapture() as cap:
+ pytest.raises(IOError, "sys.stdin.read()")
class TestStdCaptureFD(TestStdCapture):
pytestmark = needsosdup
-
- def getcapture(self, **kw):
- cap = StdCaptureFD(**kw)
- cap.start_capturing()
- return cap
+ captureclass = staticmethod(StdCaptureFD)
def test_simple_only_fd(self, testdir):
testdir.makepyfile("""
@@ -909,17 +894,16 @@
""")
def test_intermingling(self):
- cap = self.getcapture()
- oswritebytes(1, "1")
- sys.stdout.write(str(2))
- sys.stdout.flush()
- oswritebytes(1, "3")
- oswritebytes(2, "a")
- sys.stderr.write("b")
- sys.stderr.flush()
- oswritebytes(2, "c")
- out, err = cap.readouterr()
- cap.stop_capturing()
+ with self.getcapture() as cap:
+ oswritebytes(1, "1")
+ sys.stdout.write(str(2))
+ sys.stdout.flush()
+ oswritebytes(1, "3")
+ oswritebytes(2, "a")
+ sys.stderr.write("b")
+ sys.stderr.flush()
+ oswritebytes(2, "c")
+ out, err = cap.readouterr()
assert out == "123"
assert err == "abc"
@@ -930,7 +914,6 @@
cap.stop_capturing()
-
class TestStdCaptureFDinvalidFD:
pytestmark = needsosdup
https://bitbucket.org/hpk42/pytest/commits/756cdf331fc8/
Changeset: 756cdf331fc8
User: hpk42
Date: 2014-03-28 09:27:44
Summary: merge
Affected #: 16 files
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -1,12 +1,13 @@
"""
- per-test stdout/stderr capturing mechanisms,
- ``capsys`` and ``capfd`` function arguments.
+per-test stdout/stderr capturing mechanism.
+
"""
-# note: py.io capture was where copied from
-# pylib 1.4.20.dev2 (rev 13d9af95547e)
+from __future__ import with_statement
+
import sys
import os
-import tempfile
+from tempfile import TemporaryFile
+import contextlib
import py
import pytest
@@ -58,8 +59,18 @@
method = "fd"
if method == "fd" and not hasattr(os, "dup"):
method = "sys"
+ pluginmanager = early_config.pluginmanager
+ if method != "no":
+ try:
+ sys.stdout.fileno()
+ except Exception:
+ dupped_stdout = sys.stdout
+ else:
+ dupped_stdout = dupfile(sys.stdout, buffering=1)
+ pluginmanager.register(dupped_stdout, "dupped_stdout")
+ #pluginmanager.add_shutdown(dupped_stdout.close)
capman = CaptureManager(method)
- early_config.pluginmanager.register(capman, "capturemanager")
+ pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
def teardown():
@@ -68,13 +79,13 @@
except ValueError:
pass
- early_config.pluginmanager.add_shutdown(teardown)
+ pluginmanager.add_shutdown(teardown)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
if "logging" in sys.modules:
sys.modules["logging"].raiseExceptions = False
- early_config.pluginmanager.add_shutdown(silence_logging_at_shutdown)
+ pluginmanager.add_shutdown(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.resumecapture()
@@ -89,53 +100,19 @@
raise
-def addouterr(rep, outerr):
- for secname, content in zip(["out", "err"], outerr):
- if content:
- rep.sections.append(("Captured std%s" % secname, content))
-
-
-class NoCapture:
- def startall(self):
- pass
-
- def resume(self):
- pass
-
- def reset(self):
- pass
-
- def suspend(self):
- return "", ""
-
class CaptureManager:
def __init__(self, defaultmethod=None):
self._method2capture = {}
self._defaultmethod = defaultmethod
- def _maketempfile(self):
- f = py.std.tempfile.TemporaryFile()
- newf = dupfile(f, encoding="UTF-8")
- f.close()
- return newf
-
- def _makestringio(self):
- return TextIO()
-
def _getcapture(self, method):
if method == "fd":
- return StdCaptureFD(
- out=self._maketempfile(),
- err=self._maketempfile(),
- )
+ return StdCaptureBase(out=True, err=True, Capture=FDCapture)
elif method == "sys":
- return StdCapture(
- out=self._makestringio(),
- err=self._makestringio(),
- )
+ return StdCaptureBase(out=True, err=True, Capture=SysCapture)
elif method == "no":
- return NoCapture()
+ return StdCaptureBase(out=False, err=False, in_=False)
else:
raise ValueError("unknown capturing method: %r" % method)
@@ -153,12 +130,12 @@
def reset_capturings(self):
for cap in self._method2capture.values():
- cap.reset()
+ cap.pop_outerr_to_orig()
+ cap.stop_capturing()
+ self._method2capture.clear()
def resumecapture_item(self, item):
method = self._getmethod(item.config, item.fspath)
- if not hasattr(item, 'outerr'):
- item.outerr = ('', '') # we accumulate outerr on the item
return self.resumecapture(method)
def resumecapture(self, method=None):
@@ -172,87 +149,85 @@
self._capturing = method
if cap is None:
self._method2capture[method] = cap = self._getcapture(method)
- cap.startall()
+ cap.start_capturing()
else:
- cap.resume()
+ cap.pop_outerr_to_orig()
def suspendcapture(self, item=None):
self.deactivate_funcargs()
- if hasattr(self, '_capturing'):
- method = self._capturing
+ method = self.__dict__.pop("_capturing", None)
+ if method is not None:
cap = self._method2capture.get(method)
if cap is not None:
- outerr = cap.suspend()
- del self._capturing
- if item:
- outerr = (item.outerr[0] + outerr[0],
- item.outerr[1] + outerr[1])
- return outerr
- if hasattr(item, 'outerr'):
- return item.outerr
+ return cap.readouterr()
return "", ""
def activate_funcargs(self, pyfuncitem):
- funcargs = getattr(pyfuncitem, "funcargs", None)
- if funcargs is not None:
- for name, capfuncarg in funcargs.items():
- if name in ('capsys', 'capfd'):
- assert not hasattr(self, '_capturing_funcarg')
- self._capturing_funcarg = capfuncarg
- capfuncarg._start()
+ capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
+ if capfuncarg is not None:
+ capfuncarg._start()
+ self._capfuncarg = capfuncarg
def deactivate_funcargs(self):
- capturing_funcarg = getattr(self, '_capturing_funcarg', None)
- if capturing_funcarg:
- outerr = capturing_funcarg._finalize()
- del self._capturing_funcarg
- return outerr
+ capfuncarg = self.__dict__.pop("_capfuncarg", None)
+ if capfuncarg is not None:
+ capfuncarg.close()
+ @pytest.mark.hookwrapper
def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath)
try:
self.resumecapture(method)
except ValueError:
+ yield
# recursive collect, XXX refactor capturing
# to allow for more lightweight recursive capturing
return
- try:
- rep = __multicall__.execute()
- finally:
- outerr = self.suspendcapture()
- addouterr(rep, outerr)
- return rep
+ yield
+ out, err = self.suspendcapture()
+ # XXX getting the report from the ongoing hook call is a bit
+ # of a hack. We need to think about capturing during collection
+ # and find out if it's really needed fine-grained (per
+ # collector).
+ if __multicall__.results:
+ rep = __multicall__.results[0]
+ if out:
+ rep.sections.append(("Captured stdout", out))
+ if err:
+ rep.sections.append(("Captured stderr", err))
+
+ @pytest.mark.hookwrapper
+ def pytest_runtest_setup(self, item):
+ with self.item_capture_wrapper(item, "setup"):
+ yield
+
+ @pytest.mark.hookwrapper
+ def pytest_runtest_call(self, item):
+ with self.item_capture_wrapper(item, "call"):
+ self.activate_funcargs(item)
+ yield
+ #self.deactivate_funcargs() called from ctx's suspendcapture()
+
+ @pytest.mark.hookwrapper
+ def pytest_runtest_teardown(self, item):
+ with self.item_capture_wrapper(item, "teardown"):
+ yield
@pytest.mark.tryfirst
- def pytest_runtest_setup(self, item):
- self.resumecapture_item(item)
+ def pytest_keyboard_interrupt(self, excinfo):
+ self.reset_capturings()
@pytest.mark.tryfirst
- def pytest_runtest_call(self, item):
+ def pytest_internalerror(self, excinfo):
+ self.reset_capturings()
+
+ @contextlib.contextmanager
+ def item_capture_wrapper(self, item, when):
self.resumecapture_item(item)
- self.activate_funcargs(item)
-
- @pytest.mark.tryfirst
- def pytest_runtest_teardown(self, item):
- self.resumecapture_item(item)
-
- def pytest_keyboard_interrupt(self, excinfo):
- if hasattr(self, '_capturing'):
- self.suspendcapture()
-
- @pytest.mark.tryfirst
- def pytest_runtest_makereport(self, __multicall__, item, call):
- funcarg_outerr = self.deactivate_funcargs()
- rep = __multicall__.execute()
- outerr = self.suspendcapture(item)
- if funcarg_outerr is not None:
- outerr = (outerr[0] + funcarg_outerr[0],
- outerr[1] + funcarg_outerr[1])
- addouterr(rep, outerr)
- if not rep.passed or rep.when == "teardown":
- outerr = ('', '')
- item.outerr = outerr
- return rep
+ yield
+ out, err = self.suspendcapture(item)
+ item.add_report_section(when, "out", out)
+ item.add_report_section(when, "err", err)
error_capsysfderror = "cannot use capsys and capfd at the same time"
@@ -264,8 +239,8 @@
"""
if "capfd" in request._funcargs:
raise request.raiseerror(error_capsysfderror)
- return CaptureFixture(StdCapture)
-
+ request.node._capfuncarg = c = CaptureFixture(SysCapture)
+ return c
def pytest_funcarg__capfd(request):
"""enables capturing of writes to file descriptors 1 and 2 and makes
@@ -276,89 +251,30 @@
request.raiseerror(error_capsysfderror)
if not hasattr(os, 'dup'):
pytest.skip("capfd funcarg needs os.dup")
- return CaptureFixture(StdCaptureFD)
+ request.node._capfuncarg = c = CaptureFixture(FDCapture)
+ return c
class CaptureFixture:
def __init__(self, captureclass):
- self._capture = captureclass()
+ self.captureclass = captureclass
def _start(self):
- self._capture.startall()
+ self._capture = StdCaptureBase(out=True, err=True, in_=False,
+ Capture=self.captureclass)
+ self._capture.start_capturing()
- def _finalize(self):
- if hasattr(self, '_capture'):
- outerr = self._outerr = self._capture.reset()
- del self._capture
- return outerr
+ def close(self):
+ cap = self.__dict__.pop("_capture", None)
+ if cap is not None:
+ cap.pop_outerr_to_orig()
+ cap.stop_capturing()
def readouterr(self):
try:
return self._capture.readouterr()
except AttributeError:
- return self._outerr
-
- def close(self):
- self._finalize()
-
-
-class FDCapture:
- """ Capture IO to/from a given os-level filedescriptor. """
-
- def __init__(self, targetfd, tmpfile=None, patchsys=False):
- """ save targetfd descriptor, and open a new
- temporary file there. If no tmpfile is
- specified a tempfile.Tempfile() will be opened
- in text mode.
- """
- self.targetfd = targetfd
- if tmpfile is None and targetfd != 0:
- f = tempfile.TemporaryFile('wb+')
- tmpfile = dupfile(f, encoding="UTF-8")
- f.close()
- self.tmpfile = tmpfile
- self._savefd = os.dup(self.targetfd)
- if patchsys:
- self._oldsys = getattr(sys, patchsysdict[targetfd])
-
- def start(self):
- try:
- os.fstat(self._savefd)
- except OSError:
- raise ValueError(
- "saved filedescriptor not valid, "
- "did you call start() twice?")
- if self.targetfd == 0 and not self.tmpfile:
- fd = os.open(os.devnull, os.O_RDONLY)
- os.dup2(fd, 0)
- os.close(fd)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], DontReadFromInput())
- else:
- os.dup2(self.tmpfile.fileno(), self.targetfd)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], self.tmpfile)
-
- def done(self):
- """ unpatch and clean up, returns the self.tmpfile (file object)
- """
- os.dup2(self._savefd, self.targetfd)
- os.close(self._savefd)
- if self.targetfd != 0:
- self.tmpfile.seek(0)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], self._oldsys)
- return self.tmpfile
-
- def writeorg(self, data):
- """ write a string to the original file descriptor
- """
- tempfp = tempfile.TemporaryFile()
- try:
- os.dup2(self._savefd, tempfp.fileno())
- tempfp.write(data)
- finally:
- tempfp.close()
+ return "", ""
def dupfile(f, mode=None, buffering=0, raising=False, encoding=None):
@@ -408,185 +324,148 @@
return getattr(self._stream, name)
-class Capture(object):
- def reset(self):
- """ reset sys.stdout/stderr and return captured output as strings. """
- if hasattr(self, '_reset'):
- raise ValueError("was already reset")
- self._reset = True
- outfile, errfile = self.done(save=False)
- out, err = "", ""
- if outfile and not outfile.closed:
- out = outfile.read()
- outfile.close()
- if errfile and errfile != outfile and not errfile.closed:
- err = errfile.read()
- errfile.close()
- return out, err
+class StdCaptureBase(object):
+ out = err = in_ = None
- def suspend(self):
- """ return current snapshot captures, memorize tempfiles. """
- outerr = self.readouterr()
- outfile, errfile = self.done()
- return outerr
+ def __init__(self, out=True, err=True, in_=True, Capture=None):
+ if in_:
+ self.in_ = Capture(0)
+ if out:
+ self.out = Capture(1)
+ if err:
+ self.err = Capture(2)
-
-class StdCaptureFD(Capture):
- """ This class allows to capture writes to FD1 and FD2
- and may connect a NULL file to FD0 (and prevent
- reads from sys.stdin). If any of the 0,1,2 file descriptors
- is invalid it will not be captured.
- """
- def __init__(self, out=True, err=True, in_=True, patchsys=True):
- self._options = {
- "out": out,
- "err": err,
- "in_": in_,
- "patchsys": patchsys,
- }
- self._save()
-
- def _save(self):
- in_ = self._options['in_']
- out = self._options['out']
- err = self._options['err']
- patchsys = self._options['patchsys']
- if in_:
- try:
- self.in_ = FDCapture(
- 0, tmpfile=None,
- patchsys=patchsys)
- except OSError:
- pass
- if out:
- tmpfile = None
- if hasattr(out, 'write'):
- tmpfile = out
- try:
- self.out = FDCapture(
- 1, tmpfile=tmpfile,
- patchsys=patchsys)
- self._options['out'] = self.out.tmpfile
- except OSError:
- pass
- if err:
- if hasattr(err, 'write'):
- tmpfile = err
- else:
- tmpfile = None
- try:
- self.err = FDCapture(
- 2, tmpfile=tmpfile,
- patchsys=patchsys)
- self._options['err'] = self.err.tmpfile
- except OSError:
- pass
-
- def startall(self):
- if hasattr(self, 'in_'):
+ def start_capturing(self):
+ if self.in_:
self.in_.start()
- if hasattr(self, 'out'):
+ if self.out:
self.out.start()
- if hasattr(self, 'err'):
+ if self.err:
self.err.start()
- def resume(self):
- """ resume capturing with original temp files. """
- self.startall()
+ def pop_outerr_to_orig(self):
+ """ pop current snapshot out/err capture and flush to orig streams. """
+ out, err = self.readouterr()
+ if out:
+ self.out.writeorg(out)
+ if err:
+ self.err.writeorg(err)
- def done(self, save=True):
- """ return (outfile, errfile) and stop capturing. """
- outfile = errfile = None
- if hasattr(self, 'out') and not self.out.tmpfile.closed:
- outfile = self.out.done()
- if hasattr(self, 'err') and not self.err.tmpfile.closed:
- errfile = self.err.done()
- if hasattr(self, 'in_'):
+ def stop_capturing(self):
+ """ stop capturing and reset capturing streams """
+ if hasattr(self, '_reset'):
+ raise ValueError("was already stopped")
+ self._reset = True
+ if self.out:
+ self.out.done()
+ if self.err:
+ self.err.done()
+ if self.in_:
self.in_.done()
- if save:
- self._save()
- return outfile, errfile
def readouterr(self):
- """ return snapshot value of stdout/stderr capturings. """
- out = self._readsnapshot('out')
- err = self._readsnapshot('err')
- return out, err
+ """ return snapshot unicode value of stdout/stderr capturings. """
+ return self._readsnapshot('out'), self._readsnapshot('err')
def _readsnapshot(self, name):
- if hasattr(self, name):
- f = getattr(self, name).tmpfile
+ cap = getattr(self, name, None)
+ if cap is None:
+ return ""
+ return cap.snap()
+
+
+class FDCapture:
+ """ Capture IO to/from a given os-level filedescriptor. """
+
+ def __init__(self, targetfd, tmpfile=None):
+ self.targetfd = targetfd
+ try:
+ self._savefd = os.dup(self.targetfd)
+ except OSError:
+ self.start = lambda: None
+ self.done = lambda: None
else:
- return ''
+ if tmpfile is None:
+ if targetfd == 0:
+ tmpfile = open(os.devnull, "r")
+ else:
+ f = TemporaryFile()
+ with f:
+ tmpfile = dupfile(f, encoding="UTF-8")
+ self.tmpfile = tmpfile
+ if targetfd in patchsysdict:
+ self._oldsys = getattr(sys, patchsysdict[targetfd])
+ def __repr__(self):
+ return "<FDCapture %s oldfd=%s>" % (self.targetfd, self._savefd)
+
+ def start(self):
+ """ Start capturing on targetfd using memorized tmpfile. """
+ try:
+ os.fstat(self._savefd)
+ except OSError:
+ raise ValueError("saved filedescriptor not valid anymore")
+ targetfd = self.targetfd
+ os.dup2(self.tmpfile.fileno(), targetfd)
+ if hasattr(self, '_oldsys'):
+ subst = self.tmpfile if targetfd != 0 else DontReadFromInput()
+ setattr(sys, patchsysdict[targetfd], subst)
+
+ def snap(self):
+ f = self.tmpfile
f.seek(0)
res = f.read()
- enc = getattr(f, "encoding", None)
- if enc:
- res = py.builtin._totext(res, enc, "replace")
+ if res:
+ enc = getattr(f, "encoding", None)
+ if enc and isinstance(res, bytes):
+ res = py.builtin._totext(res, enc, "replace")
+ f.truncate(0)
+ f.seek(0)
+ return res
+
+ def done(self):
+ """ stop capturing, restore streams, return original capture file,
+ seeked to position zero. """
+ os.dup2(self._savefd, self.targetfd)
+ os.close(self._savefd)
+ if hasattr(self, '_oldsys'):
+ setattr(sys, patchsysdict[self.targetfd], self._oldsys)
+ self.tmpfile.close()
+
+ def writeorg(self, data):
+ """ write to original file descriptor. """
+ if py.builtin._istext(data):
+ data = data.encode("utf8") # XXX use encoding of original stream
+ os.write(self._savefd, data)
+
+
+class SysCapture:
+ def __init__(self, fd):
+ name = patchsysdict[fd]
+ self._old = getattr(sys, name)
+ self.name = name
+ if name == "stdin":
+ self.tmpfile = DontReadFromInput()
+ else:
+ self.tmpfile = TextIO()
+
+ def start(self):
+ setattr(sys, self.name, self.tmpfile)
+
+ def snap(self):
+ f = self.tmpfile
+ res = f.getvalue()
f.truncate(0)
f.seek(0)
return res
+ def done(self):
+ setattr(sys, self.name, self._old)
+ self.tmpfile.close()
-class StdCapture(Capture):
- """ This class allows to capture writes to sys.stdout|stderr "in-memory"
- and will raise errors on tries to read from sys.stdin. It only
- modifies sys.stdout|stderr|stdin attributes and does not
- touch underlying File Descriptors (use StdCaptureFD for that).
- """
- def __init__(self, out=True, err=True, in_=True):
- self._oldout = sys.stdout
- self._olderr = sys.stderr
- self._oldin = sys.stdin
- if out and not hasattr(out, 'file'):
- out = TextIO()
- self.out = out
- if err:
- if not hasattr(err, 'write'):
- err = TextIO()
- self.err = err
- self.in_ = in_
-
- def startall(self):
- if self.out:
- sys.stdout = self.out
- if self.err:
- sys.stderr = self.err
- if self.in_:
- sys.stdin = self.in_ = DontReadFromInput()
-
- def done(self, save=True):
- """ return (outfile, errfile) and stop capturing. """
- outfile = errfile = None
- if self.out and not self.out.closed:
- sys.stdout = self._oldout
- outfile = self.out
- outfile.seek(0)
- if self.err and not self.err.closed:
- sys.stderr = self._olderr
- errfile = self.err
- errfile.seek(0)
- if self.in_:
- sys.stdin = self._oldin
- return outfile, errfile
-
- def resume(self):
- """ resume capturing with original temp files. """
- self.startall()
-
- def readouterr(self):
- """ return snapshot value of stdout/stderr capturings. """
- out = err = ""
- if self.out:
- out = self.out.getvalue()
- self.out.truncate(0)
- self.out.seek(0)
- if self.err:
- err = self.err.getvalue()
- self.err.truncate(0)
- self.err.seek(0)
- return out, err
+ def writeorg(self, data):
+ self._old.write(data)
+ self._old.flush()
class DontReadFromInput:
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -56,11 +56,15 @@
raise ValueError("not a string or argument list: %r" % (args,))
args = py.std.shlex.split(args)
pluginmanager = get_plugin_manager()
- if plugins:
- for plugin in plugins:
- pluginmanager.register(plugin)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args)
+ try:
+ if plugins:
+ for plugin in plugins:
+ pluginmanager.register(plugin)
+ return pluginmanager.hook.pytest_cmdline_parse(
+ pluginmanager=pluginmanager, args=args)
+ except Exception:
+ pluginmanager.ensure_shutdown()
+ raise
class PytestPluginManager(PluginManager):
def __init__(self, hookspecs=[hookspec]):
@@ -612,6 +616,9 @@
self.hook.pytest_logwarning(code=code, message=message,
fslocation=None, nodeid=None)
+ def get_terminal_writer(self):
+ return self.pluginmanager.getplugin("terminalreporter")._tw
+
def pytest_cmdline_parse(self, pluginmanager, args):
assert self == pluginmanager.config, (self, pluginmanager.config)
self.parse(args)
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -240,18 +240,22 @@
pass
l = []
last = []
+ wrappers = []
for plugin in plugins:
try:
meth = getattr(plugin, attrname)
- if hasattr(meth, 'tryfirst'):
- last.append(meth)
- elif hasattr(meth, 'trylast'):
- l.insert(0, meth)
- else:
- l.append(meth)
except AttributeError:
continue
+ if hasattr(meth, 'hookwrapper'):
+ wrappers.append(meth)
+ elif hasattr(meth, 'tryfirst'):
+ last.append(meth)
+ elif hasattr(meth, 'trylast'):
+ l.insert(0, meth)
+ else:
+ l.append(meth)
l.extend(last)
+ l.extend(wrappers)
self._listattrcache[key] = list(l)
return l
@@ -272,6 +276,14 @@
class MultiCall:
""" execute a call into multiple python functions/methods. """
+
+ class WrongHookWrapper(Exception):
+ """ a hook wrapper does not behave correctly. """
+ def __init__(self, func, message):
+ Exception.__init__(self, func, message)
+ self.func = func
+ self.message = message
+
def __init__(self, methods, kwargs, firstresult=False):
self.methods = list(methods)
self.kwargs = kwargs
@@ -283,16 +295,39 @@
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
def execute(self):
- while self.methods:
- method = self.methods.pop()
- kwargs = self.getkwargs(method)
- res = method(**kwargs)
- if res is not None:
- self.results.append(res)
- if self.firstresult:
- return res
- if not self.firstresult:
- return self.results
+ next_finalizers = []
+ try:
+ while self.methods:
+ method = self.methods.pop()
+ kwargs = self.getkwargs(method)
+ if hasattr(method, "hookwrapper"):
+ it = method(**kwargs)
+ next = getattr(it, "next", None)
+ if next is None:
+ next = getattr(it, "__next__", None)
+ if next is None:
+ raise self.WrongHookWrapper(method,
+ "wrapper does not contain a yield")
+ res = next()
+ next_finalizers.append((method, next))
+ else:
+ res = method(**kwargs)
+ if res is not None:
+ self.results.append(res)
+ if self.firstresult:
+ return res
+ if not self.firstresult:
+ return self.results
+ finally:
+ for method, fin in reversed(next_finalizers):
+ try:
+ fin()
+ except StopIteration:
+ pass
+ else:
+ raise self.WrongHookWrapper(method,
+ "wrapper contain more than one yield")
+
def getkwargs(self, method):
kwargs = {}
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/genscript.py
--- a/_pytest/genscript.py
+++ b/_pytest/genscript.py
@@ -60,6 +60,7 @@
def pytest_cmdline_main(config):
genscript = config.getvalue("genscript")
if genscript:
+ #tw = config.get_terminal_writer()
tw = py.io.TerminalWriter()
deps = ['py', '_pytest', 'pytest']
if sys.version_info < (2,7):
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -47,6 +47,8 @@
def pytest_cmdline_main(config):
if config.option.version:
+ capman = config.pluginmanager.getplugin("capturemanager")
+ capman.reset_capturings()
p = py.path.local(pytest.__file__)
sys.stderr.write("This is pytest version %s, imported from %s\n" %
(pytest.__version__, p))
@@ -62,7 +64,7 @@
return 0
def showhelp(config):
- tw = py.io.TerminalWriter()
+ tw = config.get_terminal_writer()
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/junitxml.py
--- a/_pytest/junitxml.py
+++ b/_pytest/junitxml.py
@@ -108,12 +108,14 @@
))
def _write_captured_output(self, report):
- sec = dict(report.sections)
- for name in ('out', 'err'):
- content = sec.get("Captured std%s" % name)
- if content:
- tag = getattr(Junit, 'system-'+name)
- self.append(tag(bin_xml_escape(content)))
+ for capname in ('out', 'err'):
+ allcontent = ""
+ for name, content in report.get_sections("Captured std%s" %
+ capname):
+ allcontent += content
+ if allcontent:
+ tag = getattr(Junit, 'system-'+capname)
+ self.append(tag(bin_xml_escape(allcontent)))
def append(self, obj):
self.tests[-1].append(obj)
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -233,6 +233,7 @@
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
+
#self.extrainit()
@property
@@ -465,6 +466,14 @@
"""
nextitem = None
+ def __init__(self, name, parent=None, config=None, session=None):
+ super(Item, self).__init__(name, parent, config, session)
+ self._report_sections = []
+
+ def add_report_section(self, when, key, content):
+ if content:
+ self._report_sections.append((when, key, content))
+
def reportinfo(self):
return self.fspath, None, ""
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -40,7 +40,7 @@
def pytest_cmdline_main(config):
if config.option.markers:
config.do_configure()
- tw = py.io.TerminalWriter()
+ tw = config.get_terminal_writer()
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/pdb.py
--- a/_pytest/pdb.py
+++ b/_pytest/pdb.py
@@ -16,49 +16,36 @@
if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
- old_trace = py.std.pdb.set_trace
+ old = (py.std.pdb.set_trace, pytestPDB._pluginmanager)
def fin():
- py.std.pdb.set_trace = old_trace
+ py.std.pdb.set_trace, pytestPDB._pluginmanager = old
py.std.pdb.set_trace = pytest.set_trace
+ pytestPDB._pluginmanager = config.pluginmanager
config._cleanup.append(fin)
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
- item = None
- collector = None
+ _pluginmanager = None
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
frame = sys._getframe().f_back
- item = self.item or self.collector
-
- if item is not None:
- capman = item.config.pluginmanager.getplugin("capturemanager")
- out, err = capman.suspendcapture()
- if hasattr(item, 'outerr'):
- item.outerr = (item.outerr[0] + out, item.outerr[1] + err)
+ capman = None
+ if self._pluginmanager is not None:
+ capman = self._pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.reset_capturings()
tw = py.io.TerminalWriter()
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
py.std.pdb.Pdb().set_trace(frame)
-def pdbitem(item):
- pytestPDB.item = item
-pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
-
- at pytest.mark.tryfirst
-def pytest_make_collect_report(__multicall__, collector):
- try:
- pytestPDB.collector = collector
- return __multicall__.execute()
- finally:
- pytestPDB.collector = None
-
-def pytest_runtest_makereport():
- pytestPDB.item = None
class PdbInvoke:
def pytest_exception_interact(self, node, call, report):
+ capman = node.config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.reset_capturings()
return _enter_pdb(node, call.excinfo, report)
def pytest_internalerror(self, excrepr, excinfo):
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -885,7 +885,7 @@
nodeid = "::".join(map(str, [curdir.bestrelpath(part[0])] + part[1:]))
nodeid.replace(session.fspath.sep, "/")
- tw = py.io.TerminalWriter()
+ tw = config.get_terminal_writer()
verbose = config.getvalue("verbose")
fm = session._fixturemanager
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -135,14 +135,13 @@
self.when = when
self.start = time()
try:
- try:
- self.result = func()
- except KeyboardInterrupt:
- raise
- except:
- self.excinfo = py.code.ExceptionInfo()
- finally:
+ self.result = func()
+ except KeyboardInterrupt:
self.stop = time()
+ raise
+ except:
+ self.excinfo = py.code.ExceptionInfo()
+ self.stop = time()
def __repr__(self):
if self.excinfo:
@@ -178,6 +177,11 @@
except UnicodeEncodeError:
out.line("<unprintable longrepr>")
+ def get_sections(self, prefix):
+ for name, content in self.sections:
+ if name.startswith(prefix):
+ yield prefix, content
+
passed = property(lambda x: x.outcome == "passed")
failed = property(lambda x: x.outcome == "failed")
skipped = property(lambda x: x.outcome == "skipped")
@@ -191,6 +195,7 @@
duration = call.stop-call.start
keywords = dict([(x,1) for x in item.keywords])
excinfo = call.excinfo
+ sections = []
if not call.excinfo:
outcome = "passed"
longrepr = None
@@ -209,16 +214,18 @@
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
+ for rwhen, key, content in item._report_sections:
+ sections.append(("Captured std%s %s" %(key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
- duration=duration)
+ sections, duration)
class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
- def __init__(self, nodeid, location,
- keywords, outcome, longrepr, when, sections=(), duration=0, **extra):
+ def __init__(self, nodeid, location, keywords, outcome,
+ longrepr, when, sections=(), duration=0, **extra):
#: normalized collection node id
self.nodeid = nodeid
@@ -286,7 +293,8 @@
class CollectReport(BaseReport):
- def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
+ def __init__(self, nodeid, outcome, longrepr, result,
+ sections=(), **extra):
self.nodeid = nodeid
self.outcome = outcome
self.longrepr = longrepr
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -36,7 +36,10 @@
def pytest_configure(config):
config.option.verbose -= config.option.quiet
- reporter = TerminalReporter(config, sys.stdout)
+ out = config.pluginmanager.getplugin("dupped_stdout")
+ #if out is None:
+ # out = sys.stdout
+ reporter = TerminalReporter(config, out)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
def mywriter(tags, args):
@@ -44,6 +47,11 @@
reporter.write_line("[traceconfig] " + msg)
config.trace.root.setprocessor("pytest:config", mywriter)
+def get_terminal_writer(config):
+ tr = config.pluginmanager.getplugin("terminalreporter")
+ return tr._tw
+
+
def getreportopt(config):
reportopts = ""
optvalue = config.option.report
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 bench/bench.py
--- a/bench/bench.py
+++ b/bench/bench.py
@@ -4,8 +4,8 @@
import cProfile
import pytest
import pstats
- script = sys.argv[1] if len(sys.argv) > 1 else "empty.py"
- stats = cProfile.run('pytest.cmdline.main([%r])' % script, 'prof')
+ script = sys.argv[1:] if len(sys.argv) > 1 else "empty.py"
+ stats = cProfile.run('pytest.cmdline.main(%r)' % script, 'prof')
p = pstats.Stats("prof")
p.strip_dirs()
p.sort_stats('cumulative')
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -4,6 +4,7 @@
import os
import sys
import py
+import tempfile
import pytest
import contextlib
@@ -44,6 +45,13 @@
+def StdCaptureFD(out=True, err=True, in_=True):
+ return capture.StdCaptureBase(out, err, in_, Capture=capture.FDCapture)
+
+def StdCapture(out=True, err=True, in_=True):
+ return capture.StdCaptureBase(out, err, in_, Capture=capture.SysCapture)
+
+
class TestCaptureManager:
def test_getmethod_default_no_fd(self, testdir, monkeypatch):
config = testdir.parseconfig(testdir.tmpdir)
@@ -75,7 +83,7 @@
@needsosdup
@pytest.mark.parametrize("method", ['no', 'fd', 'sys'])
def test_capturing_basic_api(self, method):
- capouter = capture.StdCaptureFD()
+ capouter = StdCaptureFD()
old = sys.stdout, sys.stderr, sys.stdin
try:
capman = CaptureManager()
@@ -95,11 +103,11 @@
assert not out and not err
capman.reset_capturings()
finally:
- capouter.reset()
+ capouter.stop_capturing()
@needsosdup
def test_juggle_capturings(self, testdir):
- capouter = capture.StdCaptureFD()
+ capouter = StdCaptureFD()
try:
#config = testdir.parseconfig(testdir.tmpdir)
capman = CaptureManager()
@@ -119,7 +127,7 @@
finally:
capman.reset_capturings()
finally:
- capouter.reset()
+ capouter.stop_capturing()
@pytest.mark.parametrize("method", ['fd', 'sys'])
@@ -282,9 +290,9 @@
"====* FAILURES *====",
"____*____",
"*test_capturing_outerr.py:8: ValueError",
- "*--- Captured stdout ---*",
+ "*--- Captured stdout *call*",
"1",
- "*--- Captured stderr ---*",
+ "*--- Captured stderr *call*",
"2",
])
@@ -688,17 +696,15 @@
cap = capture.FDCapture(fd)
data = tobytes("hello")
os.write(fd, data)
- f = cap.done()
- s = f.read()
- f.close()
+ s = cap.snap()
+ cap.done()
assert not s
cap = capture.FDCapture(fd)
cap.start()
os.write(fd, data)
- f = cap.done()
- s = f.read()
+ s = cap.snap()
+ cap.done()
assert s == "hello"
- f.close()
def test_simple_many(self, tmpfile):
for i in range(10):
@@ -712,22 +718,21 @@
def test_simple_fail_second_start(self, tmpfile):
fd = tmpfile.fileno()
cap = capture.FDCapture(fd)
- f = cap.done()
+ cap.done()
pytest.raises(ValueError, cap.start)
- f.close()
def test_stderr(self):
- cap = capture.FDCapture(2, patchsys=True)
+ cap = capture.FDCapture(2)
cap.start()
print_("hello", file=sys.stderr)
- f = cap.done()
- s = f.read()
+ s = cap.snap()
+ cap.done()
assert s == "hello\n"
def test_stdin(self, tmpfile):
tmpfile.write(tobytes("3"))
tmpfile.seek(0)
- cap = capture.FDCapture(0, tmpfile=tmpfile)
+ cap = capture.FDCapture(0, tmpfile)
cap.start()
# check with os.read() directly instead of raw_input(), because
# sys.stdin itself may be redirected (as pytest now does by default)
@@ -744,123 +749,121 @@
cap.writeorg(data2)
finally:
tmpfile.close()
- f = cap.done()
- scap = f.read()
+ scap = cap.snap()
+ cap.done()
assert scap == totext(data1)
stmp = open(tmpfile.name, 'rb').read()
assert stmp == data2
class TestStdCapture:
+ captureclass = staticmethod(StdCapture)
+
+ @contextlib.contextmanager
def getcapture(self, **kw):
- cap = capture.StdCapture(**kw)
- cap.startall()
- return cap
+ cap = self.__class__.captureclass(**kw)
+ cap.start_capturing()
+ try:
+ yield cap
+ finally:
+ cap.stop_capturing()
def test_capturing_done_simple(self):
- cap = self.getcapture()
- sys.stdout.write("hello")
- sys.stderr.write("world")
- outfile, errfile = cap.done()
- s = outfile.read()
- assert s == "hello"
- s = errfile.read()
- assert s == "world"
+ with self.getcapture() as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ out, err = cap.readouterr()
+ assert out == "hello"
+ assert err == "world"
def test_capturing_reset_simple(self):
- cap = self.getcapture()
- print("hello world")
- sys.stderr.write("hello error\n")
- out, err = cap.reset()
+ with self.getcapture() as cap:
+ print("hello world")
+ sys.stderr.write("hello error\n")
+ out, err = cap.readouterr()
assert out == "hello world\n"
assert err == "hello error\n"
def test_capturing_readouterr(self):
- cap = self.getcapture()
- try:
+ with self.getcapture() as cap:
print ("hello world")
sys.stderr.write("hello error\n")
out, err = cap.readouterr()
assert out == "hello world\n"
assert err == "hello error\n"
sys.stderr.write("error2")
- finally:
- out, err = cap.reset()
+ out, err = cap.readouterr()
assert err == "error2"
def test_capturing_readouterr_unicode(self):
- cap = self.getcapture()
- try:
+ with self.getcapture() as cap:
print ("hx\xc4\x85\xc4\x87")
out, err = cap.readouterr()
- finally:
- cap.reset()
assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
@pytest.mark.skipif('sys.version_info >= (3,)',
reason='text output different for bytes on python3')
def test_capturing_readouterr_decode_error_handling(self):
- cap = self.getcapture()
- # triggered a internal error in pytest
- print('\xa6')
- out, err = cap.readouterr()
+ with self.getcapture() as cap:
+ # triggered a internal error in pytest
+ print('\xa6')
+ out, err = cap.readouterr()
assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
def test_reset_twice_error(self):
- cap = self.getcapture()
- print ("hello")
- out, err = cap.reset()
- pytest.raises(ValueError, cap.reset)
+ with self.getcapture() as cap:
+ print ("hello")
+ out, err = cap.readouterr()
+ pytest.raises(ValueError, cap.stop_capturing)
assert out == "hello\n"
assert not err
def test_capturing_modify_sysouterr_in_between(self):
oldout = sys.stdout
olderr = sys.stderr
- cap = self.getcapture()
- sys.stdout.write("hello")
- sys.stderr.write("world")
- sys.stdout = capture.TextIO()
- sys.stderr = capture.TextIO()
- print ("not seen")
- sys.stderr.write("not seen\n")
- out, err = cap.reset()
+ with self.getcapture() as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ sys.stdout = capture.TextIO()
+ sys.stderr = capture.TextIO()
+ print ("not seen")
+ sys.stderr.write("not seen\n")
+ out, err = cap.readouterr()
assert out == "hello"
assert err == "world"
assert sys.stdout == oldout
assert sys.stderr == olderr
def test_capturing_error_recursive(self):
- cap1 = self.getcapture()
- print ("cap1")
- cap2 = self.getcapture()
- print ("cap2")
- out2, err2 = cap2.reset()
- out1, err1 = cap1.reset()
+ with self.getcapture() as cap1:
+ print ("cap1")
+ with self.getcapture() as cap2:
+ print ("cap2")
+ out2, err2 = cap2.readouterr()
+ out1, err1 = cap1.readouterr()
assert out1 == "cap1\n"
assert out2 == "cap2\n"
def test_just_out_capture(self):
- cap = self.getcapture(out=True, err=False)
- sys.stdout.write("hello")
- sys.stderr.write("world")
- out, err = cap.reset()
+ with self.getcapture(out=True, err=False) as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ out, err = cap.readouterr()
assert out == "hello"
assert not err
def test_just_err_capture(self):
- cap = self.getcapture(out=False, err=True)
- sys.stdout.write("hello")
- sys.stderr.write("world")
- out, err = cap.reset()
+ with self.getcapture(out=False, err=True) as cap:
+ sys.stdout.write("hello")
+ sys.stderr.write("world")
+ out, err = cap.readouterr()
assert err == "world"
assert not out
def test_stdin_restored(self):
old = sys.stdin
- cap = self.getcapture(in_=True)
- newstdin = sys.stdin
- out, err = cap.reset()
+ with self.getcapture(in_=True) as cap:
+ newstdin = sys.stdin
assert newstdin != sys.stdin
assert sys.stdin is old
@@ -868,68 +871,47 @@
print ("XXX this test may well hang instead of crashing")
print ("XXX which indicates an error in the underlying capturing")
print ("XXX mechanisms")
- cap = self.getcapture()
- pytest.raises(IOError, "sys.stdin.read()")
- out, err = cap.reset()
-
- def test_suspend_resume(self):
- cap = self.getcapture(out=True, err=False, in_=False)
- try:
- print ("hello")
- sys.stderr.write("error\n")
- out, err = cap.suspend()
- assert out == "hello\n"
- assert not err
- print ("in between")
- sys.stderr.write("in between\n")
- cap.resume()
- print ("after")
- sys.stderr.write("error_after\n")
- finally:
- out, err = cap.reset()
- assert out == "after\n"
- assert not err
+ with self.getcapture() as cap:
+ pytest.raises(IOError, "sys.stdin.read()")
class TestStdCaptureFD(TestStdCapture):
pytestmark = needsosdup
+ captureclass = staticmethod(StdCaptureFD)
- def getcapture(self, **kw):
- cap = capture.StdCaptureFD(**kw)
- cap.startall()
- return cap
+ def test_simple_only_fd(self, testdir):
+ testdir.makepyfile("""
+ import os
+ def test_x():
+ os.write(1, "hello\\n".encode("ascii"))
+ assert 0
+ """)
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines("""
+ *test_x*
+ *assert 0*
+ *Captured stdout*
+ """)
def test_intermingling(self):
- cap = self.getcapture()
- oswritebytes(1, "1")
- sys.stdout.write(str(2))
- sys.stdout.flush()
- oswritebytes(1, "3")
- oswritebytes(2, "a")
- sys.stderr.write("b")
- sys.stderr.flush()
- oswritebytes(2, "c")
- out, err = cap.reset()
+ with self.getcapture() as cap:
+ oswritebytes(1, "1")
+ sys.stdout.write(str(2))
+ sys.stdout.flush()
+ oswritebytes(1, "3")
+ oswritebytes(2, "a")
+ sys.stderr.write("b")
+ sys.stderr.flush()
+ oswritebytes(2, "c")
+ out, err = cap.readouterr()
assert out == "123"
assert err == "abc"
def test_many(self, capfd):
with lsof_check():
for i in range(10):
- cap = capture.StdCaptureFD()
- cap.reset()
-
-
- at needsosdup
-def test_stdcapture_fd_tmpfile(tmpfile):
- capfd = capture.StdCaptureFD(out=tmpfile)
- try:
- os.write(1, "hello".encode("ascii"))
- os.write(2, "world".encode("ascii"))
- outf, errf = capfd.done()
- finally:
- capfd.reset()
- assert outf == tmpfile
+ cap = StdCaptureFD()
+ cap.stop_capturing()
class TestStdCaptureFDinvalidFD:
@@ -938,19 +920,22 @@
def test_stdcapture_fd_invalid_fd(self, testdir):
testdir.makepyfile("""
import os
- from _pytest.capture import StdCaptureFD
+ from _pytest import capture
+ def StdCaptureFD(out=True, err=True, in_=True):
+ return capture.StdCaptureBase(out, err, in_,
+ Capture=capture.FDCapture)
def test_stdout():
os.close(1)
cap = StdCaptureFD(out=True, err=False, in_=False)
- cap.done()
+ cap.stop_capturing()
def test_stderr():
os.close(2)
cap = StdCaptureFD(out=False, err=True, in_=False)
- cap.done()
+ cap.stop_capturing()
def test_stdin():
os.close(0)
cap = StdCaptureFD(out=False, err=False, in_=True)
- cap.done()
+ cap.stop_capturing()
""")
result = testdir.runpytest("--capture=fd")
assert result.ret == 0
@@ -958,27 +943,8 @@
def test_capture_not_started_but_reset():
- capsys = capture.StdCapture()
- capsys.done()
- capsys.done()
- capsys.reset()
-
-
- at needsosdup
-def test_capture_no_sys():
- capsys = capture.StdCapture()
- try:
- cap = capture.StdCaptureFD(patchsys=False)
- cap.startall()
- sys.stdout.write("hello")
- sys.stderr.write("world")
- oswritebytes(1, "1")
- oswritebytes(2, "2")
- out, err = cap.reset()
- assert out == "1"
- assert err == "2"
- finally:
- capsys.reset()
+ capsys = StdCapture()
+ capsys.stop_capturing()
@needsosdup
@@ -986,19 +952,18 @@
def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
if not use:
tmpfile = True
- cap = capture.StdCaptureFD(out=False, err=tmpfile)
+ cap = StdCaptureFD(out=False, err=tmpfile)
try:
- cap.startall()
+ cap.start_capturing()
capfile = cap.err.tmpfile
- cap.suspend()
- cap.resume()
+ cap.readouterr()
finally:
- cap.reset()
+ cap.stop_capturing()
capfile2 = cap.err.tmpfile
assert capfile2 == capfile
- at pytest.mark.parametrize('method', ['StdCapture', 'StdCaptureFD'])
+ at pytest.mark.parametrize('method', ['SysCapture', 'FDCapture'])
def test_capturing_and_logging_fundamentals(testdir, method):
if method == "StdCaptureFD" and not hasattr(os, 'dup'):
pytest.skip("need os.dup")
@@ -1007,23 +972,27 @@
import sys, os
import py, logging
from _pytest import capture
- cap = capture.%s(out=False, in_=False)
- cap.startall()
+ cap = capture.StdCaptureBase(out=False, in_=False,
+ Capture=capture.%s)
+ cap.start_capturing()
logging.warn("hello1")
- outerr = cap.suspend()
+ outerr = cap.readouterr()
print ("suspend, captured %%s" %%(outerr,))
logging.warn("hello2")
- cap.resume()
+ cap.pop_outerr_to_orig()
logging.warn("hello3")
- outerr = cap.suspend()
+ outerr = cap.readouterr()
print ("suspend2, captured %%s" %% (outerr,))
""" % (method,))
result = testdir.runpython(p)
- result.stdout.fnmatch_lines([
- "suspend, captured*hello1*",
- "suspend2, captured*hello2*WARNING:root:hello3*",
- ])
+ result.stdout.fnmatch_lines("""
+ suspend, captured*hello1*
+ suspend2, captured*WARNING:root:hello3*
+ """)
+ result.stderr.fnmatch_lines("""
+ WARNING:root:hello2
+ """)
assert "atexit" not in result.stderr.str()
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -523,6 +523,95 @@
res = MultiCall([m1, m2], {}).execute()
assert res == [1]
+ def test_hookwrapper(self):
+ l = []
+ def m1():
+ l.append("m1 init")
+ yield None
+ l.append("m1 finish")
+ m1.hookwrapper = True
+
+ def m2():
+ l.append("m2")
+ return 2
+ res = MultiCall([m2, m1], {}).execute()
+ assert res == [2]
+ assert l == ["m1 init", "m2", "m1 finish"]
+ l[:] = []
+ res = MultiCall([m2, m1], {}, firstresult=True).execute()
+ assert res == 2
+ assert l == ["m1 init", "m2", "m1 finish"]
+
+ def test_hookwrapper_order(self):
+ l = []
+ def m1():
+ l.append("m1 init")
+ yield 1
+ l.append("m1 finish")
+ m1.hookwrapper = True
+
+ def m2():
+ l.append("m2 init")
+ yield 2
+ l.append("m2 finish")
+ m2.hookwrapper = True
+ res = MultiCall([m2, m1], {}).execute()
+ assert res == [1, 2]
+ assert l == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
+
+ def test_listattr_hookwrapper_ordering(self):
+ class P1:
+ @pytest.mark.hookwrapper
+ def m(self):
+ return 17
+
+ class P2:
+ def m(self):
+ return 23
+
+ class P3:
+ @pytest.mark.tryfirst
+ def m(self):
+ return 19
+
+ pluginmanager = PluginManager()
+ p1 = P1()
+ p2 = P2()
+ p3 = P3()
+ pluginmanager.register(p1)
+ pluginmanager.register(p2)
+ pluginmanager.register(p3)
+ methods = pluginmanager.listattr('m')
+ assert methods == [p2.m, p3.m, p1.m]
+ ## listattr keeps a cache and deleting
+ ## a function attribute requires clearing it
+ #pluginmanager._listattrcache.clear()
+ #del P1.m.__dict__['tryfirst']
+
+ def test_hookwrapper_not_yield(self):
+ def m1():
+ pass
+ m1.hookwrapper = True
+
+ mc = MultiCall([m1], {})
+ with pytest.raises(mc.WrongHookWrapper) as ex:
+ mc.execute()
+ assert ex.value.func == m1
+ assert ex.value.message
+
+ def test_hookwrapper_too_many_yield(self):
+ def m1():
+ yield 1
+ yield 2
+ m1.hookwrapper = True
+
+ mc = MultiCall([m1], {})
+ with pytest.raises(mc.WrongHookWrapper) as ex:
+ mc.execute()
+ assert ex.value.func == m1
+ assert ex.value.message
+
+
class TestHookRelay:
def test_happypath(self):
pm = PluginManager()
diff -r 3f7abe3da42704c757ea437f14bd8caf5b8f04c7 -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 testing/test_junitxml.py
--- a/testing/test_junitxml.py
+++ b/testing/test_junitxml.py
@@ -478,10 +478,12 @@
path = testdir.tmpdir.join("test.xml")
log = LogXML(str(path), None)
ustr = py.builtin._totext("ВНИ!", "utf-8")
- class report:
+ from _pytest.runner import BaseReport
+ class Report(BaseReport):
longrepr = ustr
sections = []
nodeid = "something"
+ report = Report()
# hopefully this is not too brittle ...
log.pytest_sessionstart()
https://bitbucket.org/hpk42/pytest/commits/deee08c4eef3/
Changeset: deee08c4eef3
User: hpk42
Date: 2014-03-28 09:46:38
Summary: cleanup and refine issue412 test (still failing on py33)
Affected #: 2 files
diff -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 -r deee08c4eef3c15b09b1520bd72ba9fe990e60a7 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -12,30 +12,7 @@
import py
import pytest
-try:
- from io import StringIO
-except ImportError:
- from StringIO import StringIO
-
-try:
- from io import BytesIO
-except ImportError:
- class BytesIO(StringIO):
- def write(self, data):
- if isinstance(data, unicode):
- raise TypeError("not a byte value: %r" % (data,))
- StringIO.write(self, data)
-
-if sys.version_info < (3, 0):
- class TextIO(StringIO):
- def write(self, data):
- if not isinstance(data, unicode):
- enc = getattr(self, '_encoding', 'UTF-8')
- data = unicode(data, enc, 'replace')
- StringIO.write(self, data)
-else:
- TextIO = StringIO
-
+from py.io import TextIO
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@@ -232,7 +209,8 @@
error_capsysfderror = "cannot use capsys and capfd at the same time"
-def pytest_funcarg__capsys(request):
+ at pytest.fixture
+def capsys(request):
"""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.
@@ -242,7 +220,8 @@
request.node._capfuncarg = c = CaptureFixture(SysCapture)
return c
-def pytest_funcarg__capfd(request):
+ at pytest.fixture
+def capfd(request):
"""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.
diff -r 756cdf331fc809c566a71ca0a093f1c059e9cc10 -r deee08c4eef3c15b09b1520bd72ba9fe990e60a7 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -541,8 +541,8 @@
assert 'hello19' not in result.stdout.str()
- at pytest.mark.xfail(sys.version_info>=(3,), reason='demonstrate #412')
-def test_capture_badoutput(testdir):
+ at pytest.mark.xfail("sys.version_info >= (3,)")
+def test_capture_badoutput_issue412(testdir):
testdir.makepyfile("""
import os
@@ -552,11 +552,12 @@
assert 0
""")
result = testdir.runpytest('--cap=fd')
- #this fails on python3 - fnmatch first for debugging
- result.stdout.fnmatch_lines([
- '*1 failed*',
- ])
- assert result.ret == 1
+ result.stdout.fnmatch_lines('''
+ *def test_func*
+ *assert 0*
+ *Captured*
+ *1 failed*
+ ''')
def test_capture_early_option_parsing(testdir):
@@ -616,7 +617,7 @@
def test_bytes_io():
- f = capture.BytesIO()
+ f = py.io.BytesIO()
f.write(tobytes("hello"))
pytest.raises(TypeError, "f.write(totext('hello'))")
s = f.getvalue()
https://bitbucket.org/hpk42/pytest/commits/116ac00cd354/
Changeset: 116ac00cd354
User: hpk42
Date: 2014-03-28 11:27:02
Summary: fix issue412 and other encoding issues. Streamline dupfile() into
a new more thoughtful safe_text_dupfile helper.
Affected #: 3 files
diff -r deee08c4eef3c15b09b1520bd72ba9fe990e60a7 -r 116ac00cd354f38cd0d55118581c31c1cb845bee _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -13,6 +13,7 @@
import pytest
from py.io import TextIO
+unicode = py.builtin.text
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@@ -38,12 +39,7 @@
method = "sys"
pluginmanager = early_config.pluginmanager
if method != "no":
- try:
- sys.stdout.fileno()
- except Exception:
- dupped_stdout = sys.stdout
- else:
- dupped_stdout = dupfile(sys.stdout, buffering=1)
+ dupped_stdout = safe_text_dupfile(sys.stdout, "wb")
pluginmanager.register(dupped_stdout, "dupped_stdout")
#pluginmanager.add_shutdown(dupped_stdout.close)
capman = CaptureManager(method)
@@ -256,51 +252,41 @@
return "", ""
-def dupfile(f, mode=None, buffering=0, raising=False, encoding=None):
- """ return a new open file object that's a duplicate of f
-
- mode is duplicated if not given, 'buffering' controls
- buffer size (defaulting to no buffering) and 'raising'
- defines whether an exception is raised when an incompatible
- file object is passed in (if raising is False, the file
- object itself will be returned)
+def safe_text_dupfile(f, mode, default_encoding="UTF8"):
+ """ return a open text file object that's a duplicate of f on the
+ FD-level if possible.
"""
+ encoding = getattr(f, "encoding", None)
try:
fd = f.fileno()
- mode = mode or f.mode
- except AttributeError:
- if raising:
- raise
- return f
- newfd = os.dup(fd)
- if sys.version_info >= (3, 0):
- if encoding is not None:
- mode = mode.replace("b", "")
- buffering = True
- return os.fdopen(newfd, mode, buffering, encoding, closefd=True)
+ except Exception:
+ if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
+ # we seem to have a text stream, let's just use it
+ return f
else:
- f = os.fdopen(newfd, mode, buffering)
- if encoding is not None:
- return EncodedFile(f, encoding)
- return f
+ newfd = os.dup(fd)
+ if "b" not in mode:
+ mode += "b"
+ f = os.fdopen(newfd, mode, 0) # no buffering
+ return EncodedFile(f, encoding or default_encoding)
class EncodedFile(object):
- def __init__(self, _stream, encoding):
- self._stream = _stream
+ def __init__(self, buffer, encoding):
+ self.buffer = buffer
self.encoding = encoding
def write(self, obj):
if isinstance(obj, unicode):
- obj = obj.encode(self.encoding)
- self._stream.write(obj)
+ obj = obj.encode(self.encoding, "replace")
+ self.buffer.write(obj)
def writelines(self, linelist):
data = ''.join(linelist)
self.write(data)
def __getattr__(self, name):
- return getattr(self._stream, name)
+ return getattr(self.buffer, name)
class StdCaptureBase(object):
@@ -344,13 +330,8 @@
def readouterr(self):
""" return snapshot unicode value of stdout/stderr capturings. """
- return self._readsnapshot('out'), self._readsnapshot('err')
-
- def _readsnapshot(self, name):
- cap = getattr(self, name, None)
- if cap is None:
- return ""
- return cap.snap()
+ return (self.out.snap() if self.out is not None else "",
+ self.err.snap() if self.err is not None else "")
class FDCapture:
@@ -370,7 +351,7 @@
else:
f = TemporaryFile()
with f:
- tmpfile = dupfile(f, encoding="UTF-8")
+ tmpfile = safe_text_dupfile(f, mode="wb+")
self.tmpfile = tmpfile
if targetfd in patchsysdict:
self._oldsys = getattr(sys, patchsysdict[targetfd])
diff -r deee08c4eef3c15b09b1520bd72ba9fe990e60a7 -r 116ac00cd354f38cd0d55118581c31c1cb845bee _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -104,6 +104,7 @@
self.startdir = self.curdir = py.path.local()
if file is None:
file = py.std.sys.stdout
+ #assert file.encoding
self._tw = self.writer = py.io.TerminalWriter(file)
if self.config.option.color == 'yes':
self._tw.hasmarkup = True
@@ -144,7 +145,8 @@
self._tw.write(content, **markup)
def write_line(self, line, **markup):
- line = str(line)
+ if not py.builtin._istext(line):
+ line = py.builtin.text(line, errors="replace")
self.ensure_newline()
self._tw.line(line, **markup)
@@ -163,7 +165,7 @@
self._tw.line(msg, **kw)
def pytest_internalerror(self, excrepr):
- for line in str(excrepr).split("\n"):
+ for line in py.builtin.text(excrepr).split("\n"):
self.write_line("INTERNALERROR> " + line)
return 1
diff -r deee08c4eef3c15b09b1520bd72ba9fe990e60a7 -r 116ac00cd354f38cd0d55118581c31c1cb845bee testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -541,7 +541,6 @@
assert 'hello19' not in result.stdout.str()
- at pytest.mark.xfail("sys.version_info >= (3,)")
def test_capture_badoutput_issue412(testdir):
testdir.makepyfile("""
import os
@@ -571,8 +570,6 @@
assert 'hello19' in result.stdout.str()
- at pytest.mark.xfail(sys.version_info >= (3, 0), reason='encoding issues')
- at pytest.mark.xfail(sys.version_info < (2, 6), reason='test not run on py25')
def test_capture_binary_output(testdir):
testdir.makepyfile(r"""
import pytest
@@ -646,7 +643,7 @@
def test_dupfile(tmpfile):
flist = []
for i in range(5):
- nf = capture.dupfile(tmpfile, encoding="utf-8")
+ nf = capture.safe_text_dupfile(tmpfile, "wb")
assert nf != tmpfile
assert nf.fileno() != tmpfile.fileno()
assert nf not in flist
@@ -660,19 +657,17 @@
assert "01234" in repr(s)
tmpfile.close()
+def test_dupfile_on_bytesio():
+ io = py.io.BytesIO()
+ f = capture.safe_text_dupfile(io, "wb")
+ f.write("hello")
+ assert io.getvalue() == b"hello"
-def test_dupfile_no_mode():
- """
- dupfile should trap an AttributeError and return f if no mode is supplied.
- """
- class SomeFileWrapper(object):
- "An object with a fileno method but no mode attribute"
- def fileno(self):
- return 1
- tmpfile = SomeFileWrapper()
- assert capture.dupfile(tmpfile) is tmpfile
- with pytest.raises(AttributeError):
- capture.dupfile(tmpfile, raising=True)
+def test_dupfile_on_textio():
+ io = py.io.TextIO()
+ f = capture.safe_text_dupfile(io, "wb")
+ f.write("hello")
+ assert io.getvalue() == "hello"
@contextlib.contextmanager
https://bitbucket.org/hpk42/pytest/commits/8004c01ca861/
Changeset: 8004c01ca861
User: hpk42
Date: 2014-03-28 11:29:29
Summary: add changelog entry:
fix issue412: messing with stdout/stderr FD-level streams is now
captured without crashes.
Affected #: 1 file
diff -r 116ac00cd354f38cd0d55118581c31c1cb845bee -r 8004c01ca861a7e1bd0a7939730e9660b59242cf CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -23,6 +23,9 @@
- merge PR123: improved integration with mock.patch decorator on tests.
+- fix issue412: messing with stdout/stderr FD-level streams is now
+ captured without crashes.
+
2.5.2
-----------------------------------
https://bitbucket.org/hpk42/pytest/commits/529b5e415bdb/
Changeset: 529b5e415bdb
User: hpk42
Date: 2014-04-01 14:19:52
Summary: rename StdCaptureBase to MultiCapture
Affected #: 2 files
diff -r 8004c01ca861a7e1bd0a7939730e9660b59242cf -r 529b5e415bdbbc441e83653c0a0143e22398768b _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -81,11 +81,11 @@
def _getcapture(self, method):
if method == "fd":
- return StdCaptureBase(out=True, err=True, Capture=FDCapture)
+ return MultiCapture(out=True, err=True, Capture=FDCapture)
elif method == "sys":
- return StdCaptureBase(out=True, err=True, Capture=SysCapture)
+ return MultiCapture(out=True, err=True, Capture=SysCapture)
elif method == "no":
- return StdCaptureBase(out=False, err=False, in_=False)
+ return MultiCapture(out=False, err=False, in_=False)
else:
raise ValueError("unknown capturing method: %r" % method)
@@ -235,7 +235,7 @@
self.captureclass = captureclass
def _start(self):
- self._capture = StdCaptureBase(out=True, err=True, in_=False,
+ self._capture = MultiCapture(out=True, err=True, in_=False,
Capture=self.captureclass)
self._capture.start_capturing()
@@ -289,7 +289,7 @@
return getattr(self.buffer, name)
-class StdCaptureBase(object):
+class MultiCapture(object):
out = err = in_ = None
def __init__(self, out=True, err=True, in_=True, Capture=None):
diff -r 8004c01ca861a7e1bd0a7939730e9660b59242cf -r 529b5e415bdbbc441e83653c0a0143e22398768b testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -46,10 +46,10 @@
def StdCaptureFD(out=True, err=True, in_=True):
- return capture.StdCaptureBase(out, err, in_, Capture=capture.FDCapture)
+ return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture)
def StdCapture(out=True, err=True, in_=True):
- return capture.StdCaptureBase(out, err, in_, Capture=capture.SysCapture)
+ return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture)
class TestCaptureManager:
@@ -918,7 +918,7 @@
import os
from _pytest import capture
def StdCaptureFD(out=True, err=True, in_=True):
- return capture.StdCaptureBase(out, err, in_,
+ return capture.MultiCapture(out, err, in_,
Capture=capture.FDCapture)
def test_stdout():
os.close(1)
@@ -958,6 +958,25 @@
capfile2 = cap.err.tmpfile
assert capfile2 == capfile
+ at needsosdup
+def test_close_and_capture_again(testdir):
+ testdir.makepyfile("""
+ import os
+ def test_close():
+ os.close(1)
+ def test_capture_again():
+ os.write(1, "hello\\n")
+ assert 0
+ """)
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines("""
+ *test_capture_again*
+ *assert 0*
+ *stdout*
+ *hello*
+ """)
+
+
@pytest.mark.parametrize('method', ['SysCapture', 'FDCapture'])
def test_capturing_and_logging_fundamentals(testdir, method):
@@ -968,7 +987,7 @@
import sys, os
import py, logging
from _pytest import capture
- cap = capture.StdCaptureBase(out=False, in_=False,
+ cap = capture.MultiCapture(out=False, in_=False,
Capture=capture.%s)
cap.start_capturing()
https://bitbucket.org/hpk42/pytest/commits/cf556c9a4df3/
Changeset: cf556c9a4df3
User: hpk42
Date: 2014-04-01 14:19:55
Summary: avoid some redundancy by using SysCapture from FDCapture for manipulating sys.std{out,in,err}
Affected #: 2 files
diff -r 529b5e415bdbbc441e83653c0a0143e22398768b -r cf556c9a4df31a9b0b1d74a67b86ac2cc51f4006 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -333,6 +333,8 @@
return (self.out.snap() if self.out is not None else "",
self.err.snap() if self.err is not None else "")
+class NoCapture:
+ __init__ = start = done = lambda *args: None
class FDCapture:
""" Capture IO to/from a given os-level filedescriptor. """
@@ -340,36 +342,38 @@
def __init__(self, targetfd, tmpfile=None):
self.targetfd = targetfd
try:
- self._savefd = os.dup(self.targetfd)
+ self.targetfd_save = os.dup(self.targetfd)
except OSError:
self.start = lambda: None
self.done = lambda: None
else:
- if tmpfile is None:
- if targetfd == 0:
- tmpfile = open(os.devnull, "r")
- else:
+ if targetfd == 0:
+ assert not tmpfile, "cannot set tmpfile with stdin"
+ tmpfile = open(os.devnull, "r")
+ self.syscapture = SysCapture(targetfd)
+ else:
+ if tmpfile is None:
f = TemporaryFile()
with f:
tmpfile = safe_text_dupfile(f, mode="wb+")
+ if targetfd in patchsysdict:
+ self.syscapture = SysCapture(targetfd, tmpfile)
+ else:
+ self.syscapture = NoCapture()
self.tmpfile = tmpfile
- if targetfd in patchsysdict:
- self._oldsys = getattr(sys, patchsysdict[targetfd])
+ self.tmpfile_fd = tmpfile.fileno()
def __repr__(self):
- return "<FDCapture %s oldfd=%s>" % (self.targetfd, self._savefd)
+ return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
def start(self):
""" Start capturing on targetfd using memorized tmpfile. """
try:
- os.fstat(self._savefd)
- except OSError:
+ os.fstat(self.targetfd_save)
+ except (AttributeError, OSError):
raise ValueError("saved filedescriptor not valid anymore")
- targetfd = self.targetfd
- os.dup2(self.tmpfile.fileno(), targetfd)
- if hasattr(self, '_oldsys'):
- subst = self.tmpfile if targetfd != 0 else DontReadFromInput()
- setattr(sys, patchsysdict[targetfd], subst)
+ os.dup2(self.tmpfile_fd, self.targetfd)
+ self.syscapture.start()
def snap(self):
f = self.tmpfile
@@ -386,28 +390,38 @@
def done(self):
""" stop capturing, restore streams, return original capture file,
seeked to position zero. """
- os.dup2(self._savefd, self.targetfd)
- os.close(self._savefd)
- if hasattr(self, '_oldsys'):
- setattr(sys, patchsysdict[self.targetfd], self._oldsys)
+ targetfd_save = self.__dict__.pop("targetfd_save")
+ os.dup2(targetfd_save, self.targetfd)
+ os.close(targetfd_save)
+ self.syscapture.done()
self.tmpfile.close()
+ def suspend(self):
+ self.syscapture.suspend()
+ os.dup2(self.targetfd_save, self.targetfd)
+
+ def resume(self):
+ self.syscapture.resume()
+ os.dup2(self.tmpfile_fd, self.targetfd)
+
def writeorg(self, data):
""" write to original file descriptor. """
if py.builtin._istext(data):
data = data.encode("utf8") # XXX use encoding of original stream
- os.write(self._savefd, data)
+ os.write(self.targetfd_save, data)
class SysCapture:
- def __init__(self, fd):
+ def __init__(self, fd, tmpfile=None):
name = patchsysdict[fd]
self._old = getattr(sys, name)
self.name = name
- if name == "stdin":
- self.tmpfile = DontReadFromInput()
- else:
- self.tmpfile = TextIO()
+ if tmpfile is None:
+ if name == "stdin":
+ tmpfile = DontReadFromInput()
+ else:
+ tmpfile = TextIO()
+ self.tmpfile = tmpfile
def start(self):
setattr(sys, self.name, self.tmpfile)
@@ -421,8 +435,15 @@
def done(self):
setattr(sys, self.name, self._old)
+ del self._old
self.tmpfile.close()
+ def suspend(self):
+ setattr(sys, self.name, self._old)
+
+ def resume(self):
+ setattr(sys, self.name, self.tmpfile)
+
def writeorg(self, data):
self._old.write(data)
self._old.flush()
diff -r 529b5e415bdbbc441e83653c0a0143e22398768b -r cf556c9a4df31a9b0b1d74a67b86ac2cc51f4006 testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -726,15 +726,11 @@
assert s == "hello\n"
def test_stdin(self, tmpfile):
- tmpfile.write(tobytes("3"))
- tmpfile.seek(0)
- cap = capture.FDCapture(0, tmpfile)
+ cap = capture.FDCapture(0)
cap.start()
- # check with os.read() directly instead of raw_input(), because
- # sys.stdin itself may be redirected (as pytest now does by default)
x = os.read(0, 100).strip()
cap.done()
- assert x == tobytes("3")
+ assert x == tobytes('')
def test_writeorg(self, tmpfile):
data1, data2 = tobytes("foo"), tobytes("bar")
@@ -751,7 +747,37 @@
stmp = open(tmpfile.name, 'rb').read()
assert stmp == data2
+ def test_simple_resume_suspend(self, tmpfile):
+ with saved_fd(1):
+ cap = capture.FDCapture(1)
+ cap.start()
+ data = tobytes("hello")
+ os.write(1, data)
+ sys.stdout.write("whatever")
+ s = cap.snap()
+ assert s == "hellowhatever"
+ cap.suspend()
+ os.write(1, tobytes("world"))
+ sys.stdout.write("qlwkej")
+ assert not cap.snap()
+ cap.resume()
+ os.write(1, tobytes("but now"))
+ sys.stdout.write(" yes\n")
+ s = cap.snap()
+ assert s == "but now yes\n"
+ cap.suspend()
+ cap.done()
+ pytest.raises(AttributeError, cap.suspend)
+ at contextlib.contextmanager
+def saved_fd(fd):
+ new_fd = os.dup(fd)
+ try:
+ yield
+ finally:
+ os.dup2(new_fd, fd)
+
+
class TestStdCapture:
captureclass = staticmethod(StdCapture)
https://bitbucket.org/hpk42/pytest/commits/cccaaa5ff07f/
Changeset: cccaaa5ff07f
User: hpk42
Date: 2014-04-01 14:19:58
Summary: introduce resume/suspend functionality for FDCapture and SysCapture,
fixing problems with early bailouts (from argparse's parse() function e.g.)
that wrote to stdout.
Affected #: 2 files
diff -r cf556c9a4df31a9b0b1d74a67b86ac2cc51f4006 -r cccaaa5ff07fefada6e4929bdea33b1fa4edf7cc _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -46,13 +46,7 @@
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
- def teardown():
- try:
- capman.reset_capturings()
- except ValueError:
- pass
-
- pluginmanager.add_shutdown(teardown)
+ pluginmanager.add_shutdown(capman.reset_capturings)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
@@ -124,16 +118,18 @@
self._method2capture[method] = cap = self._getcapture(method)
cap.start_capturing()
else:
- cap.pop_outerr_to_orig()
+ cap.resume_capturing()
def suspendcapture(self, item=None):
self.deactivate_funcargs()
method = self.__dict__.pop("_capturing", None)
+ outerr = "", ""
if method is not None:
cap = self._method2capture.get(method)
if cap is not None:
- return cap.readouterr()
- return "", ""
+ outerr = cap.readouterr()
+ cap.suspend_capturing()
+ return outerr
def activate_funcargs(self, pyfuncitem):
capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
@@ -316,6 +312,18 @@
if err:
self.err.writeorg(err)
+ def suspend_capturing(self):
+ if self.out:
+ self.out.suspend()
+ if self.err:
+ self.err.suspend()
+
+ def resume_capturing(self):
+ if self.out:
+ self.out.resume()
+ if self.err:
+ self.err.resume()
+
def stop_capturing(self):
""" stop capturing and reset capturing streams """
if hasattr(self, '_reset'):
@@ -334,7 +342,7 @@
self.err.snap() if self.err is not None else "")
class NoCapture:
- __init__ = start = done = lambda *args: None
+ __init__ = start = done = suspend = resume = lambda *args: None
class FDCapture:
""" Capture IO to/from a given os-level filedescriptor. """
diff -r cf556c9a4df31a9b0b1d74a67b86ac2cc51f4006 -r cccaaa5ff07fefada6e4929bdea33b1fa4edf7cc testing/acceptance_test.py
--- a/testing/acceptance_test.py
+++ b/testing/acceptance_test.py
@@ -335,6 +335,12 @@
res = testdir.runpytest(p.basename)
assert res.ret == 0
+ def test_unknown_option(self, testdir):
+ result = testdir.runpytest("--qwlkej")
+ result.stderr.fnmatch_lines("""
+ *unrecognized*
+ """)
+
class TestInvocationVariants:
def test_earlyinit(self, testdir):
https://bitbucket.org/hpk42/pytest/commits/d9cd8600a979/
Changeset: d9cd8600a979
User: hpk42
Date: 2014-04-01 14:32:12
Summary: remove non-documented per-conftest capturing option and simplify/refactor all code accordingly. Also make capturing more robust against tests closing FD1/2 and against pdb.set_trace() calls.
Affected #: 4 files
diff -r cccaaa5ff07fefada6e4929bdea33b1fa4edf7cc -r d9cd8600a979cd2442a2790edad12809d3d5d9ce _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -21,9 +21,10 @@
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption(
- '--capture', action="store", default=None,
+ '--capture', action="store",
+ default="fd" if hasattr(os, "dup") else "sys",
metavar="method", choices=['fd', 'sys', 'no'],
- help="per-test capturing method: one of fd (default)|sys|no.")
+ help="per-test capturing method: one of fd|sys|no.")
group._addoption(
'-s', action="store_const", const="no", dest="capture",
help="shortcut for --capture=no.")
@@ -32,16 +33,13 @@
@pytest.mark.tryfirst
def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
ns = parser.parse_known_args(args)
+ pluginmanager = early_config.pluginmanager
method = ns.capture
- if not method:
- method = "fd"
- if method == "fd" and not hasattr(os, "dup"):
- method = "sys"
- pluginmanager = early_config.pluginmanager
if method != "no":
dupped_stdout = safe_text_dupfile(sys.stdout, "wb")
pluginmanager.register(dupped_stdout, "dupped_stdout")
#pluginmanager.add_shutdown(dupped_stdout.close)
+
capman = CaptureManager(method)
pluginmanager.register(capman, "capturemanager")
@@ -55,7 +53,7 @@
pluginmanager.add_shutdown(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
- capman.resumecapture()
+ capman.init_capturings()
try:
try:
return __multicall__.execute()
@@ -67,11 +65,9 @@
raise
-
class CaptureManager:
- def __init__(self, defaultmethod=None):
- self._method2capture = {}
- self._defaultmethod = defaultmethod
+ def __init__(self, method):
+ self._method = method
def _getcapture(self, method):
if method == "fd":
@@ -83,53 +79,27 @@
else:
raise ValueError("unknown capturing method: %r" % method)
- def _getmethod(self, config, fspath):
- if config.option.capture:
- method = config.option.capture
- else:
- try:
- method = config._conftest.rget("option_capture", path=fspath)
- except KeyError:
- method = "fd"
- if method == "fd" and not hasattr(os, 'dup'): # e.g. jython
- method = "sys"
- return method
+ def init_capturings(self):
+ assert not hasattr(self, "_capturing")
+ self._capturing = self._getcapture(self._method)
+ self._capturing.start_capturing()
def reset_capturings(self):
- for cap in self._method2capture.values():
+ cap = self.__dict__.pop("_capturing", None)
+ if cap is not None:
cap.pop_outerr_to_orig()
cap.stop_capturing()
- self._method2capture.clear()
- def resumecapture_item(self, item):
- method = self._getmethod(item.config, item.fspath)
- return self.resumecapture(method)
+ def resumecapture(self):
+ self._capturing.resume_capturing()
- def resumecapture(self, method=None):
- if hasattr(self, '_capturing'):
- raise ValueError(
- "cannot resume, already capturing with %r" %
- (self._capturing,))
- if method is None:
- method = self._defaultmethod
- cap = self._method2capture.get(method)
- self._capturing = method
- if cap is None:
- self._method2capture[method] = cap = self._getcapture(method)
- cap.start_capturing()
- else:
- cap.resume_capturing()
-
- def suspendcapture(self, item=None):
+ def suspendcapture(self, in_=False):
self.deactivate_funcargs()
- method = self.__dict__.pop("_capturing", None)
- outerr = "", ""
- if method is not None:
- cap = self._method2capture.get(method)
- if cap is not None:
- outerr = cap.readouterr()
- cap.suspend_capturing()
- return outerr
+ cap = getattr(self, "_capturing", None)
+ if cap is not None:
+ outerr = cap.readouterr()
+ cap.suspend_capturing(in_=in_)
+ return outerr
def activate_funcargs(self, pyfuncitem):
capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
@@ -142,28 +112,20 @@
if capfuncarg is not None:
capfuncarg.close()
- @pytest.mark.hookwrapper
+ @pytest.mark.tryfirst
def pytest_make_collect_report(self, __multicall__, collector):
- method = self._getmethod(collector.config, collector.fspath)
+ if not isinstance(collector, pytest.File):
+ return
+ self.resumecapture()
try:
- self.resumecapture(method)
- except ValueError:
- yield
- # recursive collect, XXX refactor capturing
- # to allow for more lightweight recursive capturing
- return
- yield
- out, err = self.suspendcapture()
- # XXX getting the report from the ongoing hook call is a bit
- # of a hack. We need to think about capturing during collection
- # and find out if it's really needed fine-grained (per
- # collector).
- if __multicall__.results:
- rep = __multicall__.results[0]
- if out:
- rep.sections.append(("Captured stdout", out))
- if err:
- rep.sections.append(("Captured stderr", err))
+ rep = __multicall__.execute()
+ finally:
+ out, err = self.suspendcapture()
+ if out:
+ rep.sections.append(("Captured stdout", out))
+ if err:
+ rep.sections.append(("Captured stderr", err))
+ return rep
@pytest.mark.hookwrapper
def pytest_runtest_setup(self, item):
@@ -192,9 +154,9 @@
@contextlib.contextmanager
def item_capture_wrapper(self, item, when):
- self.resumecapture_item(item)
+ self.resumecapture()
yield
- out, err = self.suspendcapture(item)
+ out, err = self.suspendcapture()
item.add_report_section(when, "out", out)
item.add_report_section(when, "err", err)
@@ -238,14 +200,14 @@
def close(self):
cap = self.__dict__.pop("_capture", None)
if cap is not None:
- cap.pop_outerr_to_orig()
+ self._outerr = cap.pop_outerr_to_orig()
cap.stop_capturing()
def readouterr(self):
try:
return self._capture.readouterr()
except AttributeError:
- return "", ""
+ return self._outerr
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
@@ -311,18 +273,25 @@
self.out.writeorg(out)
if err:
self.err.writeorg(err)
+ return out, err
- def suspend_capturing(self):
+ def suspend_capturing(self, in_=False):
if self.out:
self.out.suspend()
if self.err:
self.err.suspend()
+ if in_ and self.in_:
+ self.in_.suspend()
+ self._in_suspended = True
def resume_capturing(self):
if self.out:
self.out.resume()
if self.err:
self.err.resume()
+ if hasattr(self, "_in_suspended"):
+ self.in_.resume()
+ del self._in_suspended
def stop_capturing(self):
""" stop capturing and reset capturing streams """
@@ -393,7 +362,8 @@
res = py.builtin._totext(res, enc, "replace")
f.truncate(0)
f.seek(0)
- return res
+ return res
+ return ''
def done(self):
""" stop capturing, restore streams, return original capture file,
diff -r cccaaa5ff07fefada6e4929bdea33b1fa4edf7cc -r d9cd8600a979cd2442a2790edad12809d3d5d9ce _pytest/pdb.py
--- a/_pytest/pdb.py
+++ b/_pytest/pdb.py
@@ -34,7 +34,7 @@
if self._pluginmanager is not None:
capman = self._pluginmanager.getplugin("capturemanager")
if capman:
- capman.reset_capturings()
+ capman.suspendcapture(in_=True)
tw = py.io.TerminalWriter()
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
@@ -45,8 +45,8 @@
def pytest_exception_interact(self, node, call, report):
capman = node.config.pluginmanager.getplugin("capturemanager")
if capman:
- capman.reset_capturings()
- return _enter_pdb(node, call.excinfo, report)
+ capman.suspendcapture(in_=True)
+ _enter_pdb(node, call.excinfo, report)
def pytest_internalerror(self, excrepr, excinfo):
for line in str(excrepr).split("\n"):
diff -r cccaaa5ff07fefada6e4929bdea33b1fa4edf7cc -r d9cd8600a979cd2442a2790edad12809d3d5d9ce testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -53,79 +53,54 @@
class TestCaptureManager:
- def test_getmethod_default_no_fd(self, testdir, monkeypatch):
- config = testdir.parseconfig(testdir.tmpdir)
- assert config.getvalue("capture") is None
- capman = CaptureManager()
+ def test_getmethod_default_no_fd(self, monkeypatch):
+ from _pytest.capture import pytest_addoption
+ from _pytest.config import Parser
+ parser = Parser()
+ pytest_addoption(parser)
+ default = parser._groups[0].options[0].default
+ assert default == "fd" if hasattr(os, "dup") else "sys"
+ parser = Parser()
monkeypatch.delattr(os, 'dup', raising=False)
- try:
- assert capman._getmethod(config, None) == "sys"
- finally:
- monkeypatch.undo()
-
- @pytest.mark.parametrize("mode", "no fd sys".split())
- def test_configure_per_fspath(self, testdir, mode):
- config = testdir.parseconfig(testdir.tmpdir)
- capman = CaptureManager()
- hasfd = hasattr(os, 'dup')
- if hasfd:
- assert capman._getmethod(config, None) == "fd"
- else:
- assert capman._getmethod(config, None) == "sys"
-
- if not hasfd and mode == 'fd':
- return
- sub = testdir.tmpdir.mkdir("dir" + mode)
- sub.ensure("__init__.py")
- sub.join("conftest.py").write('option_capture = %r' % mode)
- assert capman._getmethod(config, sub.join("test_hello.py")) == mode
+ pytest_addoption(parser)
+ assert parser._groups[0].options[0].default == "sys"
@needsosdup
- @pytest.mark.parametrize("method", ['no', 'fd', 'sys'])
+ @pytest.mark.parametrize("method",
+ ['no', 'sys', pytest.mark.skipif('not hasattr(os, "dup")', 'fd')])
def test_capturing_basic_api(self, method):
capouter = StdCaptureFD()
old = sys.stdout, sys.stderr, sys.stdin
try:
- capman = CaptureManager()
- # call suspend without resume or start
- outerr = capman.suspendcapture()
+ capman = CaptureManager(method)
+ capman.init_capturings()
outerr = capman.suspendcapture()
assert outerr == ("", "")
- capman.resumecapture(method)
+ outerr = capman.suspendcapture()
+ assert outerr == ("", "")
print ("hello")
out, err = capman.suspendcapture()
if method == "no":
assert old == (sys.stdout, sys.stderr, sys.stdin)
else:
+ assert not out
+ capman.resumecapture()
+ print ("hello")
+ out, err = capman.suspendcapture()
+ if method != "no":
assert out == "hello\n"
- capman.resumecapture(method)
- out, err = capman.suspendcapture()
- assert not out and not err
capman.reset_capturings()
finally:
capouter.stop_capturing()
@needsosdup
- def test_juggle_capturings(self, testdir):
+ def test_init_capturing(self):
capouter = StdCaptureFD()
try:
- #config = testdir.parseconfig(testdir.tmpdir)
- capman = CaptureManager()
- try:
- capman.resumecapture("fd")
- pytest.raises(ValueError, 'capman.resumecapture("fd")')
- pytest.raises(ValueError, 'capman.resumecapture("sys")')
- os.write(1, "hello\n".encode('ascii'))
- out, err = capman.suspendcapture()
- assert out == "hello\n"
- capman.resumecapture("sys")
- os.write(1, "hello\n".encode('ascii'))
- py.builtin.print_("world", file=sys.stderr)
- out, err = capman.suspendcapture()
- assert not out
- assert err == "world\n"
- finally:
- capman.reset_capturings()
+ capman = CaptureManager("fd")
+ capman.init_capturings()
+ pytest.raises(AssertionError, "capman.init_capturings()")
+ capman.reset_capturings()
finally:
capouter.stop_capturing()
@@ -991,7 +966,7 @@
def test_close():
os.close(1)
def test_capture_again():
- os.write(1, "hello\\n")
+ os.write(1, b"hello\\n")
assert 0
""")
result = testdir.runpytest()
diff -r cccaaa5ff07fefada6e4929bdea33b1fa4edf7cc -r d9cd8600a979cd2442a2790edad12809d3d5d9ce testing/test_pdb.py
--- a/testing/test_pdb.py
+++ b/testing/test_pdb.py
@@ -167,6 +167,26 @@
if child.isalive():
child.wait()
+ def test_set_trace_capturing_afterwards(self, testdir):
+ p1 = testdir.makepyfile("""
+ import pdb
+ def test_1():
+ pdb.set_trace()
+ def test_2():
+ print ("hello")
+ assert 0
+ """)
+ child = testdir.spawn_pytest(str(p1))
+ child.expect("test_1")
+ child.send("c\n")
+ child.expect("test_2")
+ child.expect("Captured")
+ child.expect("hello")
+ child.sendeof()
+ child.read()
+ if child.isalive():
+ child.wait()
+
@xfail_if_pdbpp_installed
def test_pdb_interaction_doctest(self, testdir):
p1 = testdir.makepyfile("""
https://bitbucket.org/hpk42/pytest/commits/d534ac2d68ec/
Changeset: d534ac2d68ec
User: hpk42
Date: 2014-04-01 15:03:17
Summary: remove dupped_stdout logic and related changes, also simplify pytest_runtest_* calls to not use a contextlib with-decorator anymore.
Affected #: 7 files
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -26,6 +26,10 @@
- fix issue412: messing with stdout/stderr FD-level streams is now
captured without crashes.
+- simplified internal capturing mechanism and made it more robust
+ against tests or setups changing FD1/FD2, also better integrated
+ now with pytest.pdb() in single tests.
+
2.5.2
-----------------------------------
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -7,7 +7,6 @@
import sys
import os
from tempfile import TemporaryFile
-import contextlib
import py
import pytest
@@ -34,13 +33,9 @@
def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
ns = parser.parse_known_args(args)
pluginmanager = early_config.pluginmanager
- method = ns.capture
- if method != "no":
- dupped_stdout = safe_text_dupfile(sys.stdout, "wb")
- pluginmanager.register(dupped_stdout, "dupped_stdout")
- #pluginmanager.add_shutdown(dupped_stdout.close)
-
- capman = CaptureManager(method)
+ if ns.capture == "no":
+ return
+ capman = CaptureManager(ns.capture)
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
@@ -129,20 +124,23 @@
@pytest.mark.hookwrapper
def pytest_runtest_setup(self, item):
- with self.item_capture_wrapper(item, "setup"):
- yield
+ self.resumecapture()
+ yield
+ self.suspendcapture_item(item, "setup")
@pytest.mark.hookwrapper
def pytest_runtest_call(self, item):
- with self.item_capture_wrapper(item, "call"):
- self.activate_funcargs(item)
- yield
- #self.deactivate_funcargs() called from ctx's suspendcapture()
+ self.resumecapture()
+ self.activate_funcargs(item)
+ yield
+ #self.deactivate_funcargs() called from suspendcapture()
+ self.suspendcapture_item(item, "call")
@pytest.mark.hookwrapper
def pytest_runtest_teardown(self, item):
- with self.item_capture_wrapper(item, "teardown"):
- yield
+ self.resumecapture()
+ yield
+ self.suspendcapture_item(item, "teardown")
@pytest.mark.tryfirst
def pytest_keyboard_interrupt(self, excinfo):
@@ -152,10 +150,7 @@
def pytest_internalerror(self, excinfo):
self.reset_capturings()
- @contextlib.contextmanager
- def item_capture_wrapper(self, item, when):
- self.resumecapture()
- yield
+ def suspendcapture_item(self, item, when):
out, err = self.suspendcapture()
item.add_report_section(when, "out", out)
item.add_report_section(when, "err", err)
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d _pytest/genscript.py
--- a/_pytest/genscript.py
+++ b/_pytest/genscript.py
@@ -60,7 +60,6 @@
def pytest_cmdline_main(config):
genscript = config.getvalue("genscript")
if genscript:
- #tw = config.get_terminal_writer()
tw = py.io.TerminalWriter()
deps = ['py', '_pytest', 'pytest']
if sys.version_info < (2,7):
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -47,8 +47,6 @@
def pytest_cmdline_main(config):
if config.option.version:
- capman = config.pluginmanager.getplugin("capturemanager")
- capman.reset_capturings()
p = py.path.local(pytest.__file__)
sys.stderr.write("This is pytest version %s, imported from %s\n" %
(pytest.__version__, p))
@@ -64,7 +62,7 @@
return 0
def showhelp(config):
- tw = config.get_terminal_writer()
+ tw = py.io.TerminalWriter()
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -40,7 +40,7 @@
def pytest_cmdline_main(config):
if config.option.markers:
config.do_configure()
- tw = config.get_terminal_writer()
+ tw = py.io.TerminalWriter()
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -885,7 +885,7 @@
nodeid = "::".join(map(str, [curdir.bestrelpath(part[0])] + part[1:]))
nodeid.replace(session.fspath.sep, "/")
- tw = config.get_terminal_writer()
+ tw = py.io.TerminalWriter()
verbose = config.getvalue("verbose")
fm = session._fixturemanager
diff -r d9cd8600a979cd2442a2790edad12809d3d5d9ce -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -36,10 +36,7 @@
def pytest_configure(config):
config.option.verbose -= config.option.quiet
- out = config.pluginmanager.getplugin("dupped_stdout")
- #if out is None:
- # out = sys.stdout
- reporter = TerminalReporter(config, out)
+ reporter = TerminalReporter(config, sys.stdout)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
def mywriter(tags, args):
@@ -47,11 +44,6 @@
reporter.write_line("[traceconfig] " + msg)
config.trace.root.setprocessor("pytest:config", mywriter)
-def get_terminal_writer(config):
- tr = config.pluginmanager.getplugin("terminalreporter")
- return tr._tw
-
-
def getreportopt(config):
reportopts = ""
optvalue = config.option.report
@@ -104,7 +96,6 @@
self.startdir = self.curdir = py.path.local()
if file is None:
file = py.std.sys.stdout
- #assert file.encoding
self._tw = self.writer = py.io.TerminalWriter(file)
if self.config.option.color == 'yes':
self._tw.hasmarkup = True
https://bitbucket.org/hpk42/pytest/commits/f40e7ec514f3/
Changeset: f40e7ec514f3
User: hpk42
Date: 2014-04-01 15:06:44
Summary: merge main
Affected #: 4 files
diff -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d -r f40e7ec514f3fcf6e59c3e13a44c3da4eb0959bb AUTHORS
--- a/AUTHORS
+++ b/AUTHORS
@@ -40,3 +40,4 @@
Jurko Gospodnetić
Marc Schlaich
Christopher Gilling
+Daniel Grana
diff -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d -r f40e7ec514f3fcf6e59c3e13a44c3da4eb0959bb CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -26,11 +26,12 @@
- fix issue412: messing with stdout/stderr FD-level streams is now
captured without crashes.
+- fix issue483: trial/py33 works now properly. Thanks Daniel Grana for PR.
+
- simplified internal capturing mechanism and made it more robust
against tests or setups changing FD1/FD2, also better integrated
now with pytest.pdb() in single tests.
-
2.5.2
-----------------------------------
diff -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d -r f40e7ec514f3fcf6e59c3e13a44c3da4eb0959bb _pytest/unittest.py
--- a/_pytest/unittest.py
+++ b/_pytest/unittest.py
@@ -154,7 +154,7 @@
if isinstance(item, TestCaseFunction):
if 'twisted.trial.unittest' in sys.modules:
ut = sys.modules['twisted.python.failure']
- Failure__init__ = ut.Failure.__init__.im_func
+ Failure__init__ = ut.Failure.__init__
check_testcase_implements_trial_reporter()
def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
captureVars=None):
diff -r d534ac2d68ec7de2d920d0bbddc6077c7f4d267d -r f40e7ec514f3fcf6e59c3e13a44c3da4eb0959bb tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
distshare={homedir}/.tox/distshare
-envlist=flakes,py26,py27,pypy,py27-pexpect,py33-pexpect,py27-nobyte,py32,py33,py27-xdist,py33-xdist,trial
+envlist=flakes,py26,py27,pypy,py27-pexpect,py33-pexpect,py27-nobyte,py32,py33,py27-xdist,py33-xdist,py27-trial,py33-trial
[testenv]
changedir=testing
@@ -45,7 +45,7 @@
[testenv:py33-pexpect]
changedir=testing
-basepython=python2.7
+basepython=python3.3
deps={[testenv:py27-pexpect]deps}
commands=
py.test -rfsxX test_pdb.py test_terminal.py test_unittest.py
@@ -61,12 +61,22 @@
py.test -n3 -rfsxX \
--junitxml={envlogdir}/junit-{envname}.xml {posargs:testing}
-[testenv:trial]
+[testenv:py27-trial]
changedir=.
+basepython=python2.7
deps=twisted
commands=
py.test -rsxf \
--junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py}
+
+[testenv:py33-trial]
+changedir=.
+basepython=python3.3
+deps={[testenv:py27-trial]deps}
+commands=
+ py.test -rsxf \
+ --junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py}
+
[testenv:doctest]
changedir=.
commands=py.test --doctest-modules _pytest
Repository URL: https://bitbucket.org/hpk42/pytest/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
More information about the pytest-commit
mailing list