@@ -348,11 +348,17 @@ def close(self):
348348 self ._capture .stop_capturing ()
349349 self ._capture = None
350350
351- def readouterr (self ) :
351+ def readouterr (self , combined : bool = False , flush : bool = True ) -> "CaptureResult" :
352352 """Read and return the captured output so far, resetting the internal buffer.
353353
354354 :return: captured content as a namedtuple with ``out`` and ``err`` string attributes
355355 """
356+ if flush is False :
357+ if self .captureclass is not OrderedCapture :
358+ raise AttributeError ("Only capsys can read streams without flushing." )
359+ OrderedCapture .set_flush (False )
360+ if combined :
361+ return CaptureResult (self ._get_combined (), None )
356362 captured_out , captured_err = self ._captured_out , self ._captured_err
357363 if self ._capture is not None :
358364 out , err = self ._capture .readouterr ()
@@ -362,6 +368,13 @@ def readouterr(self):
362368 self ._captured_err = self .captureclass .EMPTY_BUFFER
363369 return CaptureResult (captured_out , captured_err )
364370
371+ def _get_combined (self ):
372+ if self .captureclass is not OrderedCapture :
373+ raise AttributeError ("Only capsys is able to combine streams." )
374+ result = "" .join (line [0 ] for line in OrderedCapture .streams )
375+ OrderedCapture .flush ()
376+ return result
377+
365378 def _suspend (self ):
366379 """Suspends this fixture's own capturing temporarily."""
367380 if self ._capture is not None :
@@ -604,13 +617,17 @@ def __init__(self, fd, tmpfile=None):
604617 name = patchsysdict [fd ]
605618 self ._old = getattr (sys , name )
606619 self .name = name
620+ self .fd = fd
607621 if tmpfile is None :
608622 if name == "stdin" :
609623 tmpfile = DontReadFromInput ()
610624 else :
611- tmpfile = CaptureIO ()
625+ tmpfile = self . _get_writer ()
612626 self .tmpfile = tmpfile
613627
628+ def _get_writer (self ):
629+ return CaptureIO ()
630+
614631 def __repr__ (self ):
615632 return "<{} {} _old={} _state={!r} tmpfile={!r}>" .format (
616633 self .__class__ .__name__ ,
@@ -677,14 +694,74 @@ def __init__(self, fd, tmpfile=None):
677694 self .tmpfile = tmpfile
678695
679696
697+ class OrderedCapture (SysCapture ):
698+ """Capture class that keeps streams in order."""
699+
700+ streams = collections .deque () # type: collections.deque
701+ _flush = True
702+
703+ def _get_writer (self ):
704+ return OrderedWriter (self .fd )
705+
706+ def snap (self ):
707+ res = self .tmpfile .getvalue ()
708+ if self .name == "stderr" :
709+ # both streams are being read one after another, while stderr is last - it will clear the queue
710+ self .flush ()
711+ return res
712+
713+ @classmethod
714+ def set_flush (cls , flush : bool ) -> None :
715+ cls ._flush = flush
716+
717+ @classmethod
718+ def flush (cls ) -> None :
719+ """Clear streams."""
720+ if cls ._flush is False :
721+ cls .set_flush (True )
722+ else :
723+ cls .streams .clear ()
724+
725+ @classmethod
726+ def close (cls ) -> None :
727+ cls .set_flush (True )
728+ cls .flush ()
729+
730+
680731map_fixname_class = {
681732 "capfd" : FDCapture ,
682733 "capfdbinary" : FDCaptureBinary ,
683- "capsys" : SysCapture ,
734+ "capsys" : OrderedCapture ,
684735 "capsysbinary" : SysCaptureBinary ,
685736}
686737
687738
739+ class OrderedWriter :
740+ encoding = sys .getdefaultencoding ()
741+
742+ def __init__ (self , fd : int ) -> None :
743+ super ().__init__ ()
744+ self ._fd = fd # type: int
745+
746+ def write (self , text : str , ** kwargs ) -> int :
747+ OrderedCapture .streams .append ((text , self ._fd ))
748+ return len (text )
749+
750+ def getvalue (self ) -> str :
751+ return "" .join (
752+ line [0 ] for line in OrderedCapture .streams if line [1 ] == self ._fd
753+ )
754+
755+ def flush (self ) -> None :
756+ pass
757+
758+ def isatty (self ) -> bool :
759+ return False
760+
761+ def close (self ) -> None :
762+ OrderedCapture .close ()
763+
764+
688765class DontReadFromInput :
689766 encoding = None
690767
0 commit comments