21
21
def pytest_addoption (parser ):
22
22
group = parser .getgroup ("general" )
23
23
group ._addoption (
24
- '--capture' , action = "store" , default = None ,
24
+ '--capture' , action = "store" ,
25
+ default = "fd" if hasattr (os , "dup" ) else "sys" ,
25
26
metavar = "method" , choices = ['fd' , 'sys' , 'no' ],
26
- help = "per-test capturing method: one of fd (default) |sys|no." )
27
+ help = "per-test capturing method: one of fd|sys|no." )
27
28
group ._addoption (
28
29
'-s' , action = "store_const" , const = "no" , dest = "capture" ,
29
30
help = "shortcut for --capture=no." )
@@ -32,16 +33,13 @@ def pytest_addoption(parser):
32
33
@pytest .mark .tryfirst
33
34
def pytest_load_initial_conftests (early_config , parser , args , __multicall__ ):
34
35
ns = parser .parse_known_args (args )
35
- method = ns .capture
36
- if not method :
37
- method = "fd"
38
- if method == "fd" and not hasattr (os , "dup" ):
39
- method = "sys"
40
36
pluginmanager = early_config .pluginmanager
37
+ method = ns .capture
41
38
if method != "no" :
42
39
dupped_stdout = safe_text_dupfile (sys .stdout , "wb" )
43
40
pluginmanager .register (dupped_stdout , "dupped_stdout" )
44
41
#pluginmanager.add_shutdown(dupped_stdout.close)
42
+
45
43
capman = CaptureManager (method )
46
44
pluginmanager .register (capman , "capturemanager" )
47
45
@@ -55,7 +53,7 @@ def silence_logging_at_shutdown():
55
53
pluginmanager .add_shutdown (silence_logging_at_shutdown )
56
54
57
55
# finally trigger conftest loading but while capturing (issue93)
58
- capman .resumecapture ()
56
+ capman .init_capturings ()
59
57
try :
60
58
try :
61
59
return __multicall__ .execute ()
@@ -67,11 +65,9 @@ def silence_logging_at_shutdown():
67
65
raise
68
66
69
67
70
-
71
68
class CaptureManager :
72
- def __init__ (self , defaultmethod = None ):
73
- self ._method2capture = {}
74
- self ._defaultmethod = defaultmethod
69
+ def __init__ (self , method ):
70
+ self ._method = method
75
71
76
72
def _getcapture (self , method ):
77
73
if method == "fd" :
@@ -83,53 +79,27 @@ def _getcapture(self, method):
83
79
else :
84
80
raise ValueError ("unknown capturing method: %r" % method )
85
81
86
- def _getmethod (self , config , fspath ):
87
- if config .option .capture :
88
- method = config .option .capture
89
- else :
90
- try :
91
- method = config ._conftest .rget ("option_capture" , path = fspath )
92
- except KeyError :
93
- method = "fd"
94
- if method == "fd" and not hasattr (os , 'dup' ): # e.g. jython
95
- method = "sys"
96
- return method
82
+ def init_capturings (self ):
83
+ assert not hasattr (self , "_capturing" )
84
+ self ._capturing = self ._getcapture (self ._method )
85
+ self ._capturing .start_capturing ()
97
86
98
87
def reset_capturings (self ):
99
- for cap in self ._method2capture .values ():
88
+ cap = self .__dict__ .pop ("_capturing" , None )
89
+ if cap is not None :
100
90
cap .pop_outerr_to_orig ()
101
91
cap .stop_capturing ()
102
- self ._method2capture .clear ()
103
-
104
- def resumecapture_item (self , item ):
105
- method = self ._getmethod (item .config , item .fspath )
106
- return self .resumecapture (method )
107
-
108
- def resumecapture (self , method = None ):
109
- if hasattr (self , '_capturing' ):
110
- raise ValueError (
111
- "cannot resume, already capturing with %r" %
112
- (self ._capturing ,))
113
- if method is None :
114
- method = self ._defaultmethod
115
- cap = self ._method2capture .get (method )
116
- self ._capturing = method
117
- if cap is None :
118
- self ._method2capture [method ] = cap = self ._getcapture (method )
119
- cap .start_capturing ()
120
- else :
121
- cap .resume_capturing ()
122
92
123
- def suspendcapture (self , item = None ):
93
+ def resumecapture (self ):
94
+ self ._capturing .resume_capturing ()
95
+
96
+ def suspendcapture (self , in_ = False ):
124
97
self .deactivate_funcargs ()
125
- method = self .__dict__ .pop ("_capturing" , None )
126
- outerr = "" , ""
127
- if method is not None :
128
- cap = self ._method2capture .get (method )
129
- if cap is not None :
130
- outerr = cap .readouterr ()
131
- cap .suspend_capturing ()
132
- return outerr
98
+ cap = getattr (self , "_capturing" , None )
99
+ if cap is not None :
100
+ outerr = cap .readouterr ()
101
+ cap .suspend_capturing (in_ = in_ )
102
+ return outerr
133
103
134
104
def activate_funcargs (self , pyfuncitem ):
135
105
capfuncarg = pyfuncitem .__dict__ .pop ("_capfuncarg" , None )
@@ -142,28 +112,20 @@ def deactivate_funcargs(self):
142
112
if capfuncarg is not None :
143
113
capfuncarg .close ()
144
114
145
- @pytest .mark .hookwrapper
115
+ @pytest .mark .tryfirst
146
116
def pytest_make_collect_report (self , __multicall__ , collector ):
147
- method = self ._getmethod (collector .config , collector .fspath )
148
- try :
149
- self .resumecapture (method )
150
- except ValueError :
151
- yield
152
- # recursive collect, XXX refactor capturing
153
- # to allow for more lightweight recursive capturing
117
+ if not isinstance (collector , pytest .File ):
154
118
return
155
- yield
156
- out , err = self .suspendcapture ()
157
- # XXX getting the report from the ongoing hook call is a bit
158
- # of a hack. We need to think about capturing during collection
159
- # and find out if it's really needed fine-grained (per
160
- # collector).
161
- if __multicall__ .results :
162
- rep = __multicall__ .results [0 ]
163
- if out :
164
- rep .sections .append (("Captured stdout" , out ))
165
- if err :
166
- rep .sections .append (("Captured stderr" , err ))
119
+ self .resumecapture ()
120
+ try :
121
+ rep = __multicall__ .execute ()
122
+ finally :
123
+ out , err = self .suspendcapture ()
124
+ if out :
125
+ rep .sections .append (("Captured stdout" , out ))
126
+ if err :
127
+ rep .sections .append (("Captured stderr" , err ))
128
+ return rep
167
129
168
130
@pytest .mark .hookwrapper
169
131
def pytest_runtest_setup (self , item ):
@@ -192,9 +154,9 @@ def pytest_internalerror(self, excinfo):
192
154
193
155
@contextlib .contextmanager
194
156
def item_capture_wrapper (self , item , when ):
195
- self .resumecapture_item ( item )
157
+ self .resumecapture ( )
196
158
yield
197
- out , err = self .suspendcapture (item )
159
+ out , err = self .suspendcapture ()
198
160
item .add_report_section (when , "out" , out )
199
161
item .add_report_section (when , "err" , err )
200
162
@@ -238,14 +200,14 @@ def _start(self):
238
200
def close (self ):
239
201
cap = self .__dict__ .pop ("_capture" , None )
240
202
if cap is not None :
241
- cap .pop_outerr_to_orig ()
203
+ self . _outerr = cap .pop_outerr_to_orig ()
242
204
cap .stop_capturing ()
243
205
244
206
def readouterr (self ):
245
207
try :
246
208
return self ._capture .readouterr ()
247
209
except AttributeError :
248
- return "" , ""
210
+ return self . _outerr
249
211
250
212
251
213
def safe_text_dupfile (f , mode , default_encoding = "UTF8" ):
@@ -311,18 +273,25 @@ def pop_outerr_to_orig(self):
311
273
self .out .writeorg (out )
312
274
if err :
313
275
self .err .writeorg (err )
276
+ return out , err
314
277
315
- def suspend_capturing (self ):
278
+ def suspend_capturing (self , in_ = False ):
316
279
if self .out :
317
280
self .out .suspend ()
318
281
if self .err :
319
282
self .err .suspend ()
283
+ if in_ and self .in_ :
284
+ self .in_ .suspend ()
285
+ self ._in_suspended = True
320
286
321
287
def resume_capturing (self ):
322
288
if self .out :
323
289
self .out .resume ()
324
290
if self .err :
325
291
self .err .resume ()
292
+ if hasattr (self , "_in_suspended" ):
293
+ self .in_ .resume ()
294
+ del self ._in_suspended
326
295
327
296
def stop_capturing (self ):
328
297
""" stop capturing and reset capturing streams """
@@ -393,7 +362,8 @@ def snap(self):
393
362
res = py .builtin ._totext (res , enc , "replace" )
394
363
f .truncate (0 )
395
364
f .seek (0 )
396
- return res
365
+ return res
366
+ return ''
397
367
398
368
def done (self ):
399
369
""" stop capturing, restore streams, return original capture file,
0 commit comments