Merge pull request #2925 from asottile/capfdbinary
Add capfdbinary fixture
This commit is contained in:
commit
6161bcff6e
|
@ -180,17 +180,29 @@ class CaptureManager:
|
|||
item.add_report_section(when, "stderr", err)
|
||||
|
||||
|
||||
error_capsysfderror = "cannot use capsys and capfd at the same time"
|
||||
capture_fixtures = {'capfd', 'capfdbinary', 'capsys'}
|
||||
|
||||
|
||||
def _ensure_only_one_capture_fixture(request, name):
|
||||
fixtures = set(request.fixturenames) & capture_fixtures - set((name,))
|
||||
if fixtures:
|
||||
fixtures = sorted(fixtures)
|
||||
fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
|
||||
raise request.raiseerror(
|
||||
"cannot use {0} and {1} at the same time".format(
|
||||
fixtures, name,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def capsys(request):
|
||||
"""Enable capturing of writes to sys.stdout/sys.stderr and make
|
||||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple.
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
|
||||
objects.
|
||||
"""
|
||||
if "capfd" in request.fixturenames:
|
||||
raise request.raiseerror(error_capsysfderror)
|
||||
_ensure_only_one_capture_fixture(request, 'capsys')
|
||||
with _install_capture_fixture_on_item(request, SysCapture) as fixture:
|
||||
yield fixture
|
||||
|
||||
|
@ -199,16 +211,30 @@ def capsys(request):
|
|||
def capfd(request):
|
||||
"""Enable capturing of writes to file descriptors 1 and 2 and make
|
||||
captured output available via ``capfd.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple.
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
|
||||
objects.
|
||||
"""
|
||||
if "capsys" in request.fixturenames:
|
||||
request.raiseerror(error_capsysfderror)
|
||||
_ensure_only_one_capture_fixture(request, 'capfd')
|
||||
if not hasattr(os, 'dup'):
|
||||
pytest.skip("capfd fixture needs os.dup function which is not available in this system")
|
||||
with _install_capture_fixture_on_item(request, FDCapture) as fixture:
|
||||
yield fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def capfdbinary(request):
|
||||
"""Enable capturing of write to file descriptors 1 and 2 and make
|
||||
captured output available via ``capfdbinary.readouterr`` method calls
|
||||
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
|
||||
``bytes`` objects.
|
||||
"""
|
||||
_ensure_only_one_capture_fixture(request, 'capfdbinary')
|
||||
if not hasattr(os, 'dup'):
|
||||
pytest.skip("capfdbinary fixture needs os.dup function which is not available in this system")
|
||||
with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
|
||||
yield fixture
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _install_capture_fixture_on_item(request, capture_class):
|
||||
"""
|
||||
|
@ -378,8 +404,11 @@ class NoCapture:
|
|||
__init__ = start = done = suspend = resume = lambda *args: None
|
||||
|
||||
|
||||
class FDCapture:
|
||||
""" Capture IO to/from a given os-level filedescriptor. """
|
||||
class FDCaptureBinary:
|
||||
"""Capture IO to/from a given os-level filedescriptor.
|
||||
|
||||
snap() produces `bytes`
|
||||
"""
|
||||
|
||||
def __init__(self, targetfd, tmpfile=None):
|
||||
self.targetfd = targetfd
|
||||
|
@ -418,17 +447,11 @@ class FDCapture:
|
|||
self.syscapture.start()
|
||||
|
||||
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 = six.text_type(res, enc, "replace")
|
||||
f.truncate(0)
|
||||
f.seek(0)
|
||||
return res
|
||||
return ''
|
||||
self.tmpfile.seek(0)
|
||||
res = self.tmpfile.read()
|
||||
self.tmpfile.seek(0)
|
||||
self.tmpfile.truncate()
|
||||
return res
|
||||
|
||||
def done(self):
|
||||
""" stop capturing, restore streams, return original capture file,
|
||||
|
@ -454,6 +477,19 @@ class FDCapture:
|
|||
os.write(self.targetfd_save, data)
|
||||
|
||||
|
||||
class FDCapture(FDCaptureBinary):
|
||||
"""Capture IO to/from a given os-level filedescriptor.
|
||||
|
||||
snap() produces text
|
||||
"""
|
||||
def snap(self):
|
||||
res = FDCaptureBinary.snap(self)
|
||||
enc = getattr(self.tmpfile, "encoding", None)
|
||||
if enc and isinstance(res, bytes):
|
||||
res = six.text_type(res, enc, "replace")
|
||||
return res
|
||||
|
||||
|
||||
class SysCapture:
|
||||
def __init__(self, fd, tmpfile=None):
|
||||
name = patchsysdict[fd]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add ``capfdbinary`` a version of ``capfd`` which returns bytes from
|
||||
``readouterr()``.
|
|
@ -85,9 +85,9 @@ of the failing function and hide the other one::
|
|||
Accessing captured output from a test function
|
||||
---------------------------------------------------
|
||||
|
||||
The ``capsys`` and ``capfd`` fixtures allow to access stdout/stderr
|
||||
output created during test execution. Here is an example test function
|
||||
that performs some output related checks:
|
||||
The ``capsys``, ``capfd``, and ``capfdbinary`` fixtures allow access to
|
||||
stdout/stderr output created during test execution. Here is an example test
|
||||
function that performs some output related checks:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -110,11 +110,17 @@ output streams and also interacts well with pytest's
|
|||
own per-test capturing.
|
||||
|
||||
If you want to capture on filedescriptor level you can use
|
||||
the ``capfd`` function argument which offers the exact
|
||||
the ``capfd`` fixture which offers the exact
|
||||
same interface but allows to also capture output from
|
||||
libraries or subprocesses that directly write to operating
|
||||
system level output streams (FD1 and FD2).
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
If the code under test writes non-textual data, you can capture this using
|
||||
the ``capfdbinary`` fixture which instead returns ``bytes`` from
|
||||
the ``readouterr`` method.
|
||||
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
|
|
|
@ -398,7 +398,7 @@ class TestCaptureFixture(object):
|
|||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*ERROR*setup*test_one*",
|
||||
"E*capsys*capfd*same*time*",
|
||||
"E*capfd*capsys*same*time*",
|
||||
"*ERROR*setup*test_two*",
|
||||
"E*capsys*capfd*same*time*",
|
||||
"*2 error*"])
|
||||
|
@ -418,10 +418,21 @@ class TestCaptureFixture(object):
|
|||
"*test_one*",
|
||||
"*capsys*capfd*same*time*",
|
||||
"*test_two*",
|
||||
"*capsys*capfd*same*time*",
|
||||
"*capfd*capsys*same*time*",
|
||||
"*2 failed in*",
|
||||
])
|
||||
|
||||
def test_capsyscapfdbinary(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def test_one(capsys, capfdbinary):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*ERROR*setup*test_one*",
|
||||
"E*capfdbinary*capsys*same*time*",
|
||||
"*1 error*"])
|
||||
|
||||
@pytest.mark.parametrize("method", ["sys", "fd"])
|
||||
def test_capture_is_represented_on_failure_issue128(self, testdir, method):
|
||||
p = testdir.makepyfile("""
|
||||
|
@ -446,6 +457,19 @@ class TestCaptureFixture(object):
|
|||
""")
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
@needsosdup
|
||||
def test_capfdbinary(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
def test_hello(capfdbinary):
|
||||
import os
|
||||
# some likely un-decodable bytes
|
||||
os.write(1, b'\\xfe\\x98\\x20')
|
||||
out, err = capfdbinary.readouterr()
|
||||
assert out == b'\\xfe\\x98\\x20'
|
||||
assert err == b''
|
||||
""")
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_partial_setup_failure(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def test_hello(capsys, missingarg):
|
||||
|
|
Loading…
Reference in New Issue