@@ -101,6 +101,8 @@ def __init__(self, logfile, prefix):
101
101
self .logfile = os .path .normpath (os .path .abspath (logfile ))
102
102
self .prefix = prefix
103
103
self .tests = []
104
+ self .tests_by_nodeid = {} # nodeid -> Junit.testcase
105
+ self .durations = {} # nodeid -> total duration (setup+call+teardown)
104
106
self .passed = self .skipped = 0
105
107
self .failed = self .errors = 0
106
108
self .custom_properties = {}
@@ -117,11 +119,16 @@ def _opentestcase(self, report):
117
119
"classname" : "." .join (classnames ),
118
120
"name" : bin_xml_escape (names [- 1 ]),
119
121
"file" : report .location [0 ],
120
- "time" : 0 ,
122
+ "time" : self . durations . get ( report . nodeid , 0 ) ,
121
123
}
122
124
if report .location [1 ] is not None :
123
125
attrs ["line" ] = report .location [1 ]
124
- self .tests .append (Junit .testcase (** attrs ))
126
+ testcase = Junit .testcase (** attrs )
127
+ custom_properties = self .pop_custom_properties ()
128
+ if custom_properties :
129
+ testcase .append (custom_properties )
130
+ self .tests .append (testcase )
131
+ self .tests_by_nodeid [report .nodeid ] = testcase
125
132
126
133
def _write_captured_output (self , report ):
127
134
for capname in ('out' , 'err' ):
@@ -136,17 +143,20 @@ def _write_captured_output(self, report):
136
143
def append (self , obj ):
137
144
self .tests [- 1 ].append (obj )
138
145
139
- def append_custom_properties (self ):
146
+ def pop_custom_properties (self ):
147
+ """Return a Junit node containing custom properties set for
148
+ the current test, if any, and reset the current custom properties.
149
+ """
140
150
if self .custom_properties :
141
- self .tests [- 1 ].append (
142
- Junit .properties (
143
- [
144
- Junit .property (name = name , value = value )
145
- for name , value in self .custom_properties .items ()
146
- ]
147
- )
151
+ result = Junit .properties (
152
+ [
153
+ Junit .property (name = name , value = value )
154
+ for name , value in self .custom_properties .items ()
155
+ ]
148
156
)
149
- self .custom_properties .clear ()
157
+ self .custom_properties .clear ()
158
+ return result
159
+ return None
150
160
151
161
def append_pass (self , report ):
152
162
self .passed += 1
@@ -206,20 +216,54 @@ def append_skipped(self, report):
206
216
self ._write_captured_output (report )
207
217
208
218
def pytest_runtest_logreport (self , report ):
209
- if report .when == "setup" :
210
- self ._opentestcase (report )
211
- self .tests [- 1 ].attr .time += getattr (report , 'duration' , 0 )
212
- self .append_custom_properties ()
219
+ """handle a setup/call/teardown report, generating the appropriate
220
+ xml tags as necessary.
221
+
222
+ note: due to plugins like xdist, this hook may be called in interlaced
223
+ order with reports from other nodes. for example:
224
+
225
+ usual call order:
226
+ -> setup node1
227
+ -> call node1
228
+ -> teardown node1
229
+ -> setup node2
230
+ -> call node2
231
+ -> teardown node2
232
+
233
+ possible call order in xdist:
234
+ -> setup node1
235
+ -> call node1
236
+ -> setup node2
237
+ -> call node2
238
+ -> teardown node2
239
+ -> teardown node1
240
+ """
213
241
if report .passed :
214
- if report .when == "call" : # ignore setup/teardown
242
+ if report .when == "call" : # ignore setup/teardown
243
+ self ._opentestcase (report )
215
244
self .append_pass (report )
216
245
elif report .failed :
246
+ self ._opentestcase (report )
217
247
if report .when != "call" :
218
248
self .append_error (report )
219
249
else :
220
250
self .append_failure (report )
221
251
elif report .skipped :
252
+ self ._opentestcase (report )
222
253
self .append_skipped (report )
254
+ self .update_testcase_duration (report )
255
+
256
+ def update_testcase_duration (self , report ):
257
+ """accumulates total duration for nodeid from given report and updates
258
+ the Junit.testcase with the new total if already created.
259
+ """
260
+ total = self .durations .get (report .nodeid , 0.0 )
261
+ total += getattr (report , 'duration' , 0.0 )
262
+ self .durations [report .nodeid ] = total
263
+
264
+ testcase = self .tests_by_nodeid .get (report .nodeid )
265
+ if testcase is not None :
266
+ testcase .attr .time = total
223
267
224
268
def pytest_collectreport (self , report ):
225
269
if not report .passed :
0 commit comments