@@ -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+
708758map_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+
716784class DontReadFromInput :
717785 encoding = None
718786
0 commit comments