Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add readouterr_bytes for retrieving captured binary output #2279

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ New Features
* ``pytest.raises`` now asserts that the error message matches a text or regex
with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR.

* ``capsys`` and ``capfd`` fixtures now expose a ``readouterr_bytes()`` method
which returns captured output as bytes. Thanks `@asottile`_ for the PR.


Changes
-------
Expand Down
27 changes: 27 additions & 0 deletions _pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ def readouterr(self):
except AttributeError:
return self._outerr

def readouterr_bytes(self):
try:
return self._capture.readouterr_bytes()
except AttributeError:
return self._outerr

@contextlib.contextmanager
def disabled(self):
capmanager = self.request.config.pluginmanager.getplugin('capturemanager')
Expand Down Expand Up @@ -315,6 +321,12 @@ def readouterr(self):
return (self.out.snap() if self.out is not None else "",
self.err.snap() if self.err is not None else "")

def readouterr_bytes(self):
""" return snapshot bytes value of stdout/stderr capturings. """
return (self.out.snap_bytes() if self.out is not None else b'',
self.err.snap_bytes() if self.err is not None else b'')


class NoCapture(object):
__init__ = start = done = suspend = resume = lambda *args: None

Expand Down Expand Up @@ -370,6 +382,14 @@ def snap(self):
return res
return ''

def snap_bytes(self):
f = self.tmpfile
f.seek(0)
res = f.read()
f.truncate(0)
f.seek(0)
return res

def done(self):
""" stop capturing, restore streams, return original capture file,
seeked to position zero. """
Expand Down Expand Up @@ -416,6 +436,13 @@ def snap(self):
f.seek(0)
return res

def snap_bytes(self):
f = getattr(self.tmpfile, 'buffer', self.tmpfile)
res = f.getvalue()
f.truncate(0)
f.seek(0)
return res

def done(self):
setattr(sys, self.name, self._old)
del self._old
Expand Down
10 changes: 10 additions & 0 deletions testing/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,16 @@ def test_capturing_readouterr_decode_error_handling(self):
out, err = cap.readouterr()
assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')

def test_capturing_bytes(self):
some_bytes = b'\xf1\xe7\x12\r\n'
with self.getcapture() as cap:
# Write bytes to output in a py2+py3
getattr(sys.stdout, 'buffer', sys.stdout).write(some_bytes)
getattr(sys.stderr, 'buffer', sys.stderr).write(some_bytes)
out, err = cap.readouterr_bytes()
assert out == some_bytes
assert err == some_bytes

def test_reset_twice_error(self):
with self.getcapture() as cap:
print ("hello")
Expand Down