diff --git a/AUTHORS b/AUTHORS index e9e7f7c82..d17963e73 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 23b87b18c..ee3eae097 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 @@ -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 diff --git a/_pytest/capture.py b/_pytest/capture.py index 3fe1816d8..6a1cae41d 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -12,8 +12,8 @@ from tempfile import TemporaryFile 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'} @@ -403,7 +403,7 @@ class SysCapture(object): 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 e097dee51..09df385d1 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -251,3 +251,19 @@ else: 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/testing/test_capture.py b/testing/test_capture.py index 365329f59..364281fe6 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -281,7 +281,7 @@ class TestLoggingInteraction(object): 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 test_error_during_readouterr(testdir): ]) -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'))") @@ -642,6 +642,18 @@ class TestTextIO(object): 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() @@ -900,8 +912,8 @@ class TestStdCapture(object): 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()