Skip to content

Commit 8ee8a41

Browse files
committed
Rebase and squash.
1 parent 706ea86 commit 8ee8a41

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

src/_pytest/capture.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,11 +358,17 @@ def close(self):
358358
self._capture.stop_capturing()
359359
self._capture = None
360360

361-
def readouterr(self):
361+
def readouterr(self, combined=False, flush=True):
362362
"""Read and return the captured output so far, resetting the internal buffer.
363363
364364
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
365365
"""
366+
if combined:
367+
return CaptureResult(self._get_combined(), None)
368+
if flush is False:
369+
if self.captureclass is not OrderedCapture:
370+
raise AttributeError("Only capsys can read streams without flushing.")
371+
OrderedCapture.set_flush(False)
366372
captured_out, captured_err = self._captured_out, self._captured_err
367373
if self._capture is not None:
368374
out, err = self._capture.readouterr()
@@ -372,6 +378,13 @@ def readouterr(self):
372378
self._captured_err = self.captureclass.EMPTY_BUFFER
373379
return CaptureResult(captured_out, captured_err)
374380

381+
def _get_combined(self):
382+
if self.captureclass is not OrderedCapture:
383+
raise AttributeError("Only capsys is able to combine streams.")
384+
result = "".join(line[0] for line in OrderedCapture.streams)
385+
OrderedCapture.flush()
386+
return result
387+
375388
def _suspend(self):
376389
"""Suspends this fixture's own capturing temporarily."""
377390
if self._capture is not None:
@@ -637,12 +650,16 @@ def __init__(self, fd, tmpfile=None):
637650
name = patchsysdict[fd]
638651
self._old = getattr(sys, name)
639652
self.name = name
653+
self.fd = fd
640654
if tmpfile is None:
641655
if name == "stdin":
642656
tmpfile = DontReadFromInput()
643657
else:
644-
tmpfile = CaptureIO()
658+
tmpfile = self._get_writer()
645659
self.tmpfile = tmpfile
660+
661+
def _get_writer(self):
662+
return CaptureIO()
646663

647664
def __repr__(self):
648665
return "<{} {} _old={} _state={!r} tmpfile={!r}>".format(
@@ -705,14 +722,65 @@ def __init__(self, fd, tmpfile=None):
705722
self.tmpfile = tmpfile
706723

707724

725+
726+
class OrderedCapture(SysCapture):
727+
"""Capture class that keeps streams in order."""
728+
streams = collections.deque()
729+
_flush = True
730+
731+
def _get_writer(self):
732+
return OrderedWriter(self.fd)
733+
734+
def snap(self):
735+
res = self.tmpfile.getvalue()
736+
if self.name == "stderr":
737+
# both streams are being read one after another, while stderr is last - it will clear the queue
738+
self.flush()
739+
return res
740+
741+
@classmethod
742+
def set_flush(cls, flush: bool) -> None:
743+
cls._flush = flush
744+
745+
@classmethod
746+
def flush(cls) -> None:
747+
"""Clear streams. """
748+
if cls._flush is False:
749+
cls.set_flush(True)
750+
else:
751+
cls.streams.clear()
752+
753+
@classmethod
754+
def close(cls) -> None:
755+
cls.set_flush(True)
756+
cls.flush()
757+
708758
map_fixname_class = {
709759
"capfd": FDCapture,
710760
"capfdbinary": FDCaptureBinary,
711-
"capsys": SysCapture,
761+
"capsys": OrderedCapture,
712762
"capsysbinary": SysCaptureBinary,
713763
}
714764

715765

766+
class OrderedWriter:
767+
encoding = sys.getdefaultencoding()
768+
769+
def __init__(self, fd: int) -> None:
770+
super().__init__()
771+
self._fd = fd # type: int
772+
773+
def write(self, text, **kwargs):
774+
OrderedCapture.streams.append((text, self._fd))
775+
return len(text)
776+
777+
def getvalue(self):
778+
return "".join((line[0] for line in OrderedCapture.streams if line[1] == self._fd))
779+
780+
def close(self):
781+
OrderedCapture.close()
782+
783+
716784
class DontReadFromInput:
717785
encoding = None
718786

testing/test_capture.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,3 +1572,34 @@ def test__get_multicapture() -> None:
15721572
pytest.raises(ValueError, _get_multicapture, "unknown").match(
15731573
r"^unknown capturing method: 'unknown'"
15741574
)
1575+
1576+
def test_combined_streams(capsys):
1577+
"""Show that capsys is capable of preserving chronological order of streams."""
1578+
print("stdout1")
1579+
print("stdout2")
1580+
print("stderr1", file=sys.stderr)
1581+
print("stdout3")
1582+
print("stderr2", file=sys.stderr)
1583+
print("stderr3", file=sys.stderr)
1584+
print("stdout4")
1585+
print("stdout5")
1586+
1587+
# this won't clear streams
1588+
out, err = capsys.readouterr(flush=False)
1589+
assert "stdout1" in out
1590+
assert "stdout1" not in err
1591+
assert "stderr1" in err
1592+
assert "stderr1" not in out
1593+
1594+
out, err = capsys.readouterr(combined=True)
1595+
assert (
1596+
out == "stdout1\n"
1597+
"stdout2\n"
1598+
"stderr1\n"
1599+
"stdout3\n"
1600+
"stderr2\n"
1601+
"stderr3\n"
1602+
"stdout4\n"
1603+
"stdout5\n"
1604+
)
1605+
assert err is None

0 commit comments

Comments
 (0)