diff --git a/AUTHORS b/AUTHORS index b09516a1dd0..2bc304981c8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Andrzej Ostrowski Andy Freeland Anthon van der Neut Antony Lee +Anthony Sottile Armin Rigo Aron Curzon Aviv Palivoda diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0706243787..fe78210854c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ 3.0.7 (unreleased) ================== +* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer`` + while using ``capsys`` fixture in python 3. (`#1407`_). + Thanks to `@asottile`_. + * Fix regression, pytest now skips unittest correctly if run with ``--pdb`` (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. @@ -32,6 +36,7 @@ .. _@sirex: https://github.com/sirex .. _@vidartf: https://github.com/vidartf +.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 .. _#2137: https://github.com/pytest-dev/pytest/issues/2137 .. _#2160: https://github.com/pytest-dev/pytest/issues/2160 .. _#2231: https://github.com/pytest-dev/pytest/issues/2231 diff --git a/_pytest/capture.py b/_pytest/capture.py index eea81ca187d..9b0e35ac7da 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -11,8 +11,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'} @@ -402,7 +402,7 @@ def __init__(self, fd, tmpfile=None): if name == "stdin": tmpfile = DontReadFromInput() else: - tmpfile = TextIO() + tmpfile = CaptureIO() self.tmpfile = tmpfile def start(self): diff --git a/_pytest/compat.py b/_pytest/compat.py index d278b89cd44..a4022f8d094 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -239,3 +239,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') diff --git a/_pytest/pytester.py b/_pytest/pytester.py index de24f904442..017e6631137 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -12,6 +12,7 @@ from py.builtin import print_ +from _pytest.capture import MultiCapture, SysCapture from _pytest._code import Source import py import pytest @@ -734,7 +735,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) @@ -749,7 +751,8 @@ class reprec: class reprec: ret = 3 finally: - out, err = capture.reset() + out, err = capture.readouterr() + capture.stop_capturing() sys.stdout.write(out) sys.stderr.write(err) diff --git a/testing/test_capture.py b/testing/test_capture.py index 763e28315df..b7375775385 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -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 """) @@ -622,16 +622,16 @@ def bad_snap(self): ]) -class TestTextIO: +class TestCaptureIO: 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'))") @@ -642,6 +642,13 @@ def test_unicode_and_str_mixture(self): f.close() assert isinstance(s, unicode) + def test_write_bytes_to_buffer(self): + f = capture.CaptureIO() + # In python3, stdout / stderr are text io wrappers (exposing a buffer + # property of the underlying bytestream). + getattr(f, 'buffer', f).write(b'foo\n') + assert f.getvalue() == 'foo\n' + def test_bytes_io(): f = py.io.BytesIO() @@ -900,8 +907,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()