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

SysCaptureBinary: decode in writeorg #6880

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6871.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix crash with captured output when using the :fixture:`capsysbinary fixture <capsysbinary>`.
7 changes: 6 additions & 1 deletion src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,8 @@ def resume(self):
setattr(sys, self.name, self.tmpfile)
self._state = "resumed"

def writeorg(self, data):
def writeorg(self, data: str) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

This needs to be bytes. It seems like the data = data.... confused mypy so it didn't catch it.

data = data.decode(self._old.encoding)
self._old.write(data)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe write to self._old.buffer instead of doing the decode? Although it might not exist in case of "nested" patching of sys.stdout and friends (e.g. if someone patches sys.stdout to a io.StringIO, which doesn't have an underlying buffer, and then tries to capture it -- probably not a proper thing to do).

self._old.flush()

Expand All @@ -695,6 +696,10 @@ def snap(self):
self.tmpfile.truncate()
return res

def writeorg(self, data: str) -> None:
self._old.write(data)
self._old.flush()


class TeeSysCapture(SysCapture):
def __init__(self, fd, tmpfile=None):
Expand Down
19 changes: 17 additions & 2 deletions testing/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,18 +542,33 @@ def test_hello(capfdbinary):
reprec.assertoutcome(passed=1)

def test_capsysbinary(self, testdir):
reprec = testdir.inline_runsource(
p1 = testdir.makepyfile(
"""\
def test_hello(capsysbinary):
import sys

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

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

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

def test_partial_setup_failure(self, testdir):
p = testdir.makepyfile(
Expand Down