Skip to content

Commit 0f3d7ac

Browse files
authored
Merge pull request #2266 from asottile/capture_v2
Make capsys more like stdio streams in python3. Resolves #1407.
2 parents 44ad369 + 8b598f0 commit 0f3d7ac

File tree

6 files changed

+51
-10
lines changed

6 files changed

+51
-10
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Andrzej Ostrowski
1313
Andy Freeland
1414
Anthon van der Neut
1515
Antony Lee
16+
Anthony Sottile
1617
Armin Rigo
1718
Aron Curzon
1819
Aviv Palivoda

CHANGELOG.rst

+9
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ Changes
5555
Thanks `@The-Compiler`_ for the PR.
5656

5757

58+
Bug Fixes
59+
---------
60+
61+
* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer``
62+
while using ``capsys`` fixture in python 3. (`#1407`_).
63+
Thanks to `@asottile`_.
64+
65+
5866
.. _@davidszotten: https://github.com/davidszotten
5967
.. _@fushi: https://github.com/fushi
6068
.. _@mattduck: https://github.com/mattduck
@@ -65,6 +73,7 @@ Changes
6573
.. _@unsignedint: https://github.com/unsignedint
6674
.. _@Kriechi: https://github.com/Kriechi
6775

76+
.. _#1407: https://github.com/pytest-dev/pytest/issues/1407
6877
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
6978
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
7079
.. _#1952: https://github.com/pytest-dev/pytest/pull/1952

_pytest/capture.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
import py
1414
import pytest
15+
from _pytest.compat import CaptureIO
1516

16-
from py.io import TextIO
1717
unicode = py.builtin.text
1818

1919
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@@ -403,7 +403,7 @@ def __init__(self, fd, tmpfile=None):
403403
if name == "stdin":
404404
tmpfile = DontReadFromInput()
405405
else:
406-
tmpfile = TextIO()
406+
tmpfile = CaptureIO()
407407
self.tmpfile = tmpfile
408408

409409
def start(self):

_pytest/compat.py

+16
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,19 @@ def safe_str(v):
251251
except UnicodeError:
252252
errors = 'replace'
253253
return v.encode('ascii', errors)
254+
255+
256+
if _PY2:
257+
from py.io import TextIO as CaptureIO
258+
else:
259+
import io
260+
261+
class CaptureIO(io.TextIOWrapper):
262+
def __init__(self):
263+
super(CaptureIO, self).__init__(
264+
io.BytesIO(),
265+
encoding='UTF-8', newline='', write_through=True,
266+
)
267+
268+
def getvalue(self):
269+
return self.buffer.getvalue().decode('UTF-8')

_pytest/pytester.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from py.builtin import print_
1616

17+
from _pytest.capture import MultiCapture, SysCapture
1718
from _pytest._code import Source
1819
import py
1920
import pytest
@@ -737,7 +738,8 @@ def runpytest_inprocess(self, *args, **kwargs):
737738
if kwargs.get("syspathinsert"):
738739
self.syspathinsert()
739740
now = time.time()
740-
capture = py.io.StdCapture()
741+
capture = MultiCapture(Capture=SysCapture)
742+
capture.start_capturing()
741743
try:
742744
try:
743745
reprec = self.inline_run(*args, **kwargs)
@@ -752,7 +754,8 @@ class reprec(object):
752754
class reprec(object):
753755
ret = 3
754756
finally:
755-
out, err = capture.reset()
757+
out, err = capture.readouterr()
758+
capture.stop_capturing()
756759
sys.stdout.write(out)
757760
sys.stderr.write(err)
758761

testing/test_capture.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ def test_logging_stream_ownership(self, testdir):
281281
def test_logging():
282282
import logging
283283
import pytest
284-
stream = capture.TextIO()
284+
stream = capture.CaptureIO()
285285
logging.basicConfig(stream=stream)
286286
stream.close() # to free memory/release resources
287287
""")
@@ -622,16 +622,16 @@ def bad_snap(self):
622622
])
623623

624624

625-
class TestTextIO(object):
625+
class TestCaptureIO(object):
626626
def test_text(self):
627-
f = capture.TextIO()
627+
f = capture.CaptureIO()
628628
f.write("hello")
629629
s = f.getvalue()
630630
assert s == "hello"
631631
f.close()
632632

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

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

646658
def test_bytes_io():
647659
f = py.io.BytesIO()
@@ -900,8 +912,8 @@ def test_capturing_modify_sysouterr_in_between(self):
900912
with self.getcapture() as cap:
901913
sys.stdout.write("hello")
902914
sys.stderr.write("world")
903-
sys.stdout = capture.TextIO()
904-
sys.stderr = capture.TextIO()
915+
sys.stdout = capture.CaptureIO()
916+
sys.stderr = capture.CaptureIO()
905917
print ("not seen")
906918
sys.stderr.write("not seen\n")
907919
out, err = cap.readouterr()

0 commit comments

Comments
 (0)