@@ -86,6 +86,37 @@ def parse_stats_file(file_path):
8686 return {"tests" : 0 , "passed" : 0 , "failed" : 0 , "skipped" : 0 , "slowest_tests" : []}
8787
8888
89+ def parse_durations_file (file_path ):
90+ """Parse a durations file to extract test timing information."""
91+ slowest_tests = []
92+ try :
93+ durations_file = file_path .replace ("_stats.txt" , "_durations.txt" )
94+ if os .path .exists (durations_file ):
95+ with open (durations_file , "r" ) as f :
96+ content = f .read ()
97+
98+ # Skip the header line
99+ for line in content .split ('\n ' )[1 :]:
100+ if line .strip ():
101+ # Format is typically: 10.37s call tests/path/to/test.py::TestClass::test_method
102+ parts = line .strip ().split ()
103+ if len (parts ) >= 3 :
104+ time_str = parts [0 ]
105+ test_path = ' ' .join (parts [2 :])
106+ try :
107+ time_seconds = float (time_str .rstrip ('s' ))
108+ slowest_tests .append ({
109+ "test" : test_path ,
110+ "duration" : time_seconds
111+ })
112+ except ValueError :
113+ pass
114+ except Exception as e :
115+ print (f"Error parsing durations file { file_path .replace ('_stats.txt' , '_durations.txt' )} : { e } " )
116+
117+ return slowest_tests
118+
119+
89120def parse_failures_file (file_path ):
90121 """Parse a failures file to extract failed test details."""
91122 failures = []
@@ -217,6 +248,10 @@ def consolidate_reports(reports_dir):
217248
218249 # Parse stats
219250 stats = parse_stats_file (stats_file )
251+
252+ # If no slowest tests found in stats file, try the durations file directly
253+ if not stats .get ("slowest_tests" ):
254+ stats ["slowest_tests" ] = parse_durations_file (stats_file )
220255
221256 # Update total stats
222257 for key in ["tests" , "passed" , "failed" , "skipped" ]:
@@ -271,8 +306,50 @@ def consolidate_reports(reports_dir):
271306 # Get the number of slowest tests to show from environment variable or default to 10
272307 num_slowest_tests = int (os .environ .get ("SHOW_SLOWEST_TESTS" , "10" ))
273308 top_slowest_tests = all_slow_tests [:num_slowest_tests ] if all_slow_tests else []
274-
275- return {"total_stats" : total_stats , "test_suites" : results , "slowest_tests" : top_slowest_tests }
309+
310+ # Calculate additional duration statistics
311+ total_duration = sum (test ["duration" ] for test in all_slow_tests )
312+
313+ # Calculate duration per suite
314+ suite_durations = {}
315+ for test in all_slow_tests :
316+ suite_name = test ["suite" ]
317+ if suite_name not in suite_durations :
318+ suite_durations [suite_name ] = 0
319+ suite_durations [suite_name ] += test ["duration" ]
320+
321+ # Create duration categories
322+ duration_categories = {
323+ "under_1s" : 0 ,
324+ "1s_to_5s" : 0 ,
325+ "5s_to_10s" : 0 ,
326+ "10s_to_30s" : 0 ,
327+ "over_30s" : 0
328+ }
329+
330+ for test in all_slow_tests :
331+ duration = test ["duration" ]
332+ if duration < 1 :
333+ duration_categories ["under_1s" ] += 1
334+ elif duration < 5 :
335+ duration_categories ["1s_to_5s" ] += 1
336+ elif duration < 10 :
337+ duration_categories ["5s_to_10s" ] += 1
338+ elif duration < 30 :
339+ duration_categories ["10s_to_30s" ] += 1
340+ else :
341+ duration_categories ["over_30s" ] += 1
342+
343+ return {
344+ "total_stats" : total_stats ,
345+ "test_suites" : results ,
346+ "slowest_tests" : top_slowest_tests ,
347+ "duration_stats" : {
348+ "total_duration" : total_duration ,
349+ "suite_durations" : suite_durations ,
350+ "duration_categories" : duration_categories
351+ }
352+ }
276353
277354
278355def generate_report (consolidated_data ):
@@ -289,21 +366,49 @@ def generate_report(consolidated_data):
289366 total = consolidated_data ["total_stats" ]
290367 report .append ("## Summary" )
291368
369+ # Get duration stats if available
370+ duration_stats = consolidated_data .get ("duration_stats" , {})
371+ total_duration = duration_stats .get ("total_duration" , 0 )
372+
292373 summary_table = [
293374 ["Total Tests" , total ["tests" ]],
294375 ["Passed" , total ["passed" ]],
295376 ["Failed" , total ["failed" ]],
296377 ["Skipped" , total ["skipped" ]],
297378 ["Success Rate" , f"{ (total ['passed' ] / total ['tests' ] * 100 ):.2f} %" if total ["tests" ] > 0 else "N/A" ],
379+ ["Total Duration" , f"{ total_duration :.2f} s" if total_duration else "N/A" ],
298380 ]
299381
300382 report .append (tabulate (summary_table , tablefmt = "pipe" ))
301383 report .append ("" )
384+
385+ # Add duration distribution if available
386+ duration_categories = duration_stats .get ("duration_categories" )
387+ if duration_categories :
388+ report .append ("### Test Duration Distribution" )
389+
390+ distribution_table = [
391+ ["Duration Range" , "Number of Tests" ],
392+ ["Under 1s" , duration_categories .get ("under_1s" , 0 )],
393+ ["1s to 5s" , duration_categories .get ("1s_to_5s" , 0 )],
394+ ["5s to 10s" , duration_categories .get ("5s_to_10s" , 0 )],
395+ ["10s to 30s" , duration_categories .get ("10s_to_30s" , 0 )],
396+ ["Over 30s" , duration_categories .get ("over_30s" , 0 )],
397+ ]
398+
399+ report .append (tabulate (distribution_table , headers = "firstrow" , tablefmt = "pipe" ))
400+ report .append ("" )
302401
303402 # Add test suites summary
304403 report .append ("## Test Suites" )
305404
306- suites_table = [["Test Suite" , "Tests" , "Passed" , "Failed" , "Skipped" , "Success Rate" ]]
405+ # Include duration in test suites table if available
406+ suite_durations = consolidated_data .get ("duration_stats" , {}).get ("suite_durations" , {})
407+
408+ if suite_durations :
409+ suites_table = [["Test Suite" , "Tests" , "Passed" , "Failed" , "Skipped" , "Success Rate" , "Duration (s)" ]]
410+ else :
411+ suites_table = [["Test Suite" , "Tests" , "Passed" , "Failed" , "Skipped" , "Success Rate" ]]
307412
308413 # Sort test suites by number of failures (descending)
309414 sorted_suites = sorted (
@@ -313,9 +418,16 @@ def generate_report(consolidated_data):
313418 for suite_name , suite_data in sorted_suites :
314419 stats = suite_data ["stats" ]
315420 success_rate = f"{ (stats ['passed' ] / stats ['tests' ] * 100 ):.2f} %" if stats ["tests" ] > 0 else "N/A"
316- suites_table .append (
317- [suite_name , stats ["tests" ], stats ["passed" ], stats ["failed" ], stats ["skipped" ], success_rate ]
318- )
421+
422+ if suite_durations :
423+ duration = suite_durations .get (suite_name , 0 )
424+ suites_table .append (
425+ [suite_name , stats ["tests" ], stats ["passed" ], stats ["failed" ], stats ["skipped" ], success_rate , f"{ duration :.2f} " ]
426+ )
427+ else :
428+ suites_table .append (
429+ [suite_name , stats ["tests" ], stats ["passed" ], stats ["failed" ], stats ["skipped" ], success_rate ]
430+ )
319431
320432 report .append (tabulate (suites_table , headers = "firstrow" , tablefmt = "pipe" ))
321433 report .append ("" )
0 commit comments