@@ -101,6 +101,8 @@ def __init__(self, logfile, prefix):
101101 self .logfile = os .path .normpath (os .path .abspath (logfile ))
102102 self .prefix = prefix
103103 self .tests = []
104+ self .tests_by_nodeid = {} # nodeid -> Junit.testcase
105+ self .durations = {} # nodeid -> total duration (setup+call+teardown)
104106 self .passed = self .skipped = 0
105107 self .failed = self .errors = 0
106108 self .custom_properties = {}
@@ -117,11 +119,16 @@ def _opentestcase(self, report):
117119 "classname" : "." .join (classnames ),
118120 "name" : bin_xml_escape (names [- 1 ]),
119121 "file" : report .location [0 ],
120- "time" : 0 ,
122+ "time" : self . durations . get ( report . nodeid , 0 ) ,
121123 }
122124 if report .location [1 ] is not None :
123125 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
125132
126133 def _write_captured_output (self , report ):
127134 for capname in ('out' , 'err' ):
@@ -136,17 +143,20 @@ def _write_captured_output(self, report):
136143 def append (self , obj ):
137144 self .tests [- 1 ].append (obj )
138145
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+ """
140150 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+ ]
148156 )
149- self .custom_properties .clear ()
157+ self .custom_properties .clear ()
158+ return result
159+ return None
150160
151161 def append_pass (self , report ):
152162 self .passed += 1
@@ -206,20 +216,54 @@ def append_skipped(self, report):
206216 self ._write_captured_output (report )
207217
208218 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+ """
213241 if report .passed :
214- if report .when == "call" : # ignore setup/teardown
242+ if report .when == "call" : # ignore setup/teardown
243+ self ._opentestcase (report )
215244 self .append_pass (report )
216245 elif report .failed :
246+ self ._opentestcase (report )
217247 if report .when != "call" :
218248 self .append_error (report )
219249 else :
220250 self .append_failure (report )
221251 elif report .skipped :
252+ self ._opentestcase (report )
222253 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
223267
224268 def pytest_collectreport (self , report ):
225269 if not report .passed :
0 commit comments