Skip to content

Commit e3e10bd

Browse files
blueyedbluetech
authored andcommitted
Fix crash when printing while capsysbinary is active
Previously, writing to sys.stdout/stderr in text-mode (e.g. `print('foo')`) while a `capsysbinary` fixture is active, would crash with: /usr/lib/python3.7/contextlib.py:119: in __exit__ next(self.gen) E TypeError: write() argument must be str, not bytes This is due to some confusion in the types. The relevant functions are `snap()` and `writeorg()`. The function `snap()` returns what was captured, and the return type should be `bytes` for the binary captures and `str` for the regular ones. The `snap()` return value is eventually passed to `writeorg()` to be written to the original file, so it's input type should correspond to `snap()`. But this was incorrect for `SysCaptureBinary`, which handled it like `str`. To fix this, be explicit in the `snap()` and `writeorg()` implementations, also of the other Capture types. We can't add type annotations yet, because the current inheritance scheme breaks Liskov Substitution and mypy would complain. To be refactored later. Fixes: #6871 Co-authored-by: Ran Benita (some modifications & commit message)
1 parent 1d244b3 commit e3e10bd

File tree

3 files changed

+30
-6
lines changed

3 files changed

+30
-6
lines changed

changelog/6871.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix crash with captured output when using the :fixture:`capsysbinary fixture <capsysbinary>`.

src/_pytest/capture.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,8 +570,6 @@ def resume(self):
570570

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

577575

@@ -591,6 +589,11 @@ def snap(self):
591589
self.tmpfile.truncate()
592590
return res
593591

592+
def writeorg(self, data):
593+
""" write to original file descriptor. """
594+
data = data.encode("utf-8") # XXX use encoding of original stream
595+
os.write(self.targetfd_save, data)
596+
594597

595598
class SysCaptureBinary:
596599

@@ -642,8 +645,9 @@ def resume(self):
642645
self._state = "resumed"
643646

644647
def writeorg(self, data):
645-
self._old.write(data)
646648
self._old.flush()
649+
self._old.buffer.write(data)
650+
self._old.buffer.flush()
647651

648652

649653
class SysCapture(SysCaptureBinary):
@@ -655,6 +659,10 @@ def snap(self):
655659
self.tmpfile.truncate()
656660
return res
657661

662+
def writeorg(self, data):
663+
self._old.write(data)
664+
self._old.flush()
665+
658666

659667
class TeeSysCapture(SysCapture):
660668
def __init__(self, fd, tmpfile=None):

testing/test_capture.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,18 +515,33 @@ def test_hello(capfdbinary):
515515
reprec.assertoutcome(passed=1)
516516

517517
def test_capsysbinary(self, testdir):
518-
reprec = testdir.inline_runsource(
518+
p1 = testdir.makepyfile(
519519
"""\
520520
def test_hello(capsysbinary):
521521
import sys
522+
522523
# some likely un-decodable bytes
523524
sys.stdout.buffer.write(b'\\xfe\\x98\\x20')
525+
524526
out, err = capsysbinary.readouterr()
525527
assert out == b'\\xfe\\x98\\x20'
526528
assert err == b''
529+
530+
# handles writing strings
531+
print("hello")
532+
print("hello stderr", file=sys.stderr)
527533
"""
528534
)
529-
reprec.assertoutcome(passed=1)
535+
result = testdir.runpytest(str(p1), "-rA")
536+
result.stdout.fnmatch_lines(
537+
[
538+
"*- Captured stdout call -*",
539+
"hello",
540+
"*- Captured stderr call -*",
541+
"hello stderr",
542+
"*= 1 passed in *",
543+
]
544+
)
530545

531546
def test_partial_setup_failure(self, testdir):
532547
p = testdir.makepyfile(
@@ -890,7 +905,7 @@ def test_writeorg(self, tmpfile):
890905
cap.start()
891906
tmpfile.write(data1)
892907
tmpfile.flush()
893-
cap.writeorg(data2)
908+
cap.writeorg(data2.decode("ascii"))
894909
scap = cap.snap()
895910
cap.done()
896911
assert scap == data1.decode("ascii")

0 commit comments

Comments
 (0)