77import os
88import re
99import warnings
10+ from collections import defaultdict
1011from pathlib import Path
1112
1213import pytest
@@ -29,6 +30,7 @@ def __init__(self, report_path, config, report_data, template, css):
2930 config .getini ("max_asset_filename_length" )
3031 )
3132
33+ self ._reports = defaultdict (dict )
3234 self ._report = report_data
3335 self ._report .title = self ._report_path .name
3436
@@ -204,15 +206,50 @@ def pytest_runtest_logreport(self, report):
204206 DeprecationWarning ,
205207 )
206208
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 ):
207245 outcome = _process_outcome (report )
208246 try :
209247 # 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
212250 )[0 ]
213251 except IndexError :
214- duration = _format_duration (report .duration )
215- self ._report .total_duration += report .duration
252+ formatted_duration = _format_duration (duration )
216253
217254 test_id = report .nodeid
218255 if report .when != "call" :
@@ -229,7 +266,7 @@ def pytest_runtest_logreport(self, report):
229266 cells = [
230267 f'<td class="col-result">{ outcome } </td>' ,
231268 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>' ,
233270 f'<td class="col-links">{ _process_links (links )} </td>' ,
234271 ]
235272 self ._config .hook .pytest_html_results_table_row (report = report , cells = cells )
@@ -240,17 +277,12 @@ def pytest_runtest_logreport(self, report):
240277 self ._hydrate_data (data , cells )
241278 data ["resultsTableRow" ] = cells
242279
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-
247280 processed_logs = _process_logs (report )
248281 self ._config .hook .pytest_html_results_table_html (
249282 report = report , data = processed_logs
250283 )
251284
252- if self ._report .add_test (data , report , processed_logs ):
253- self ._generate_report ()
285+ self ._report .add_test (data , report , outcome , processed_logs )
254286
255287
256288def _format_duration (duration ):
0 commit comments