@@ -358,11 +358,17 @@ def close(self):
358
358
self ._capture .stop_capturing ()
359
359
self ._capture = None
360
360
361
- def readouterr (self ):
361
+ def readouterr (self , combined = False , flush = True ):
362
362
"""Read and return the captured output so far, resetting the internal buffer.
363
363
364
364
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
365
365
"""
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 )
366
372
captured_out , captured_err = self ._captured_out , self ._captured_err
367
373
if self ._capture is not None :
368
374
out , err = self ._capture .readouterr ()
@@ -372,6 +378,13 @@ def readouterr(self):
372
378
self ._captured_err = self .captureclass .EMPTY_BUFFER
373
379
return CaptureResult (captured_out , captured_err )
374
380
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
+
375
388
def _suspend (self ):
376
389
"""Suspends this fixture's own capturing temporarily."""
377
390
if self ._capture is not None :
@@ -637,12 +650,16 @@ def __init__(self, fd, tmpfile=None):
637
650
name = patchsysdict [fd ]
638
651
self ._old = getattr (sys , name )
639
652
self .name = name
653
+ self .fd = fd
640
654
if tmpfile is None :
641
655
if name == "stdin" :
642
656
tmpfile = DontReadFromInput ()
643
657
else :
644
- tmpfile = CaptureIO ()
658
+ tmpfile = self . _get_writer ()
645
659
self .tmpfile = tmpfile
660
+
661
+ def _get_writer (self ):
662
+ return CaptureIO ()
646
663
647
664
def __repr__ (self ):
648
665
return "<{} {} _old={} _state={!r} tmpfile={!r}>" .format (
@@ -705,14 +722,65 @@ def __init__(self, fd, tmpfile=None):
705
722
self .tmpfile = tmpfile
706
723
707
724
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
+
708
758
map_fixname_class = {
709
759
"capfd" : FDCapture ,
710
760
"capfdbinary" : FDCaptureBinary ,
711
- "capsys" : SysCapture ,
761
+ "capsys" : OrderedCapture ,
712
762
"capsysbinary" : SysCaptureBinary ,
713
763
}
714
764
715
765
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
+
716
784
class DontReadFromInput :
717
785
encoding = None
718
786
0 commit comments