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

Fix crash when printing while capsysbinary is active #6926

Merged
merged 1 commit into from Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6871.bugfix.rst
@@ -0,0 +1 @@
Fix crash with captured output when using the :fixture:`capsysbinary fixture <capsysbinary>`.
14 changes: 11 additions & 3 deletions src/_pytest/capture.py
Expand Up @@ -570,8 +570,6 @@ def resume(self):

def writeorg(self, data):
""" write to original file descriptor. """
if isinstance(data, str):
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)


Expand All @@ -591,6 +589,11 @@ def snap(self):
self.tmpfile.truncate()
return res

def writeorg(self, data):
""" write to original file descriptor. """
data = data.encode("utf-8") # XXX use encoding of original stream
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm not related to this code, but curious how FDCaptureBinary still creates tmpfile with encoding="UTF-8"...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean why FDCaptureBinary wraps the temporary file with EncodedFile (== TextIOWrapper), it's for two reasons:

  1. In FD capturing, in addition to the FD redirection itself, it also does sys capturing. And the sys capturing requires a file object which looks like sys.stdout and friends, which are text-mode.
  2. The FDCapture subclass uses it in its snap() implementation.

I have some plans to untangle all of this, but thought it'd be better not to mix refactorings with bug fixes.

os.write(self.targetfd_save, data)


class SysCaptureBinary:

Expand Down Expand Up @@ -642,8 +645,9 @@ def resume(self):
self._state = "resumed"

def writeorg(self, data):
self._old.write(data)
self._old.flush()
self._old.buffer.write(data)
self._old.buffer.flush()


class SysCapture(SysCaptureBinary):
Expand All @@ -655,6 +659,10 @@ def snap(self):
self.tmpfile.truncate()
return res

def writeorg(self, data):
self._old.write(data)
self._old.flush()


class TeeSysCapture(SysCapture):
def __init__(self, fd, tmpfile=None):
Expand Down
36 changes: 29 additions & 7 deletions testing/test_capture.py
Expand Up @@ -515,18 +515,40 @@ def test_hello(capfdbinary):
reprec.assertoutcome(passed=1)

def test_capsysbinary(self, testdir):
reprec = testdir.inline_runsource(
"""\
p1 = testdir.makepyfile(
r"""
def test_hello(capsysbinary):
import sys
# some likely un-decodable bytes
sys.stdout.buffer.write(b'\\xfe\\x98\\x20')

sys.stdout.buffer.write(b'hello')

# Some likely un-decodable bytes.
sys.stdout.buffer.write(b'\xfe\x98\x20')

sys.stdout.buffer.flush()

# Ensure writing in text mode still works and is captured.
# https://github.com/pytest-dev/pytest/issues/6871
print("world", flush=True)

out, err = capsysbinary.readouterr()
assert out == b'\\xfe\\x98\\x20'
assert out == b'hello\xfe\x98\x20world\n'
assert err == b''

print("stdout after")
print("stderr after", file=sys.stderr)
"""
)
reprec.assertoutcome(passed=1)
result = testdir.runpytest(str(p1), "-rA")
result.stdout.fnmatch_lines(
[
"*- Captured stdout call -*",
"stdout after",
"*- Captured stderr call -*",
"stderr after",
"*= 1 passed in *",
]
)

def test_partial_setup_failure(self, testdir):
p = testdir.makepyfile(
Expand Down Expand Up @@ -890,7 +912,7 @@ def test_writeorg(self, tmpfile):
cap.start()
tmpfile.write(data1)
tmpfile.flush()
cap.writeorg(data2)
cap.writeorg(data2.decode("ascii"))
scap = cap.snap()
cap.done()
assert scap == data1.decode("ascii")
Expand Down