@@ -180,17 +180,29 @@ def suspend_capture_item(self, item, when, in_=False):
180
180
item .add_report_section (when , "stderr" , err )
181
181
182
182
183
- error_capsysfderror = "cannot use capsys and capfd at the same time"
183
+ capture_fixtures = {'capfd' , 'capfdbinary' , 'capsys' }
184
+
185
+
186
+ def _ensure_only_one_capture_fixture (request , name ):
187
+ fixtures = set (request .fixturenames ) & capture_fixtures - set ((name ,))
188
+ if fixtures :
189
+ fixtures = sorted (fixtures )
190
+ fixtures = fixtures [0 ] if len (fixtures ) == 1 else fixtures
191
+ raise request .raiseerror (
192
+ "cannot use {0} and {1} at the same time" .format (
193
+ fixtures , name ,
194
+ ),
195
+ )
184
196
185
197
186
198
@pytest .fixture
187
199
def capsys (request ):
188
200
"""Enable capturing of writes to sys.stdout/sys.stderr and make
189
201
captured output available via ``capsys.readouterr()`` method calls
190
- which return a ``(out, err)`` tuple.
202
+ which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
203
+ objects.
191
204
"""
192
- if "capfd" in request .fixturenames :
193
- raise request .raiseerror (error_capsysfderror )
205
+ _ensure_only_one_capture_fixture (request , 'capsys' )
194
206
with _install_capture_fixture_on_item (request , SysCapture ) as fixture :
195
207
yield fixture
196
208
@@ -199,16 +211,30 @@ def capsys(request):
199
211
def capfd (request ):
200
212
"""Enable capturing of writes to file descriptors 1 and 2 and make
201
213
captured output available via ``capfd.readouterr()`` method calls
202
- which return a ``(out, err)`` tuple.
214
+ which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
215
+ objects.
203
216
"""
204
- if "capsys" in request .fixturenames :
205
- request .raiseerror (error_capsysfderror )
217
+ _ensure_only_one_capture_fixture (request , 'capfd' )
206
218
if not hasattr (os , 'dup' ):
207
219
pytest .skip ("capfd fixture needs os.dup function which is not available in this system" )
208
220
with _install_capture_fixture_on_item (request , FDCapture ) as fixture :
209
221
yield fixture
210
222
211
223
224
+ @pytest .fixture
225
+ def capfdbinary (request ):
226
+ """Enable capturing of write to file descriptors 1 and 2 and make
227
+ captured output available via ``capfdbinary.readouterr`` method calls
228
+ which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
229
+ ``bytes`` objects.
230
+ """
231
+ _ensure_only_one_capture_fixture (request , 'capfdbinary' )
232
+ if not hasattr (os , 'dup' ):
233
+ pytest .skip ("capfdbinary fixture needs os.dup function which is not available in this system" )
234
+ with _install_capture_fixture_on_item (request , FDCaptureBinary ) as fixture :
235
+ yield fixture
236
+
237
+
212
238
@contextlib .contextmanager
213
239
def _install_capture_fixture_on_item (request , capture_class ):
214
240
"""
@@ -378,8 +404,11 @@ class NoCapture:
378
404
__init__ = start = done = suspend = resume = lambda * args : None
379
405
380
406
381
- class FDCapture :
382
- """ Capture IO to/from a given os-level filedescriptor. """
407
+ class FDCaptureBinary :
408
+ """Capture IO to/from a given os-level filedescriptor.
409
+
410
+ snap() produces `bytes`
411
+ """
383
412
384
413
def __init__ (self , targetfd , tmpfile = None ):
385
414
self .targetfd = targetfd
@@ -418,17 +447,11 @@ def start(self):
418
447
self .syscapture .start ()
419
448
420
449
def snap (self ):
421
- f = self .tmpfile
422
- f .seek (0 )
423
- res = f .read ()
424
- if res :
425
- enc = getattr (f , "encoding" , None )
426
- if enc and isinstance (res , bytes ):
427
- res = six .text_type (res , enc , "replace" )
428
- f .truncate (0 )
429
- f .seek (0 )
430
- return res
431
- return ''
450
+ self .tmpfile .seek (0 )
451
+ res = self .tmpfile .read ()
452
+ self .tmpfile .seek (0 )
453
+ self .tmpfile .truncate ()
454
+ return res
432
455
433
456
def done (self ):
434
457
""" stop capturing, restore streams, return original capture file,
@@ -454,6 +477,19 @@ def writeorg(self, data):
454
477
os .write (self .targetfd_save , data )
455
478
456
479
480
+ class FDCapture (FDCaptureBinary ):
481
+ """Capture IO to/from a given os-level filedescriptor.
482
+
483
+ snap() produces text
484
+ """
485
+ def snap (self ):
486
+ res = FDCaptureBinary .snap (self )
487
+ enc = getattr (self .tmpfile , "encoding" , None )
488
+ if enc and isinstance (res , bytes ):
489
+ res = six .text_type (res , enc , "replace" )
490
+ return res
491
+
492
+
457
493
class SysCapture :
458
494
def __init__ (self , fd , tmpfile = None ):
459
495
name = patchsysdict [fd ]
0 commit comments