Skip to content

Commit 219b758

Browse files
committed
Add capsysbinary fixture
`capsysbinary` works like `capsys` but produces bytes for `readouterr()`.
1 parent 6161bcf commit 219b758

File tree

4 files changed

+76
-10
lines changed

4 files changed

+76
-10
lines changed

_pytest/capture.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def suspend_capture_item(self, item, when, in_=False):
180180
item.add_report_section(when, "stderr", err)
181181

182182

183-
capture_fixtures = {'capfd', 'capfdbinary', 'capsys'}
183+
capture_fixtures = {'capfd', 'capfdbinary', 'capsys', 'capsysbinary'}
184184

185185

186186
def _ensure_only_one_capture_fixture(request, name):
@@ -207,6 +207,22 @@ def capsys(request):
207207
yield fixture
208208

209209

210+
@pytest.fixture
211+
def capsysbinary(request):
212+
"""Enable capturing of writes to sys.stdout/sys.stderr and make
213+
captured output available via ``capsys.readouterr()`` method calls
214+
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
215+
objects.
216+
"""
217+
_ensure_only_one_capture_fixture(request, 'capsysbinary')
218+
# Currently, the implementation uses the python3 specific `.buffer`
219+
# property of CaptureIO.
220+
if sys.version_info < (3,):
221+
raise request.raiseerror('capsysbinary is only supported on python 3')
222+
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
223+
yield fixture
224+
225+
210226
@pytest.fixture
211227
def capfd(request):
212228
"""Enable capturing of writes to file descriptors 1 and 2 and make
@@ -506,10 +522,9 @@ def start(self):
506522
setattr(sys, self.name, self.tmpfile)
507523

508524
def snap(self):
509-
f = self.tmpfile
510-
res = f.getvalue()
511-
f.truncate(0)
512-
f.seek(0)
525+
res = self.tmpfile.getvalue()
526+
self.tmpfile.seek(0)
527+
self.tmpfile.truncate()
513528
return res
514529

515530
def done(self):
@@ -528,6 +543,14 @@ def writeorg(self, data):
528543
self._old.flush()
529544

530545

546+
class SysCaptureBinary(SysCapture):
547+
def snap(self):
548+
res = self.tmpfile.buffer.getvalue()
549+
self.tmpfile.seek(0)
550+
self.tmpfile.truncate()
551+
return res
552+
553+
531554
class DontReadFromInput:
532555
"""Temporary stub class. Ideally when stdin is accessed, the
533556
capturing should be turned off, with possibly all data captured

changelog/2934.feature

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``capsysbinary`` a version of ``capsys`` which returns bytes from
2+
``readouterr()``.

doc/en/capture.rst

+13-4
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ of the failing function and hide the other one::
8585
Accessing captured output from a test function
8686
---------------------------------------------------
8787

88-
The ``capsys``, ``capfd``, and ``capfdbinary`` fixtures allow access to
89-
stdout/stderr output created during test execution. Here is an example test
90-
function that performs some output related checks:
88+
The ``capsys``, ``capsysbinary``, ``capfd``, and ``capfdbinary`` fixtures
89+
allow access to stdout/stderr output created during test execution. Here is
90+
an example test function that performs some output related checks:
9191

9292
.. code-block:: python
9393
@@ -115,11 +115,20 @@ same interface but allows to also capture output from
115115
libraries or subprocesses that directly write to operating
116116
system level output streams (FD1 and FD2).
117117

118+
.. versionadded:: 3.3
119+
120+
If the code under test writes non-textual data, you can capture this using
121+
the ``capsysbinary`` fixture which instead returns ``bytes`` from
122+
the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only
123+
available in python 3.
124+
125+
118126
.. versionadded:: 3.3
119127

120128
If the code under test writes non-textual data, you can capture this using
121129
the ``capfdbinary`` fixture which instead returns ``bytes`` from
122-
the ``readouterr`` method.
130+
the ``readouterr`` method. The ``capfdbinary`` fixture operates on the
131+
filedescriptor level.
123132

124133

125134
.. versionadded:: 3.0

testing/test_capture.py

+33-1
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,38 @@ def test_hello(capfdbinary):
470470
""")
471471
reprec.assertoutcome(passed=1)
472472

473+
@pytest.mark.skipif(
474+
sys.version_info < (3,),
475+
reason='only have capsysbinary in python 3',
476+
)
477+
def test_capsysbinary(self, testdir):
478+
reprec = testdir.inline_runsource("""
479+
def test_hello(capsysbinary):
480+
import sys
481+
# some likely un-decodable bytes
482+
sys.stdout.buffer.write(b'\\xfe\\x98\\x20')
483+
out, err = capsysbinary.readouterr()
484+
assert out == b'\\xfe\\x98\\x20'
485+
assert err == b''
486+
""")
487+
reprec.assertoutcome(passed=1)
488+
489+
@pytest.mark.skipif(
490+
sys.version_info >= (3,),
491+
reason='only have capsysbinary in python 3',
492+
)
493+
def test_capsysbinary_forbidden_in_python2(self, testdir):
494+
testdir.makepyfile("""
495+
def test_hello(capsysbinary):
496+
pass
497+
""")
498+
result = testdir.runpytest()
499+
result.stdout.fnmatch_lines([
500+
"*test_hello*",
501+
"*capsysbinary is only supported on python 3*",
502+
"*1 error in*",
503+
])
504+
473505
def test_partial_setup_failure(self, testdir):
474506
p = testdir.makepyfile("""
475507
def test_hello(capsys, missingarg):
@@ -1233,7 +1265,7 @@ def test_capattr():
12331265
reprec.assertoutcome(passed=1)
12341266

12351267

1236-
def test_pickling_and_unpickling_enocded_file():
1268+
def test_pickling_and_unpickling_encoded_file():
12371269
# See https://bitbucket.org/pytest-dev/pytest/pull-request/194
12381270
# pickle.loads() raises infinite recursion if
12391271
# EncodedFile.__getattr__ is not implemented properly

0 commit comments

Comments
 (0)