@@ -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,29 @@ 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
+ if not hasattr (os , 'dup' ):
232
+ pytest .skip ("capfdbinary funcarg needs os.dup" )
233
+ with _install_capture_fixture_on_item (request , FDCaptureBinary ) as fixture :
234
+ yield fixture
235
+
236
+
212
237
@contextlib .contextmanager
213
238
def _install_capture_fixture_on_item (request , capture_class ):
214
239
"""
@@ -378,8 +403,11 @@ class NoCapture:
378
403
__init__ = start = done = suspend = resume = lambda * args : None
379
404
380
405
381
- class FDCapture :
382
- """ Capture IO to/from a given os-level filedescriptor. """
406
+ class FDCaptureBinary :
407
+ """Capture IO to/from a given os-level filedescriptor.
408
+
409
+ snap() produces `bytes`
410
+ """
383
411
384
412
def __init__ (self , targetfd , tmpfile = None ):
385
413
self .targetfd = targetfd
@@ -418,17 +446,11 @@ def start(self):
418
446
self .syscapture .start ()
419
447
420
448
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 ''
449
+ self .tmpfile .seek (0 )
450
+ res = self .tmpfile .read ()
451
+ self .tmpfile .seek (0 )
452
+ self .tmpfile .truncate ()
453
+ return res
432
454
433
455
def done (self ):
434
456
""" stop capturing, restore streams, return original capture file,
@@ -454,6 +476,19 @@ def writeorg(self, data):
454
476
os .write (self .targetfd_save , data )
455
477
456
478
479
+ class FDCapture (FDCaptureBinary ):
480
+ """Capture IO to/from a given os-level filedescriptor.
481
+
482
+ snap() produces text
483
+ """
484
+ def snap (self ):
485
+ res = FDCaptureBinary .snap (self )
486
+ enc = getattr (self .tmpfile , "encoding" , None )
487
+ if enc and isinstance (res , bytes ):
488
+ res = six .text_type (res , enc , "replace" )
489
+ return res
490
+
491
+
457
492
class SysCapture :
458
493
def __init__ (self , fd , tmpfile = None ):
459
494
name = patchsysdict [fd ]
0 commit comments