7
7
import os
8
8
import re
9
9
import warnings
10
+ from collections import defaultdict
10
11
from pathlib import Path
11
12
12
13
import pytest
@@ -29,6 +30,7 @@ def __init__(self, report_path, config, report_data, template, css):
29
30
config .getini ("max_asset_filename_length" )
30
31
)
31
32
33
+ self ._reports = defaultdict (dict )
32
34
self ._report = report_data
33
35
self ._report .title = self ._report_path .name
34
36
@@ -204,15 +206,50 @@ def pytest_runtest_logreport(self, report):
204
206
DeprecationWarning ,
205
207
)
206
208
209
+ # "reruns" makes this code a mess.
210
+ # We store each combination of when and outcome
211
+ # exactly once, unless that outcome is a "rerun"
212
+ # then we store all of them.
213
+ key = (report .when , report .outcome )
214
+ if report .outcome == "rerun" :
215
+ if key not in self ._reports [report .nodeid ]:
216
+ self ._reports [report .nodeid ][key ] = list ()
217
+ self ._reports [report .nodeid ][key ].append (report )
218
+ else :
219
+ self ._reports [report .nodeid ][key ] = [report ]
220
+
221
+ self ._report .total_duration += report .duration
222
+
223
+ finished = report .when == "teardown" and report .outcome != "rerun"
224
+ if not finished :
225
+ return
226
+
227
+ # Calculate total duration for a single test.
228
+ # This is needed to add the "teardown" duration
229
+ # to tests total duration.
230
+ test_duration = 0
231
+ for key , reports in self ._reports [report .nodeid ].items ():
232
+ _ , outcome = key
233
+ if outcome != "rerun" :
234
+ test_duration += reports [0 ].duration
235
+
236
+ for key , reports in self ._reports [report .nodeid ].items ():
237
+ when , _ = key
238
+ for each in reports :
239
+ dur = test_duration if when == "call" else each .duration
240
+ self ._process_report (each , dur )
241
+
242
+ self ._generate_report ()
243
+
244
+ def _process_report (self , report , duration ):
207
245
outcome = _process_outcome (report )
208
246
try :
209
247
# hook returns as list for some reason
210
- duration = self ._config .hook .pytest_html_duration_format (
211
- duration = report . duration
248
+ formatted_duration = self ._config .hook .pytest_html_duration_format (
249
+ duration = duration
212
250
)[0 ]
213
251
except IndexError :
214
- duration = _format_duration (report .duration )
215
- self ._report .total_duration += report .duration
252
+ formatted_duration = _format_duration (duration )
216
253
217
254
test_id = report .nodeid
218
255
if report .when != "call" :
@@ -229,7 +266,7 @@ def pytest_runtest_logreport(self, report):
229
266
cells = [
230
267
f'<td class="col-result">{ outcome } </td>' ,
231
268
f'<td class="col-testId">{ test_id } </td>' ,
232
- f'<td class="col-duration">{ duration } </td>' ,
269
+ f'<td class="col-duration">{ formatted_duration } </td>' ,
233
270
f'<td class="col-links">{ _process_links (links )} </td>' ,
234
271
]
235
272
self ._config .hook .pytest_html_results_table_row (report = report , cells = cells )
@@ -240,17 +277,12 @@ def pytest_runtest_logreport(self, report):
240
277
self ._hydrate_data (data , cells )
241
278
data ["resultsTableRow" ] = cells
242
279
243
- # don't count passed setups and teardowns
244
- if not (report .when in ["setup" , "teardown" ] and report .outcome == "passed" ):
245
- self ._report .outcomes = outcome
246
-
247
280
processed_logs = _process_logs (report )
248
281
self ._config .hook .pytest_html_results_table_html (
249
282
report = report , data = processed_logs
250
283
)
251
284
252
- if self ._report .add_test (data , report , processed_logs ):
253
- self ._generate_report ()
285
+ self ._report .add_test (data , report , outcome , processed_logs )
254
286
255
287
256
288
def _format_duration (duration ):
0 commit comments