Skip to content

Commit

Permalink
Merge pull request #2266 from asottile/capture_v2
Browse files Browse the repository at this point in the history
Make capsys more like stdio streams in python3. Resolves #1407.
  • Loading branch information
nicoddemus authored Feb 24, 2017
2 parents 44ad369 + 8b598f0 commit 0f3d7ac
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 10 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Andrzej Ostrowski
Andy Freeland
Anthon van der Neut
Antony Lee
Anthony Sottile
Armin Rigo
Aron Curzon
Aviv Palivoda
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ Changes
Thanks `@The-Compiler`_ for the PR.


Bug Fixes
---------

* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer``
while using ``capsys`` fixture in python 3. (`#1407`_).
Thanks to `@asottile`_.


.. _@davidszotten: https://github.com/davidszotten
.. _@fushi: https://github.com/fushi
.. _@mattduck: https://github.com/mattduck
Expand All @@ -65,6 +73,7 @@ Changes
.. _@unsignedint: https://github.com/unsignedint
.. _@Kriechi: https://github.com/Kriechi

.. _#1407: https://github.com/pytest-dev/pytest/issues/1407
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
.. _#1952: https://github.com/pytest-dev/pytest/pull/1952
Expand Down
4 changes: 2 additions & 2 deletions _pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

import py
import pytest
from _pytest.compat import CaptureIO

from py.io import TextIO
unicode = py.builtin.text

patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
Expand Down Expand Up @@ -403,7 +403,7 @@ def __init__(self, fd, tmpfile=None):
if name == "stdin":
tmpfile = DontReadFromInput()
else:
tmpfile = TextIO()
tmpfile = CaptureIO()
self.tmpfile = tmpfile

def start(self):
Expand Down
16 changes: 16 additions & 0 deletions _pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,19 @@ def safe_str(v):
except UnicodeError:
errors = 'replace'
return v.encode('ascii', errors)


if _PY2:
from py.io import TextIO as CaptureIO
else:
import io

class CaptureIO(io.TextIOWrapper):
def __init__(self):
super(CaptureIO, self).__init__(
io.BytesIO(),
encoding='UTF-8', newline='', write_through=True,
)

def getvalue(self):
return self.buffer.getvalue().decode('UTF-8')
7 changes: 5 additions & 2 deletions _pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from py.builtin import print_

from _pytest.capture import MultiCapture, SysCapture
from _pytest._code import Source
import py
import pytest
Expand Down Expand Up @@ -737,7 +738,8 @@ def runpytest_inprocess(self, *args, **kwargs):
if kwargs.get("syspathinsert"):
self.syspathinsert()
now = time.time()
capture = py.io.StdCapture()
capture = MultiCapture(Capture=SysCapture)
capture.start_capturing()
try:
try:
reprec = self.inline_run(*args, **kwargs)
Expand All @@ -752,7 +754,8 @@ class reprec(object):
class reprec(object):
ret = 3
finally:
out, err = capture.reset()
out, err = capture.readouterr()
capture.stop_capturing()
sys.stdout.write(out)
sys.stderr.write(err)

Expand Down
24 changes: 18 additions & 6 deletions testing/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def test_logging_stream_ownership(self, testdir):
def test_logging():
import logging
import pytest
stream = capture.TextIO()
stream = capture.CaptureIO()
logging.basicConfig(stream=stream)
stream.close() # to free memory/release resources
""")
Expand Down Expand Up @@ -622,16 +622,16 @@ def bad_snap(self):
])


class TestTextIO(object):
class TestCaptureIO(object):
def test_text(self):
f = capture.TextIO()
f = capture.CaptureIO()
f.write("hello")
s = f.getvalue()
assert s == "hello"
f.close()

def test_unicode_and_str_mixture(self):
f = capture.TextIO()
f = capture.CaptureIO()
if sys.version_info >= (3, 0):
f.write("\u00f6")
pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))")
Expand All @@ -642,6 +642,18 @@ def test_unicode_and_str_mixture(self):
f.close()
assert isinstance(s, unicode)

@pytest.mark.skipif(
sys.version_info[0] == 2,
reason='python 3 only behaviour',
)
def test_write_bytes_to_buffer(self):
"""In python3, stdout / stderr are text io wrappers (exposing a buffer
property of the underlying bytestream). See issue #1407
"""
f = capture.CaptureIO()
f.buffer.write(b'foo\r\n')
assert f.getvalue() == 'foo\r\n'


def test_bytes_io():
f = py.io.BytesIO()
Expand Down Expand Up @@ -900,8 +912,8 @@ def test_capturing_modify_sysouterr_in_between(self):
with self.getcapture() as cap:
sys.stdout.write("hello")
sys.stderr.write("world")
sys.stdout = capture.TextIO()
sys.stderr = capture.TextIO()
sys.stdout = capture.CaptureIO()
sys.stderr = capture.CaptureIO()
print ("not seen")
sys.stderr.write("not seen\n")
out, err = cap.readouterr()
Expand Down

0 comments on commit 0f3d7ac

Please sign in to comment.